深入Hibernate的flush机制

本文深入探讨Hibernate框架中的Flush机制,包括其工作原理、不同主键生成方式的影响、以及如何通过设置Flush模式来优化性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、理解flush机制


之后单纯用原始的Hibernate框架做了一些验证,并且打开执行SQL打印输出台的,得出的结论:


前提是在同一事务中间:



1、利用sql语句, session.createSQLQuery(sql).executeUpdate();进行插入,输出台打印出sql插入语句; 再利用sql语句,进行session.createSQLQuery(sql).uniqueResult(); 也会打印SQL查询语句,没有问题,可以查询到数据。

2、利用hibernate封装操作, session.save(entity); 进行插入,输出台并没有打印出插入的SQL语句, 再利用 session.get(entity,id);方法做查询 ;也没有打印出SQL查询语句,但是是可以查询到数据的。到执行事务提交语句时,插入的SQL语句被打印出来

3、利用hibernate的session.save(entity); 进行插入,再利用《HQL》语句进行查询,效果同上面第二点。

4、利用hibernate的session.save(entity); 进行插入,输出台并没有打印出插入的SQL语句。 再利用sql语句,进行session.createSQLQuery(sql).uniqueResult(); 会打印SQL查询语句。问题出现了,查询不到任何数据。这种情况下利用session.flush()方法,在查询之前执行到flush()方法,输出台会打印出插入的SQL语句。 再进行查询就有数据。


验证完成之后,查了下往上资料,对于第四点,在开发过程中出现频繁,非常的常见,相信很多人都曾遇到,但又有很多人继续摸不到头脑。正好以此加深了印象。



从打印控制台SQL可以看出一个基本的hibernate save方法的操作流程


1. 判断所要保存的实例是否已处于持久化状态,如果不是,则将其置入缓存;

2. 根据所要保存的实例计划一条insert sql语句,注意只是计划,并不执行;

3. 事务提交时执行之前所计划的insert语句;



将tx.commit()换成session.flush,此时控制太打印出了insert语句,但是数据库中并没有添加新的记录;



flush方法的主要作用就是清理缓存,强制数据库与Hibernate缓存同步,以保证数据的一致性。它的主要动作就是向数据库发送一系列的sql语句,并执行这些sql语句,但是不会向数据库提交。而commit方法则会首先调用flush方法,然后提交事务。这就是为什么我们仅仅调用flush的时候记录并未插入到数据库中的原因,因为只有提交了事务,对数据库所做的更新才会被保存下来。因为commit方法隐式的调用了flush,所以一般我们都不会显示的调用flush方法。

这是hibernate的flush机制。在一些复杂的对象更新和保存的过程中就要考虑数据库操作顺序的改变以及延时flush是否对程序的结果有影响。如果确实存在着影响,那就可以在需要保持这种操作顺序的位置加入flush强制Hibernate将缓存中记录的操作flush入数据库,这样看起来也许不太美观,但很有效。


二、深入flush机制


先讲解两个常用方法:

session.evict(obj):会把指定的缓冲对象进行清除。
session.clear():把缓冲区内的全部对象清除,但不包括操作中的对象。


如果在save(obj)后,evict(obj),再事务提交会怎样:



Hibernate执行的顺序如下:



(1)生成一个事务的对象,并标记当前的Session处于事务状态(注:此时并未启动数据库级事务)。


(2)应用使用s.save保存对象,这个时候Session将这个对象放入entityEntries,用来标记对象已经和当前的会话建立了关联,由于应用对对象做了保存的操作,Session还要在insertions中登记应用的这个插入行为(行为包括:对象引用、对象id、Session、持久化处理类)。


(3)s.evict将对象从s会话中拆离,这时s会从entityEntries中将这个对象移出。


(4)事务提交,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert(save),update,……,delete的顺序提交所有之前登记的操作(注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush),现在对象不在entityEntries中,但在执行insert的行为时只需要访问insertions就足够了,所以此时不会有任何的异常。异常出现在插入后通知Session该对象已经插入完毕这个步骤上,这个步骤中需要将entityEntries中对象的existsInDatabase标志置为true,由于对象并不存在于entityEntries中,此时Hibernate就认为insertions和entityEntries可能因为线程安全的问题产生了不同步(也不知道Hibernate的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个bug吧),于是一个net.sf.hibernate.AssertionFailure就被抛出,程序终止。

一般我们会错误的认为s.save会立即执行,而将对象过早的与Session拆离,造成了Session的insertions和entityEntries中内容的不同步。所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据flush入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。解决办法是在save之后,添加session.flush。


三、flush的设置


Flush方法是可以设置的,也就是fulsh什么时候执行是可以设置的

在session.beginTransaction前设置FlushMode

session.setFlushMode(FlushMode.Always|AUTO|COMMIT|NEVER|MANUAL)

FlushMode有5个值可选


Always:任何代码都会Flush
AUTO:默认方式–自动
Commit:COMMIT时
Never:始终不
MANUAL:手动方式


设置FlushMode有个好处是可以节省开销,比如默认session只做查询时,就可以不让他与数据库同步了。



四、主键生成方式不同时,flush调用的时刻也不同


1、当主键的生成方式是uuid时:


调用完save()后,只是将save的对象纳入到了session的管理,不会发出insert语句,但是id已经生成,session中existsInDatebase状态为false(也就是说,此时数据库中并不存在所save的对象);如果此时调用session.flush()方法,那么Hibernate会清除缓存,执行相关的sql语句,则此时数据已经在数据库中存在了,且如果数据库的隔离级别设置成“未提交读”时,我们应该可以在数据库中读到相关的数据记录(此时的数据仍然可以“回滚”),显然,session中existsInDatebase状态将更改为true;如果transaction.commit()方法被调用,在默认会调用session.flush()方法,同时,此时数据库中的数据不能“回滚”。

2、当主键的生成方式为native时:


调用完save()后,将save的对象纳入到了session的管理,发出insert语句,并返回有数据库生成的id,修改了session中existsInDatebase状态为true,如果数据库的隔离级别设置为为提交读,那么我们可以看到save过的数据,这种情况下,显示的调用session.flush()方法,已经显的多余了,因为在后面的transaction.commit()方法被调用时,会隐式的调用session.flush()方法。

3、当主键的生成方式为assigned时:


调用完save()后,将save的对象纳入到了session的管理,不会发出insert语句,而此时的主键已经由我们手动分配了,于是,显示的调用session.flush()方法,能起到主键生成方式为uuid时的效果。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值