簡單總結一下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