简单总结一下Hibernate的缓存问题
今天发现之前做的一个restful web based application有一个bug。比如先执行一个搜索,结果中有10条数据,执行数据库scrip删除这10条数据,在网页上再次执行同一搜索时,按道理结果应该为空,结果发现仍然看到已经删除的10条结果,反复点击搜索按钮后10条数据又不见了。QA之前居然没查出这个问题,可能它们以为只是一点点延迟,于是多点几次搜索按钮,删除的信息又不见了。
删除的信息为什么会出现?很显然,要么是浏览器缓存,要么是Hibernate缓存。
通过debug,发现在删除数据后,query.list() 返回的result set不为空,结论是:肯定是Hibernate缓存的问题。
Hibernate提供两层缓存机制,第一层是在session,第二层是在所谓的session factory。
先说第一层,hibernate的操作都是在session中进行的,在一个session中执行save(), update(), saveOrUpdate() 操作,或者通过load(), get(), list(), iterate(), scroll() 读取数据对象,都会将这些对象缓存在session中。第一层session level的缓存是不能被disable的。当session被close的时候,所有的缓存就被自动释放。
第二层session factory的缓存是否启用是可选的,用好了功能很强大,不懂内部原理用的不好就会适得其反。首先,第二层缓存不会cache对象的实例类型,只 cache对象的属性值,这一点非常重要,因为1、hibernate不需要担心因为你在代码中对缓存对象的操作而破坏缓存,2、缓存对象之间的关联很容易保持最新,因为他们之间的关系仅仅是通过identifier。
比如说有这样一个mapping
<class name="org.javalobby.tnt.hibernate.Person"> <cache usage="read-write"/> <id name="id" column="id" type="long"> <generator class="identity"/> </id> <property name="firstName" type="string"/> <property name="middleInitial" type="string"/> <property name="lastName" type="string"/> <many-to-one name="parent" column="parent_id" class="Person"/> <set name="children"> <key column="parent_id"/> <one-to-many class="Person"/> </set> </class>保存在第二层缓存中的不是Person实例,而是一系列的属性值,每一组有一个identifier。
*-----------------------------------------* | Person Data Cache | |-----------------------------------------| | 1 -> [ "John" , "Q" , "Public" , null ] | | 2 -> [ "Joey" , "D" , "Public" , 1 ] | | 3 -> [ "Sara" , "N" , "Public" , 1 ] | *-----------------------------------------*
另外还有一个Query Cache(查询缓存),必须和第二层缓存共同使用,因为它的作用是把query,query中的参数以及参数的值,和第二层缓存对象的identifier关联起来。
Query query = session.createQuery("from Person as p where p.parent.id=? and p.firstName=?"); query.setInt(0, Integer.valueOf(1)); query.setString(1, "Joey"); query.setCacheable(true); List l = query.list();如果query cache启用了的话,上面的代码运行后就会产生一段查询缓存如下
*----------------------------------------------------------------------------------------* | Query Cache | |----------------------------------------------------------------------------------------| | ["from Person as p where p.parent.id=? and p.firstName=?", [ 1 , "Joey"] ] -> [ 2 ] ] | *----------------------------------------------------------------------------------------*这里保存了query本身,query的参数和值(1,“Joey”),第二层缓存对象的identifier(2)。
这样下次当再次执行这条查询语句,并且参数和值都对上的时候,query cache就会返回一个identifier 2。
很显然,必须要启用了第二层缓存,这个2(identifier)才有意义,才能通过这个值去找到一组Person的属性值。
Hibernate的缓存机制可以有效地减少对数据库的反复查询,这一点是相当有价值的,因为在实际项目中,对数据库的查询次数过多常常是performance的瓶颈。
回到我遇到的问题,我的项目中没有启用第二层缓存,也没有启用query cache,而且每次执行HQL的时候,我都从session factory里面打开一个新的connection,每次结束查询后,都会调用session.close(),这样理论上来讲,第一层缓存也不会有问题。
Anyway,死马当活马医,第一步先显式地把second level cache和query cache的属性设置成false,确保第二层缓存不来捣乱,第二步,把query.list()放到transaction里面,
session.beginTransaction(); ...... ...... session.getTransaction().commit();
第三步,调用session.flush(),使缓存对象(如果有的话)和数据库同步,调用session.evict(),显式地清除缓存对象(如果有的话),最后在session.close()之前调用session.clear(),在结束session生命周期之前释放所有的一级缓存。
测试后发现效果好了很多,在数据库中对数据进行操作后,在浏览器进行查询时,最多第一次的时候还会显示old data,后面就都正常了。
一篇很好的文章 Truely understanding the second-level and query caches
Hibernate官方文档中关于缓存的部分
转自:http://blog.csdn.net/qinjienj/article/details/7574679