簡單總結一下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部分內容的截圖:




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

Hibernate官方文檔中關於緩存的部分




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章