背景:
一个偶然的机会,我做了一个例子,中间我遇到了一个有意思的问题,就是在执行commit方法之前,做了两次save操作,如下:
这样执行之后,报了一个:关于session主键冲突的异常,这之前,我也见过一个,就是整棵继承树映射一张表的时候,主键自增长不配置时,这个,我没有弄啊!然后开始这种查找资料,后来知道,commit方法会隐形的调用一个flush方法,而这个方法不是按照我们代码来提交事务的,而是会按着save、update、delete这个顺序来提交事务,所以会连续有两个save变成持久化对象,而造成session生成的Oid重复。
也就是因为这个,我对flush感了兴趣,下面我们介绍一下关于flush的那些事:
1、flush有什么用?
调用flush方法,会按save,update,delete顺序执行,把缓存中的数据flush入数据库中,并清空缓存区;这里注意啊,这里只是将缓存中的数据同步到数据库中,这里并没有在数据库中insert数据,可以理解为把缓存内数据同步到一张临时表内(缓存区),这时候,直接查询数据库是没有新添数据的,但是使用发送sql语句查询却可以查出数据来(前提:数据库隔离级别要为 未提交读),当提交事务时,数据库事务管理器提交事务执行提交过来的sql语句,修改数据库数据。也就是说,只有commit才会更改数据库数据。
整理一下啊:flush方法的主要作用就是清理缓存,强制数据库与hibernate缓存同步,以保证数据的一致性。其实在session持久化操作和数据库中之间还有一层对象缓冲区
flush的主要动作就是向数据库发送一系列的sql语句,并执行这些sql语句,但是不会向数据库提交。而commit方法则会首先调用flush方法,然后提交事务。这就是为什么我们仅仅调用flush的时候记录并未插入到数据库中的原因,因为只有提交了事务,对数据库所做的更新才会被保存下来。因为commit方法隐式的调用了flush,所以一般我们都不会显示的调用flush方法。
2、关于主键生成策略的一点隐私:
当你的id主键生成策略为native,调用session.save后,将执行insert语句,返回有数据库生成的id 纳入了session的管理,修改了session中existsInDatabase状态为true,如果数据库的隔离级别设置为未提交读,那么我们可以看到save过的数据 ;
当id主键生成策略采用的是uuid,调用完成save后,只是将user纳入到了session的管理,不会发出insert语句,但是id已经生成,session中existsInDatabase状态为false;
3、最后那如何解决上面的问题呢,很简单,在update之后,执行session.flush,然后再第二个save之后,再执行一次flush就可以,
这样就能按照我们的想法顺序缓存sql语句了有木有?
4、总结:
由于flush()的特殊处理机制,虽然不建议使用此方法,但是在一些复杂的事务处理过程中,加入此方法虽然会破坏事务的一个提交的完整性,但是可以规避一些不可预见的异常情况!