簡單總結一下Hibernate的緩存問題

openkk 12年前發布 | 27K 次閱讀 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

 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!