Hibernate 核心技术(二)
Hibernate一级缓存
简介
缓存,介于应用程序和永久数据存储源之间,作用是为了降低应用程序对物理数据源访问的频率,从而提高应用的运行性能。
例如我们cpu执行效率每秒处理的数据高达上千兆,而我们的硬盘读取速度却没那么高,读取几百兆,这时候我们使用缓存来存储数据,存储满后一次性交由cpu处理。
Hibernate中也存在缓存,同样是为了提高效率。Hibernate的缓存包括Session的缓存和SessionFactory的缓存。
Session的缓存是内置的,不能被卸载,也被称为Hibertnate的一级缓存。
SessionFactory有一个内置缓存和外置缓存。SessionFactory的外置缓存是一个可配置的缓存插件。默认情况下,Hibernate不会启用这个缓存插件。被称为Hibernate的二级缓存。
缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。
事务范围:缓存只能被当前事务访问。一级缓存是Session的缓存,Session对象生命周期通常对应一个事务,因此是事务范围的缓存。 进程范围:缓存被进程内的所有事务共享。二级缓存是可配置的缓存插件,由SessionFactory管理,SessionFactory生命周期和应用程序的进程对应,因此是进程范围的缓存。集群范围:在集群环境中,缓存被同一个机器或者多个机器上的多个进程共享。
Session缓存
Session缓存是Hibernate的一级缓存。Session对象中具有一个缓存。Session的缓存是一块内存空间,存放的是持久化对象。
当Session通过save方法持久化一个对象时,该对象被加入到Session缓存中。
当Session通过get方法获取一个持久化对象时,Session会先判断Session缓存中是否存在这个对象,如果存在,就不需要再从数据库中查找。
========我们来测试一下缓存的存在==============
//开启事务Transaction ts=session.beginTransaction();//加上断点,当我们执行完这一步,会打印select语句,而后面的都不会打印,说明并没有从数据库中获取User user1=session.get(User.class, 5); //这次get方法会先从session缓存中查找,由于已经存在,直接返回引用 User user2=session.get(User.class, 5);User user3=session.get(User.class, 5);System.out.println(user1==user2);//trueSystem.out.println(user1==user3);//truesession.close();
脏检查及清理缓存的机制
我们先来看下面的例子
Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);user1.setName("swaggy");ts.commit();
我们发现我们改变了Name属性,这时候session缓存中的对象的name属性和数据库表中的NAME字段不一致了。但是我们并没有进行更新操作,而是直接提交了事务
幸运的是,Session中在清理缓存的时候,会自动进行脏检查。如果发现Session缓存中的持久化对象和数据库中的记录不一致,就会根据对象的最新属性去更新数据库。
所以在本例中,Session会自动提交一个update语句对数据库进行更新。
Session是怎样进行脏检查的呢?
当一个对象被加入到Sesion缓存中时,Session会为该对象复制一份快照。当Session清理缓存时,会比较当前对象的属性和快照来判断是否发生变化,如果发生变化,就会根据最新属性来执行相关的更新操作。
我们看下面一个例子加深对快照的理解
//我们从数据库中取出 id为5,name为tom,password为123456的对象Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);session.update(user1);session.close(); 过程:获取了持久化对象,放入缓存中,并创建了快照,我们执行更新,Session缓存中的对象会和快照进行比较,没有任何变化,所以不会执行update语句。
//我们自己设置一个和数据库中一模一样的对象,这时候会打印update语句Transaction ts=session.beginTransaction();User user=new User();user.setId(5);user.setName("tom");user.setPassword("123456");session.update(user);ts.commit();session.close();过程:因为此时我们执行update语句时会将对象直接放入缓存中,但是没有持久化对象的快照,所以进行对比结果就是不一致,所以尽管什么都没更改,还是会执行update语句,在控制台打印。
什么时候会清理缓存呢?
-默认情况下,在调用commit()方法时会先清理缓存。再向数据库提交事务。
-当执行查询操作时,如果缓存中的持久化对象属性已经发生了改变,就会先清理缓存,同步数据,保证查询到的是正确的结果。
-当应用程序显式调用Session的flush()方法时
-Session清理缓存的例外情况,如果对象使用的是native生成策略生成OID,那么调用Session的save()方法来保存该对象时,会立刻执行向数据库的插入语句。
如果不希望Session在以上默认的时间点清理缓存,可以通过Session的setFlushMode()方法来设定清理缓存的时间点。
FlushMode类定义了三种清理模式。
各种查询方法 commit()方法 flush()方法
-FlushMode.AUTO(默认模式) 清理 清理 清理
-FlushMode.COMMIT 不清理 清理 清理
-FlushMode.NEVER 不清理 不清理 清理
例如Session.setFlushMode(FlushMode.AUTO)
Java对象在Hibernate持久化层的状态
-临时状态:刚用new语句创建对象,还没有被持久化,并且不处于Session缓存中。处于临时状态的java对象被称为临时对象。
-持久化状态:已经被持久化,并且加入到Session的缓存中。处于持久化状态的java对象被成为持久化对象。
-游离状态:已经被持久化,但不再处于Session的缓存中。处于游离状态的java对象被成为游离对象。
//对象状态转换的过程Transaction ts=session.beginTransaction();User user =new User(); //临时状态user.setName("tom");user.setPassword("123456");session.save(user); //转变为持久化状态ts.commit(); //持久化状态 session.close(); //转变为游离状态sessionfactory.close();System.out.println(user.getName()); //游离状态
临时对象的特征:-OID为null
-不处于Session的缓存中,也可以说,不被任何一个Session实例关联
-在数据库中没有对应的记录
持久化对象的特征:-OID不为null
-位于一个Session实例的缓存中,持久化对象总是被一个Session实例关联
-持久化对象和数据库中的相关记录对应
-会根据持久化对象的属性变化,同步更新数据库。
User user=(User)session.get(User.class,1);//获取持久化对象 持久状态user.setName("jerry");transaction.commit();我们发现我们没有执行update语句,却打印了update语句。Hibernate会自动将持久化对象的状态同步到数据库中。游离对象的特征:-OID不为null
-不再位于Session的缓存中,也可以说,游离对象不被Session关联
-数据库中有对应的记录
三种状态的转换
1)临时状态转换持久化状态-Session的save()方法会将临时状态转换成持久状态。把要保存的对象放入Session缓存中,使它进入持久化状态。使用映射文件指定的主键生成策略,为持久化对象分配唯一OID。save方法只是为对象分配UID。我们可以在save方法处打断点。当我们的主键生成策略为native时,由于我们使用mysql数据库,主键自增,所以执行完save方法后,打印insert语句,mysql数据库为我们对象自增OID 当我们的主键生成策略为incrementt时,increment是由Hibernate维护,先去表中查最大ID然后+1,我们执行完save方法之后,发现打印select查找最大id的语句,执行commit时才打印插入语句2)临时状态转换成游离状态-将临时状态的对象OID设置为数据库中对应的记录.User user=new User();user.setId(1);3)持久化状态转换成临时状态第一种:User user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.close(); //游离状态user.setId(null);//临时状态 第二种:User user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.evict(user); //游离状态,此方法会将session缓存中清除持久化对象,使其变为游离状态user.setId(null);//临时状态 4)持久化状态转换成游离状态第一:调用session的close方法,持久化状态变为游离状态第二: 调用session的evict()方法方法,将持久状态转变为游离状态5)游离状态转换成临时状态只需要将游离状态的对象OID变为null。6)游离状态转换成持久状态Session的update()方法使游离状态转换成持久状态。User user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.evict(user); //游离状态,此方法会将session缓存中清除持久化对象,使其变为游离状态session.update(user);
Session接口的其他API
--Session的save()和persist()方法两个方法都是用来保存对象,能把临时状态转变为持久化状态。 两个方法的区别在于:save方法持久化对象时,会返回持久化对象的OID。所以程序执行save时会立刻执行insert语句,来返回数据库生成的OID。 persist方法持久化对象时,不会保证立即为持久化对象的OID赋值,不会立即生成insert语句,而是有可能在Session清理缓存时才为OID赋值。--Session的clear()方法清空一级缓存 --Session的update方法update()方法可以将游离对象转变为持久化对象。用来执行修改操作。update()方法完成以下操作-把游离对象加入到当前缓存中,变为持久化对象-然后计划执行update语句只要通过update使游离对象转变为持久化对象,即使没有修改任何属性,在清理缓存时还是会执行update语句。如果希望Session仅当修改了属性时才执行update语句,可以在映射文件中的<class>元素中设置select-before-update="true",默认为false这样当Session清理缓存时,会先发送一条查询语句,然后判断缓存中的对象和记录是否一致,不一致才执行update语句。当update()方法将游离对象转变为持久化对象时,如果Session缓存中已经存在相同的OID持久化对象,那么会抛出异常。例如:Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);session.evict(user1);User user2=session.get(User.class, 5);session.update(user1);ts.commit();因为Session的缓存是一个Map结构,OID为key,对象为value。当执行session的update方法时,由于缓存中已经存在了OID为5的持久化对象,因此会抛出异常。 -Session的saveOrUpdate()方法 Session的saveOrUpdate()方法同时包含了save()和update()方法的功能如果传入的参数是临时对象(OID为null),就调用save方法。如果传入的参数是游离对象(OID不为null),就执行update方法。
Hibernate的一对多关联关系
映射一对多双向关联关系
例如,以客户(Customer)和订单(Order)为例,一个客户能有多个订单,一个订单只能有一个客户。 从Customer到Order是一对多关联,在java类中的面向对象设计应该一个Customer对象包含多个Order对象,因此应该定义一个集合,来包含所有的Order对象。 从Order到Customer是多对一关联,在java类中设计每个Order对象需要关联一个Customer对象,因此Order类中应该定义一个Cutomer类型的属性,来引用关联的customer对象。但是在关系数据库中,只存在主外键参照关系来表达两者的关联。
第一步:我们先创建两个实体类 Customer和Orderpublic class Customer {private Integer id;private String name;private Set <Order> orders=new HashSet<Order>();public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Set getOrders() {return orders;}public void setOrders(Set orders) {this.orders = orders;}} public class Order {private Integer id;private String name;private Customer customer;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Customer getCustomer() {return customer;}public void setCustomer(Customer customer) {this.customer = customer;}}
第二步:实体类写好了,使用Hibernate映射文件来映射关系。创建Customer.hbm.xml <hibernate-mapping ><!--指定实体类和表的映射关系--><class name="com.cad.domain.Customer" table="customer"><id name="id" column="id"><generator class="native"></generator></id><property name="name" column="name"></property><!--使用<set>元素来映射set集合类型--><!--name:持久化类中的属性名--><set name="orders"><!--<key>元素设定所关联的持久化类对应的表的外键--><!--<one-to-many>元素设定关联的持久化类--><key column="cid"/><one-to-many class="com.cad.domain.Order"/> </set></class></hibernate-mapping> Hibernate根据映射文件获得以下信息-<set>元素表明Customer类中的orders属性为java.util.Set集合-<one-to-many>元素表明orders集合中存放的是一组order对象-<key>元素表明orders表通过外键cid关联customer表
创建Order.hbm.xml <hibernate-mapping ><class name="com.cad.domain.Order" table="order"><id name="id" column="id"><generator class="native"></generator></id><property name="name" column="name"></property><!--<many-to-one>元素建立了customer属性和对应表中外键的映射--><!--name:持久化类中的属性名column:表中的外键class:关键的Customer对象实现类--><many-to-one name="customer" column="cid" class="com.cad.domain.Customer"></many-to-one></class></hibernate-mapping>
第三步:在hibernate.cfg.xml中配置映射文件 <mapping resource="com/cad/domain/Customer.hbm.xml"/><mapping resource="com/cad/domain/Order.hbm.xml"/>
第四步: 我们来操作一下测试一下。 //添加方法 @Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); //创建CustomerCustomer c=new Customer();c.setName("张三"); //创建订单Order o1=new Order();o1.setName("矿泉水");Order o2=new Order();o2.setName("方便面"); //双向关联c.getOrders().add(o1);c.getOrders().add(o2);o1.setCustomer(c);o2.setCustomer(c);//保存session.save(c);session.save(o1);session.save(o2);ts.commit();session.close();sessionfactory.close();}然后执行,控制台会打印如下语句Hibernate: insert intocustomer(name) values(?)Hibernate: insert intoorders(name, cid) values(?, ?)Hibernate: insert intoorders(name, cid) values(?, ?)Hibernate: updateorders setcid=? whereid=?Hibernate: updateorders setcid=? whereid=?
我们来执行删除操作,直接删除Customer,但由于还有Order关联着Customer会执行成功么?@Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction();Customer c=session.get(Customer.class, 2); //删除Customersession.delete(c);ts.commit();session.close();sessionfactory.close();}执行,打印如下语句Hibernate: selectcustomer0_.id as id1_0_0_,customer0_.name as name2_0_0_ fromcustomer customer0_ wherecustomer0_.id=?Hibernate: updateorders setcid=null wherecid=?Hibernate: delete fromcustomer whereid=? 我们会发现Hibernate会自动将Order中的cid设置为null,然后执行删除操作我们发现Hibernate还是挺智能的,但这是由inverse属性操控的。
< set >元素的inverse属性
inverse所描述的是对象之间关联关系的维护方式。 inverse属性指定由哪方来维护关联关系。
inverse默认为false,即关联关系由自己控制,若为true,则反转,关联关系由对方控制.
Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。 在映射一对多的双向关联关系中,应该在"一"方把inverse属性设为true,由对方来维护主键关联关系.所以上述例子中,inverse默认是false.即Customer维护关联关系,所以Customer会执行两条更新语句来更新Order的cid.
但是我们Order在插入的时候已经插入cid,所以这样会影响性能,我们只需要将Customer的<set>元素的inverse属性改为true即可。 我们的删除案例中也是,Customer执行删除时,会先去把Order的主键约束解除,然后删除。
我们只需要将Customer的inverse设置为true,然后由对方维护关联关系,我们再进行删除时,就会出现异常,因为有主键约束,我们Customer不再维护关联关系。
级联操纵
在实际应用中,对象和对象之间是相互关联的。例如我们的一对多关联关系。
在关系-对象映射文件中,用于映射持久化类之间关联关系的元素,如 < set>,< many-to-one>,< one-to-many>,都有一个cascade属性,用来指定如何操纵与当前对象关联的其他对象。
我们先看下面的例子,我们创建一个Customer,再创建两个Order,然后关联我们只保存Customer,会抛出org.hibernate.TransientObjectException异常,这是为什么呢?这是因为我们的Customer的inverse为false,关联关系由Customer维护。我们保存Customer时,会维护Customer中orders中的所有Order的主键,但是Order是临时对象,并没有转变为持久状态,这时候就会抛出异常。@Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction();Customer c=new Customer();c.setName("jack");Order o1=new Order();o1.setName("苹果");Order o2=new Order();o2.setName("香蕉");c.getOrders().add(o1);c.getOrders().add(o2);o1.setCustomer(c);o2.setCustomer(c);session.save(c);ts.commit();session.close();sessionfactory.close();}
当Hibernate持久化一个临时对象时,并不会自动持久化所关联的其他临时对象,所以会抛出异常。
如果我们希望Hibernate持久化对象时自动持久化所关联的其他对象,那么就需要指定cascade属性
级联保存和更新
当我们持久化对象时自动持久化所关联的其他对象。 把cascade属性设置为save-update ,这时候我们再执行上面的代码就会自动帮我们保存Customer关联的Order对象。当cascade属性为save-update时,表明保存或更新当前对象时,会级联保存或更新与它关联的对象。
级联删除
如果我们的cascade属性为delete时,我们删除当前对象,会自动删除与之关联的对象。慎用这个delete属性。例如:我们的Order配置了这个属性,Customer也配置了这个属性,我们删除订单时,因为是级联删除所以会查找Customer,删除Customer,但Customer也配置了级联删除,所以会查找所有关联的订单,最后会删除该客户的所有订单和该客户。
孤儿删除
如果我们的对象和关联的对象解除关系后,希望自动删除不再关联的对象。需要将cascade设置为delete-orphan.例如 ,我们设置cascade="delete-orphan"Transaction ts=session.beginTransaction();Customer c=session.get(Customer.class, 7);Order order=(Order) c.getOrders().iterator().next();c.getOrders().remove(order);order.setCustomer(null);ts.commit(); 我们解除Customer和Order的关系,Hibernate就会自动删除Order。 当cascade的值为all时,是save-update和delete的整合。当cascade的值为all-dalete-orphan时,是all和delete-orphan的整合。
Hibernate 核心技术(二)相关推荐
- spring mvc+spring + hibernate 整合(二)
在上篇文章中,我建立了工程并配置了spring + hibernate.今天我们检验下上篇文章的成果,如何检查呢?那就是进行单元测试.本篇文章就让大家和我一起来就前面的建的工程进行单元测试. 本项目使 ...
- Hibernate学习(二):heibernate核心接口
Hibernate是一种对JDBC做了轻量级封装的对象---关系映射工具,所谓轻量级封装,是指Hibernate并没有完全封装JDBC,Java应用即可以通过Hibernate API访问数据库,还可 ...
- hibernate框架(二)核心配置API
一:核心配置分为三大部分 必须的配置 .可选的配置和引入映射文件. 1.必须的配置 连接数据库的参数:驱动类 url路径 用户名 密码 方言 <property name=" ...
- 框架之 hibernate之二
1. Hibernate持久化对象的状态 2. Hibernate的一级缓存 3. Hibernate操作持久化对象的方法 4. Hibernate的基本查询 Hibernate的持久化类 什么是持久 ...
- Hibernate入门(二)——hibernateAPI详解
Hibernate API 详解 1.Configuration 功能:配置加载类,用于加载主配置,orm元数据加载 .创建: Configuration conf = new Configurati ...
- Hibernate(二)——一对多查询
1. 前言 本章节我们讨论Hibernate一对多查询的处理. 在上一章节中(Hibernate(一)--入门),我们探讨了Hibernate执行最基本的增删改查操作.现在我们将情况复杂化:加入我们在 ...
- 夯实基础,彻底掌握js的核心技术(二):面向对象编程(Object Oriented Programming)
单例设计模式(Singleton Pattern) 1. 单例模式解决的问题: 表现形式: Var obj = {xxx; xxx, - } 2. 作用: 把描述同一件事物的属性和特征进行" ...
- Java程序员从笨鸟到菜鸟之(五十一)细谈Hibernate(二)开发第一个hibernate基本详解...
在上篇博客中,我们介绍了<hibernate基本概念和体系结构>,也对hibernate框架有了一个初步的了解,本文我将向大家简单介绍Hibernate的核心API调用库,并讲解一下它的基 ...
- hibernate入门二之单表操作
友情提醒:以下所有的操作都是在hibernate入门一的基础上操作的,参考链接:https://www.cnblogs.com/lindaiyu/p/10990525.html 单一主键 assign ...
最新文章
- jmeter 自定义参数_jmeter参数化并在jenkins上执行
- 解决IE正常模式与兼容性模式的办法
- 软件构造学习笔记-第十二周
- LeetCode 1073. 负二进制数相加(负数进制)
- 【内容管理系统】之 Strapi
- IE 首页被改为www.1188.com的恢复办法
- $与一些特殊字符的组合用法
- python基础7-函数
- 去掉Scala的糖衣(4) -- Type Aliase
- SSH免密登陆:Win登陆linux
- Windows7 的激活命令小结
- 将两个顺序表合并为一个新的顺序表
- 【STM32F407F429H7的DSP教程】第34章 滤波器基础知识
- Java8 Stream(11)List转Map
- 泛泰A860版本的识别
- php 开源cms 族谱,20款最为知名的开源PHP CMS
- 【Python爬虫】:爬取58同城二手房的所有房产标题
- Blog Management System Based on JSP(基于JSP的实验室设备管理系统的开发)外文翻译
- Multisim基础 发光二极管 添加元件的位置
- oracle 按天数 均值,oracle 按天数统计数据
热门文章
- 强人工智能不能碰,那电影中的机器人只是一场梦?
- ACM准备之路(蓝桥杯5)杨辉三角公式解法
- ChatGPT中文版正式上线,掀起人工智能语言生成新热潮
- CLUSTER SLOTS
- vscode 编辑md文件 好用的插件
- font-icon各类图标的使用
- 谈谈在线团队协作工具 - Tower im
- 新课改计算机专业选课要求,新高考如何选科?各专业类选科要求对照表来了
- 5.12 汶川大地震记忆(2008-06-23 15:34:50| 分类: 心情故事)
- Win8.1系统“Windows 套接字初始化失败”解决方案