在了解此文前,请首先阅读:
[hibernate基础(二)保存一个对象(http://blog.csdn.net/wangyy130/article/details/50420545)
在使用hibernate时,我们还是很有必要将hibernate的内部实现原理来搞清楚一下的。比如,hibernate在保存一个对象时,
它的内部实现原理是怎样的。当然要想清楚的了解这些,就必须要从它的主键生成策略出发来一步一步深入探究!
在通过hibernate来保存一个对象时,要先了解它内部的原理:
1、save:
此方法在执行时,会先把一个对象放在session缓存中
(当然可以宏观上先这么理解,会有意外,这个意外也是这篇博客介绍的重点)
于此同时,在缓存中还会再生成一个临时对象的缓存区,在这块临时区域中会在进行数据库操作时,与session中的对象进行对比,最后 生成sql语句。
(在session缓存区中会有一个isDatabase这个标识来标记当前数据是否已经存在数据库中)
2、commit
默认在执行完save之后,数据其实并没有真正的到达数据库(有意外),通常在执行完commit之后,才会是真正意义上的对象保存。
但是默认情况下,在执行commit之前,hibernate会自动先执
flush方法,此方法用来根据临时对象缓存区生成sql语句,操作数据库
并清理session缓存区。完成真正的数据插入操作。
这里并不是想一一介绍主键有哪些生成策略,而是想简单的了解一下主键的生成方式都是由什么来决定的,会造成什么影响。
1、uuid:
主要是通过hibernate生成,效率较高。所以此中方式保存对象时,在执行完save后,就会为对象的主键进行赋值。
只有在执行完commit或者显式执行flush之后才会生成sql并清理缓存,形成真正意义上的数据保存
2、native(下面就是意外)
通过不同数据库的能力来生成主键。所以效率相对较差。
在执行完save方法之后,由于他要去数据库中生成id,所以此时会直接生成sql并清理缓存。如果你的数据库隔离级别设置为不提交读时,
在未执行完commit方法之前就可以从数据库中查到数据了。
3、assigned
此种方式一种会涉及到
上面说了hibernate中在执行commit时,会默认先执行flush,似乎平时用不到显式调用,那么什么时候才会用到flush的显式调用呢?
1、evict方法
此方法是用来清理session缓存区的。
当我们在程序中调用save方法后,有时需要调用evict方法来清理session缓存区。但此时再执行commit方法时,hibernate会报线程
不安全的错误。
首先分析下原因:
正常情况下:
save之后,执行commit时,先执行flush方法,根据临时对象区域生成sql语句,然后清理掉临时对象区域,
会返回到session中修改isDatabase标记,修改完成后再清理session缓存区。
save之后执行evict情况
此时,刚执行完save后,就执行evict方法,会先清理session缓存,也就是说isDatabase标记已经不存在了,那么当再执行commit时,
默认先执行flush方法时,会去修改session缓存区中的isDatabase标记,此时,标记找不到,因此会报错。
解决方案
我们可以通过在save方法执行完成之后,显式执行flush方法,此时,会先进行数据真实保存,再清理session,这样就不会再报错了。
2、assigned手动分配与业务逻辑相结合
如果主键配置为assigned,那么在业务逻辑中如果出现需要按顺序执行insert,update,insert这三个语句时,我们发现在commit之后,
hibernate生成语句的顺序为:insert,insert,update
那么此时,就与我们的设想偏离,若想按照原来的顺序执行,此时就可以通过显式执行flush方法来先将前两个语句进行执行,再去执行最后一个语句。
主键的生成与hibernate的实现原理息息相关。同时flush方法的使用也对具体的程序开发起到很重要的作用。同时了解hibernate
中具体操作的实现原理对于理解hibernate和程序开发中的优化也有很大的帮助