简单总结一下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,后面就都正常了。



----------------------------------------------------------

update:

找到了问题的根源了------tomcat缓存!

解决办法:在http request的header把 Cache-Control属性设置为 no-cache,problem solved!

        builder.setHeader("Content-Type", "application/xml");
        builder.setHeader("Cache-Control", "no-cache");

实际发送的http request header部分内容的截图:

简单总结一下Hibernate的缓存问题_第1张图片



一篇很好的文章 Truely understanding the second-level and query caches

Hibernate官方文档中关于缓存的部分




你可能感兴趣的:(Hibernate,数据库,session,cache,query,generator)