1.1.1.
?????
???
基本的緩存原理
Hibernate
緩存分為二級,第一級存放于
session
中稱為一級緩存,默認帶有且不能卸載。
?
第二級是由
sessionFactory
控制的進程級緩存。是全局共享的緩存,凡是會調用二級緩存的查詢方法
都會從中受益。只有經正確的配置后二級緩存才會發揮作用。同時在進行條件查詢時必須使用相應的方法才能從緩存中獲取數據。比如
Query.iterate()
方法、
load
、
get
方法等。必須注意的是
session.find
方法永遠是從數據庫中獲取數據,不會從二級緩存中獲取數據,即便其中有其所需要的數據也是如此。
?
查詢時使用緩存的實現過程為:首先查詢一級緩存中是否具有需要的數據,如果沒有,查詢二級緩存,如果二級緩存中也沒有,此時再執行查詢數據庫的工作。要注意的是:此
3
種方式的查詢速度是依次降低的。
???
?
因為
Session
的生命期往往很短,存在于
Session
內部的第一級最快緩存的生命期當然也很短,所以第一級緩存的命中率是很低的。其對系統性能的改善也是很有限的。當然,這個
Session
內部緩存的主要作用是保持
Session
內部數據狀態同步。并非是
hibernate
為了大幅提高系統性能所提供的。
為了提高使用
hibernate
的性能,除了常規的一些需要注意的方法比如:
使用延遲加載、迫切外連接、查詢過濾等以外,還需要配置
hibernate
的二級緩存。其對系統整體性能的改善往往具有立竿見影的效果!
(經過自己以前作項目的經驗,一般會有
3~4
倍的性能提高)
?
執行條件查詢時,
iterate
()方法具有著名的
“
n+
1
”
次查詢的問題,也就是說在第一次查詢時
iterate
方法會執行滿足條件的查詢結果數再加一次(
n+1
)的查詢。但是此問題只存在于第一次查詢時,在后面執行相同查詢時性能會得到極大的改善。此方法適合于查詢數據量較大的業務數據。
但是注意:當數據量特別大時(比如流水線數據等)需要針對此持久化對象配置其具體的緩存策略,比如設置其存在于緩存中的最大記錄數、緩存存在的時間等參數,以避免系統將大量的數據同時裝載入內存中引起內存資源的迅速耗盡,反而降低系統的性能!!!
?
另外,
hibernate
會自行維護二級緩存中的數據,以保證緩存中的數據和數據庫中的真實數據的一致性!
無論何時,當你調用
save()
、
update()
或
?saveOrUpdate()
方法傳遞一個對象時,或使用
load()
、
?get()
、
list()
、
iterate()?
或
scroll()
方法獲得一個對象時
,?
該對象都將被加入到
Session
的內部緩存中。
?
當隨后
flush()
方法被調用時,對象的狀態會和數據庫取得同步。
?
也就是說刪除、更新、增加數據的時候,同時更新緩存。當然這也包括二級緩存!
?
只要是調用
hibernate API
執行數據庫相關的工作。
hibernate
都會為你自動保證
緩存數據的有效性!!
?
但是,如果你使用了
JDBC
繞過
hibernate
直接執行對數據庫的操作。此時,
Hibernate
不會
/
也不可能自行感知到數據庫被進行的變化改動,也就不能再保證緩存中數據的有效性!!
?
這也是所有的
ORM
產品共同具有的問題。幸運的是,
Hibernate
為我們暴露了
Cache
的清除方法,這給我們提供了一個手動保證數據有效性的機會!!
一級緩存,二級緩存都有相應的清除方法。
?
其中二級緩存提供的清除方法為:
按對象
class
清空緩存
???????????????
按對象
class
和對象的主鍵
id
清空緩存
???????????????
清空對象的集合中的緩存數據等。
???
并非所有的情況都適合于使用二級緩存,需要根據具體情況來決定。同時可以針對某一個持久化對象配置其具體的緩存策略。
?
適合于使用二級緩存的情況:
1、
數據不會被第三方修改;
?
一般情況下,會被
hibernate
以外修改的數據最好不要配置二級緩存,以免引起不一致的數據。但是如果此數據因為性能的原因需要被緩存,同時又有可能被第
3
方比如
SQL
修改,也可以為其配置二級緩存。只是此時需要在
sql
執行修改后手動調用
cache
的清除方法。以保證數據的一致性
?
? 2
、數據大小在可接收范圍之內;
?
????
如果數據表數據量特別巨大,此時不適合于二級緩存。原因是緩存的數據量過大可能會引起內存資源緊張,反而降低性能。
?
如果數據表數據量特別巨大,但是經常使用的往往只是較新的那部分數據。此時,也可為其配置二級緩存。但是必須單獨配置其持久化類的緩存策略,比如最大緩存數、緩存過期時間等,將這些參數降低至一個合理的范圍(太高會引起內存資源緊張,太低了緩存的意義不大)。
?
? 3
、數據更新頻率低;
?
????
對于數據更新頻率過高的數據,頻繁同步緩存中數據的代價可能和
查詢緩存中的數據從中獲得的好處相當,壞處益處相抵消。此時緩存的意義也不大。
?
?
? 4
、非關鍵數據(不是財務數據等)
?
?
財務數據等是非常重要的數據,絕對不允許出現或使用無效的數據,所以此時為了安全起見最好不要使用二級緩存。
?
因為此時
“正確性”的重要性遠遠大于
“高性能”的重要性。
?
?一般系統中有三種情況會繞開hibernate執行數據庫
操作:
1、多個應用系統同時訪問一個數據庫
?? 此種情況使用hibernate二級緩存會不可避免的造成數據不一致的問題,
?? 此時要進行
詳細的設計。比如在設計上避免對同一數據表的同時的寫入操作,
?? 使用數據庫各種級別的鎖定機制等。
?
2
、動態表相關
???所謂“動態表”是指在系統運行時根據用戶的操作系統自動建立的數據表。
?? 比如“自定義表單”等屬于用戶自定義擴展開發性質的功能模塊,因為此時數據表是運行時建立的,所以不能進行hibernate的映射。因此對它的操作只能是繞開hibernate的直接數據庫JDBC操作。
???
???
如果此時動態表中的數據沒有設計緩存,就不存在數據不一致的問題。
???如果此時自行設計了緩存機制,則調用自己的緩存同步方法即可。
3
、使用
sql對hibernate持久化對象表
進行批量刪除時
???? 此時
執行批量刪除后,緩存中會存在已被刪除的數據。
分析:?
?? 當執行了第3條(
sql
批量刪除)后,后續的查詢只可能是以下三種方式:
a. session.find
()方法:
根據前面的總結,
find
方法不會查詢二級緩存的數據,而是直接查詢數據庫。
所以不存在數據有效性的問題。
b.
調用
iterate
方法執行條件查詢時:
根據
iterate
查詢方法的執行方式,其每次都會到數據庫中查詢滿足條件的
id
值,然后再根據此
id
到緩存中獲取數據,當緩存中沒有此
id
的數據才會執行數據庫查詢;
如果此記錄已被
sql
直接刪除,則
iterate
在執行
id
查詢時不會將此
id
查詢出來。所以,即便緩存中有此條記錄也不會被客戶獲得,也就不存在不一致的情況。(此情況經過測試驗證)
?
c.
用
get
或
load
方法按
id
執行查詢:
?
客觀上此時會查詢得到已過期的數據。但是又因為系統中執行sql批量刪除一般是
針對中間關聯數據表,對于
中間關聯表的查詢一般都是采用條件查詢
,
按
id
來查詢某一條關聯關系的幾率很低
,
所以此問題也不存在
!
?
?
?
?
如果某個值對象確實需要按
id
查詢一條關聯關系
,
同時又因為數據量大使用
了
sql
執行批量刪除。當滿足此兩個條件時
,
為了保證按
id
的查詢得到正確的結果
,
可以使用手動清楚二級緩存中此對象的數據的方法
!!
(
此種情況出現的可能性較小
)
?
1
、建議不要使用
sql
直接執行數據持久化對象的數據的更新,但是可以執行
批量刪除。(系統中需要批量更新的地方也較少)
?
2
、如果必須使用
sql
執行數據的更新,必須清空此對象的緩存數據。調用
SessionFactory.evict(class)
SessionFactory.evict(class,id)
等方法。
?
3
、在批量刪除數據量不大的時候可以直接采用
hibernate
的批量刪除,這樣就不存在繞開
hibernate
執行
sql
產生的緩存數據一致性的問題。
?
4
、不推薦采用
hibernate
的批量刪除方法來刪除大批量的記錄數據。
原因是
hibernate
的批量刪除會執行
1
條查詢語句外加
滿足條件的
n
條刪除語句。而不是一次執行一條條件刪除語句!!
當待刪除的數據很多時會有很大的性能瓶頸!!!如果批量刪除數據量較大
,
比如超過
50
條
,
可以采用
JDBC
直接刪除。這樣作的好處是只執行一條
sql
刪除語句
,
性能會有很大的改善。同時,緩存數據同步的問題
,
可以采用
hibernate
清除二級緩存中的相關數據的方法。
調用
SessionFactory.evict(class)
;
SessionFactory.evict(class,id)
等方法。
?
所以說,對于一般的應用系統開發而言(不涉及到集群,分布式數據同步問題等),因為只在中間關聯表執行批量刪除時調用了
sql
執行,同時中間關聯表一般是執行條件查詢不太可能執行按
id
查詢。所以,此時可以直接執行
sql
刪除,甚至不需要調用緩存的清除方法。這樣做不會導致以后配置了二級緩存引起數據有效性的問題。
?
退一步說,即使以后真的調用了按
id
查詢中間表對象的方法,也可以通過調用清除緩存的方法來解決。
?
4、具體的配置方法?
根
據我了解的很多hibernate的使用者在調用其相應方法時都迷信的相信“hibernate會自行為我們處理性能的問題”,或者“hibernate
會自動為我們的所有操作調用緩存”,實際的情況是hibernate雖然為我們提供了很好的緩存機制和擴展緩存框架的支持,但是必須經過正確的調用其才有
可能發揮作用!!所以造成很多使用hibernate的系統的性能問題,實際上并不是hibernate不行或者不好,而是因為使用者沒有正確的了解其使
用方法造成的。相反,如果配置得當hibernate的性能表現會讓你有相當“驚喜的”發現。下面我講解具體的配置方法.
?ibernate提供了二級緩存的接口:
net.sf.hibernate.cache.Provider,
同時提供了一個默認的 實現net.sf.hibernate.cache.HashtableCacheProvider,
也可以配置 其他的實現 比如ehcache,jbosscache等。
具體的配置位置位于hibernate.cfg.xml文件中
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</property>
很多的hibernate使用者在 配置到 這一步 就以為 完事了,
注
意:其實光這樣配,根本
就沒有使用hibernate的二級緩存。同時因為他們在使用hibernate時大多時候是馬上關閉session,所以,一級緩存也沒有起到任何作
用。結果就是沒有使用任何緩存,所有的hibernate操作都是直接操作的數據庫!!性能可以想見。
正確的辦法是除了以上的配置外還應該配置每一個vo對象的具體緩存策略,在影射文件中配置。例如:
<hibernate-mapping>
<class name="com.sobey.sbm.model.entitySystem.vo.DataTypeVO" table="dcm_datatype">
<cache usage="read-write"/>
<id name="id" column="TYPEID" type="java.lang.Long">
<generator class="sequence"/>
</id>
<property name="name" column="NAME" type="java.lang.String"/>
<property name="dbType" column="DBTYPE" type="java.lang.String"/>
</class>
</hibernate-mapping>
關鍵就是這個<cache usage="read-write"/>,其有幾個選擇
read-only,read-write,transactional,等
然后在執行查詢時 注意了 ,如果是條件查詢,或者返回所有結果的查詢,此時session.find()方法 不會獲取緩存中的數據。只有調用query.iterate()方法時才會調緩存的數據。
同時 get 和 load方法 是都會查詢緩存中的數據 .
對于不同的緩存框架具體的配置方法會有不同,但是大體是以上的配置
(另外,對于支持事務型,以及支持集群的環境的配置我會爭取在后續的文章中中 發表出來)
?
總之是根據不同的業務情況和項目情況對
hibernate
進行有效的配置和正確的使用,揚長避短。不存在適合于任何情況的一個“萬能”的方案。
以上結論及建議均建立在自己在對
Hibernate 2.1.2
中的測試結果以及以前的項目經驗的基礎上。如有謬處,請打家提出指正:)!
最后,祝大家 新年快樂!!在新的一年里 取得人生的進步!!!
posted on 2006-08-22 09:37
保爾任 閱讀(170)
評論(0) 編輯 收藏