Session接口是Hibernate向应用程序提供的操纵数据库的主要接口,它提供了基本的保存,更新,删除和加载Java对象的方法。
Session具有一个具体的缓存,位于缓存中的对象称为持久化对象。它和数据库中的记录对应。Session可以在某些时间点,按照缓存中的对象变化来执行相应的SQL语句,同步更新数据库,这一过程称为刷新缓存(flush)。
站在持久化的角度将Hibernate分为四种状态:
- 持久化状态
- 临时状态
- 游离状态
- 删除状态
Session中的特定方法能使对象从一个状态转为另一个状态。
Session缓存
在Session接口的实现中包含了一系列的Java集合,这些Java集合构成了Session缓存。只要Session实例没有结束生命周期,且没有清理缓存。则存放在它缓存的对象也不会结束生命周期。
Session缓存可以减少Hibernate应用程序访问数据库的次数。
HelloWorld helloWorld = this.session.get(HelloWorld.class, 1);
HelloWorld helloWorld2 = this.session.get(HelloWorld.class, 1);
这两条查询只会使用一次SQL语句。
HelloWorld helloWorld = session.get(HelloWorld.class, 1);
helloWorld.setTitle("Today");
当我们使用set方法是自动更新到了数据库。查看控制台我们会发现程序实际上调用了SQL语句。此时会自动调用flush方法。
flush缓存
flush的作用:使数据库中的对象与Session中的对象保持一致。为了保持一致,则可能发送SQL语句。
在调用commit方法时,会先进行flush,然后再提交事务。
在Transaction的commit方法中,先调用session的flush方法,再提交事务。
flush方法可能会发送SQL语句。不会提交事务。
注意:在未提交事务或者显示的调用session.flush()方法之前,也有可能会进行flush操作。
- 执行HQL或者QBC查询。会先进行flush操作,得到数据表最新的记录。
- 若数据记录的ID是由底层数据库使用自增的方式生成的,则在调用save方法后就会立即发送insert语句。因为save方法必须保证对象的ID是存在的。
commit和flush方法的区别:flush执行一系列的SQL语句,但不提交事务。commit方法会先调用flush方法,然后提交事务。意味着提交事务那么数据库操作就被永久的保存下来。
refresh
缓存中的数据是最新的状态。调用这个方法会强制发送一条select语句得到记录的最新状态。
refresh会强制发送select语句,使session缓存中的对象的状态和数据表中对应的记录保持一致。
操作Session缓存
持久化对象
-
临时对象(Transient)
在使用代理主键的情况下,OID通常为null。
不处于Session缓存中。
在数据库中没有对应的记录。 -
持久化对象(Persist)
持久化对象也叫托管。
OID不为null。
位于Session缓存中。
若在数据库中已经有和其对应的记录,持久化对象和数据库中相关的记录对应。
Session在flush缓存时,会根据持久化对象的属性变化,来同步更新数据库。
在同一个Session实例的缓存中,数据库表中的每条记录只对应唯一的持久化对象。 -
删除对象(Removed)
在数据库中没有和其OID对应的记录。
不再处于Session的缓存中。
一般情况下,应用程序不该再使用被删除的对象。 -
游离对象(Detached)
对象状态转换图
也叫托管。
OID不为null。
不再处于Session缓存中。
一般情况下,游离对象是从持久化对象转变过来的。因此在数据库中可能还存在与它对应的记录。
Session持久化操作的方法
首先说一个概念OID。Hibernate通过持久化对象的OID来维持它和数据库相关记录的对应关系。当News对象处于持久化状态时,不允许程序随意修改它的Id。
save()方法:
- 使一个临时对象变为一个持久化对象。
- 为对象分配Id。
- 在flush缓存时会发送一条insert语句。
- 在save方法之前的Id是无效的。
- 持久化对象的Id是不能被修改的。
persist()方法:
- 也会执行insert操作。
- 如果在persist方法之前有Id了,则不会执行insert操作,而是抛出一个异常。
get和load的区别:
- 执行get方法会立即加载对象,而执行load方法,若不使用该对象。则不会立即执行查询操作,而返回一个代理对象。get立即查询。load延迟加载。
- 如果在数据库中没有对应的记录。Session没有被关闭,同时需要使用对象时,get会返回一个空值(null),load则会抛出一个异常。
- load方法可能会抛出懒加载异常(LazyInitializationException)。在需要初始化代理对象之前已经关闭了Session。
update方法:
- 若更新一个持久化对象,则不需要显式的调用update方法。因为在调用transaction的commit方法时会先执行Session的flush方法。
- 更新一个游离对象时,需要显示的调用Session的update方法。同时可以将一个游离对象变为一个持久化对象。
注意:
-
无论要更新的游离对象与数据包中的记录是否一致,都会发送Update语句。
如何让update方法不盲目的触发Update语句:
在*.hbm.xml文件的class结点设置一个属性select-before-update=true。通常不需要设置该属性。 -
若数据表中没有对应的记录,但还是调用了update方法则会抛出异常。
-
若在Session缓存中存在相同的OID的持久化对象,则会抛出异常。因为在同一个Session的缓存中不能有两个Id相同的对象。
saveOrUpdate方法:
saveOrUpdate方法同时包含了save方法和update方法的功能。
判定对象为临时对象的标准:
- Java对象的OID是否为null。
- 映射文件中<id>设置了unsaved-value属性。并且Java对象的OID取值和这个unsaved-value属性相匹配。
若OID不为空,但数据表中还没有和其对应的记录,会抛出一个异常。
OID的值等于ID的unsaved-value属性值的对象,也被认为是一个游离对象。
delete方法
执行删除操作。只要OID和数据表中的一条记录对应,就会执行delete操作。如果没有对应的记录则抛出异常。
可以通过设置Hibernate配置文件的属性hibernate.use_identifier_rollback为true使删除对象后把其OID置为null。
evict方法
从Session缓存中把指定的持久化对象移除。
通过Hibernate调用存储过程
Session的doWork(Work)方法用于执行Work对象指定的操作。即调用Work对象的execute方法,Session会把当前使用的数据库连接传递给execute方法。
Hibernate与触发器协同工作
Hibernate与触发器协同工作时会造成两个问题:
- 触发器使Session缓存中的持久化对象与数据库中对应的数据不一致。触发器在数据库中操作,它执行的操作对于Session是透明的。
- Session的update方法盲目的触发触发器。无论游离对象的属性是否发生变化都会执行update语句。而update语句会触发数据库中相应的触发器。
解决方案
- 在执行完Session的相关操作后,立即调用Session的flush和refresh方法。迫使Session缓存和数据库的数据同步。方法重新从数据库中加载数据。
- 在映射文件的<class>元素中设置select-before-update属性。当Session的update或saveOrUpdate方法更新一个游离对象时,先执行select语句获取该游离对象在数据库中的最新数据。只有在不一致的情况下才会执行update操作。
网友评论