關於hibernate緩存的問題
基本的緩存原理
Hibernate緩存分為二級
第一級存放於session中稱為一級緩存默認帶有且不能卸載
第二級是由sessionFactory控制的進程級緩存是全局共享的緩存凡是會調用二級緩存的查詢方法 都會從中受益只有經正確的配置後二級緩存才會發揮作用同時在進行條件查詢時必須使用相應的方法才能從緩存中獲取數據比如erate()方法loadget方法等必須注意的是sessionfind方法永遠是從數據庫中獲取數據不會從二級緩存中獲取數據即便其中有其所需要的數據也是如此
查詢時使用緩存的實現過程為首先查詢一級緩存中是否具有需要的數據如果沒有查詢二級緩存如果二級緩存中也沒有此時再執行查詢數據庫的工作要注意的是此種方式的查詢速度是依次降低的
存在的問題
一級緩存的問題以及使用二級緩存的原因
因為Session的生命期往往很短存在於Session內部的第一級最快緩存的生命期當然也很短所以第一級緩存的命中率是很低的其對系統性能的改善也是很有限的當然這個Session內部緩存的主要作用是保持Session內部數據狀態同步並非是hibernate為了大幅提高系統性能所提供的
為了提高使用hibernate的性能除了常規的一些需要注意的方法比如
使用延遲加載迫切外連接查詢過濾等以外還需要配置hibernate的二級緩存其對系統整體性能的改善往往具有立竿見影的效果!
(經過自己以前作項目的經驗一般會有~倍的性能提高)
N+次查詢的問題
什麼時候會遇到+N的問題?
前提Hibernate默認表與表的關聯方法是fetch=select不是fetch=join這都是為了懶加載而准備的
)一對多(<set><list>) 在的這方通過條sql查找得到了個對象由於關聯的存在 那麼又需要將這個對象關聯的集合取出所以合集數量是n還要發出n條sql於是本來的條sql查詢變成了 +n條
)多對一<manytoone> 在多的這方通過條sql查詢得到了n個對象由於關聯的存在也會將這n個對象對應的 方的對象取出 於是本來的條sql查詢變成了 +n條
)iterator 查詢時一定先去緩存中找(條sql查集合只查出ID)在沒命中時會再按ID到庫中逐一查找 產生+n條SQL
怎麼解決+N 問題?
)lazy=true hibernate開始已經默認是lazy=true了lazy=true時不會立刻查詢關聯對象只有當需要關聯對象(訪問其屬性非id字段)時才會發生查詢動作
)使用二級緩存 二級緩存的應用將不怕+N 問題因為即使第一次查詢很慢(未命中)以後查詢直接緩存命中也是很快的剛好又利用了+N
) 當然你也可以設定fetch=join一次關聯表全查出來但失去了懶加載的特性
執行條件查詢時iterate()方法具有著名的 n+次查詢的問題也就是說在第一次查詢時iterate方法會執行滿足條件的查詢結果數再加一次(n+)的查詢但是此問題只存在於第一次查詢時在後面執行相同查詢時性能會得到極大的改善此方法適合於查詢數據量較大的業務數據
但是注意當數據量特別大時(比如流水線數據等)需要針對此持久化對象配置其具體的緩存策略比如設置其存在於緩存中的最大記錄數緩存存在的時間等參數以避免系統將大量的數據同時裝載入內存中引起內存資源的迅速耗盡反而降低系統的性能!!!
使用hibernate二級緩存的其他注意事項
關於數據的有效性
另外hibernate會自行維護二級緩存中的數據以保證緩存中的數據和數據庫中的真實數據的一致性!無論何時當你調用save()update()或 saveOrUpdate()方法傳遞一個對象時或使用load() get()list()iterate() 或scroll()方法獲得一個對象時 該對象都將被加入到Session的內部緩存中 當隨後flush()方法被調用時對象的狀態會和數據庫取得同步
也就是說刪除更新增加數據的時候同時更新緩存當然這也包括二級緩存!
只要是調用hibernate API執行數據庫相關的工作hibernate都會為你自動保證 緩存數據的有效性!!
但是如果你使用了JDBC繞過hibernate直接執行對數據庫的操作此時Hibernate不會/也不可能自行感知到數據庫被進行的變化改動也就不能再保證緩存中數據的有效性!!
這也是所有的ORM產品共同具有的問題幸運的是Hibernate為我們暴露了Cache的清除方法這給我們提供了一個手動保證數據有效性的機會!!
一級緩存二級緩存都有相應的清除方法
其中二級緩存提供的清除方法為
按對象class清空緩存
按對象class和對象的主鍵id清空緩存
清空對象的集合中的緩存數據等
適合使用的情況
並非所有的情況都適合於使用二級緩存需要根據具體情況來決定同時可以針對某一個持久化對象配置其具體的緩存策略
適合於使用二級緩存的情況
數據不會被第三方修改
一般情況下會被hibernate以外修改的數據最好不要配置二級緩存以免引起不一致的數據但是如果此數據因為性能的原因需要被緩存同時又有可能被第方比如SQL修改也可以為其配置二級緩存只是此時需要在sql執行修改後手動調用cache的清除方法以保證數據的一致性
數據大小在可接收范圍之內
如果數據表數據量特別巨大此時不適合於二級緩存原因是緩存的數據量過大可能會引起內存資源緊張反而降低性能
如果數據表數據量特別巨大但是經常使用的往往只是較新的那部分數據此時也可為其配置二級緩存但是必須單獨配置其持久化類的緩存策略比如最大緩存數緩存過期時間等將這些參數降低至一個合理的范圍(太高會引起內存資源緊張太低了緩存的意義不大)
數據更新頻率低
對於數據更新頻率過高的數據頻繁同步緩存中數據的代價可能和 查詢緩存中的數據從中獲得的好處相當壞處益處相抵消此時緩存的意義也不大
非關鍵數據(不是財務數據等)
財務數據等是非常重要的數據絕對不允許出現或使用無效的數據所以此時為了安全起見最好不要使用二級緩存
因為此時 正確性的重要性遠遠大於 高性能的重要性
目前系統中使用hibernate緩存的建議
目前情況
一般系統中有三種情況會繞開hibernate執行數據庫操作
多個應用系統同時訪問一個數據庫
此種情況使用hibernate二級緩存會不可避免的造成數據不一致的問題此時要進行詳細的設計比如在設計上避免對同一數據表的同時的寫入操作
使用數據庫各種級別的鎖定機制等
動態表相關
所謂動態表是指在系統運行時根據用戶的操作系統自動建立的數據表
比如自定義表單等屬於用戶自定義擴展開發性質的功能模塊因為此時數據表是運行時建立的所以不能進行hibernate的映射因此對它的操作只能是繞開hibernate的直接數據庫JDBC操作
如果此時動態表中的數據沒有設計緩存就不存在數據不一致的問題
如果此時自行設計了緩存機制則調用自己的緩存同步方法即可
使用sql對hibernate持久化對象表進行批量刪除時
此時執行批量刪除後緩存中會存在已被刪除的數據
分析
當執行了第條(sql批量刪除)後後續的查詢只可能是以下三種方式
a sessionfind()方法
根據前面的總結find方法不會查詢二級緩存的數據而是直接查詢數據庫
所以不存在數據有效性的問題
b 調用iterate方法執行條件查詢時
根據iterate查詢方法的執行方式其每次都會到數據庫中查詢滿足條件的id值然後再根據此id 到緩存中獲取數據當緩存中沒有此id的數據才會執行數據庫查詢
如果此記錄已被sql直接刪除則iterate在執行id查詢時不會將此id查詢出來所以即便緩存中有此條記錄也不會被客戶獲得也就不存在不一致的情況(此情況經過測試驗證)
c 用get或load方法按id執行查詢
客觀上此時會查詢得到已過期的數據但是又因為系統中執行sql批量刪除一般是針對中間關聯數據表對於中間關聯表的查詢一般都是采用條件查詢 按id來查詢某一條關聯關系的幾率很低所以此問題也不存在!
如果某個值對象確實需要按id查詢一條關聯關系同時又因為數據量大使用 了sql執行批量刪除當滿足此兩個條件時為了保證按id 的查詢得到正確的結果可以使用手動清楚二級緩存中此對象的數據的方法!!(此種情況出現的可能性較小)
建 議
建議不要使用sql直接執行數據持久化對象的數據的更新但是可以執行 批量刪除(系統中需要批量更新的地方也較少)
如果必須使用sql執行數據的更新必須清空此對象的緩存數據調用
SessionFactoryevict(class)
SessionFactoryevict(classid)等方法
在批量刪除數據量不大的時候可以直接采用hibernate的批量刪除這樣就不存在繞開hibernate執行sql產生的緩存數據一致性的問題
不推薦采用hibernate的批量刪除方法來刪除大批量的記錄數據
原因是hibernate的批量刪除會執行條查詢語句外加 滿足條件的n條刪除語句而不是一次執行一條條件刪除語句!!
當待刪除的數據很多時會有很大的性能瓶頸!!!如果批量刪除數據量較大比如超過條可以采用JDBC直接刪除這樣作的好處是只執行一條sql刪除語句性能會有很大的改善同時緩存數據同步的問題可以采用 hibernate清除二級緩存中的相關數據的方法
調 用
SessionFactoryevict(class) ;
SessionFactoryevict(classid)等方法
所以說對於一般的應用系統開發而言(不涉及到集群分布式數據同步問題等)因為只在中間關聯表執行批量刪除時調用了sql執行同時中間關聯表一般是執行條件查詢不太可能執行按id查詢所以此時可以直接執行sql刪除甚至不需要調用緩存的清除方法這樣做不會導致以後配置了二級緩存引起數據有效性的問題
退一步說即使以後真的調用了按id查詢中間表對象的方法也可以通過調用清除緩存的方法來解決
具體的配置方法
根據我了解的很多hibernate的使用者在調用其相應方法時都迷信的相信hibernate會自行為我們處理性能的問題或者hibernate 會自動為我們的所有操作調用緩存實際的情況是hibernate雖然為我們提供了很好的緩存機制和擴展緩存框架的支持但是必須經過正確的調用其才有可能發揮作用!!所以造成很多使用hibernate的系統的性能問題實際上並不是hibernate不行或者不好而是因為使用者沒有正確的了解其使用方法造成的相反如果配置得當hibernate的性能表現會讓你有相當驚喜的發現下面我講解具體的配置方法
ibernate提供了二級緩存的接口
netsfhibernatecacheProvider
同時提供了一個默認的 實現netsfhibernatecacheHashtableCacheProvider
也可以配置 其他的實現 比如ehcachejbosscache等
具體的配置位置位於hibernatecfgxml文件中
<property name=hibernatecacheuse_query_cache>true</property>
<property name=hibernatecacheprovider_class>netsfhibernatecacheHashtableCacheProvider</property>
很多的hibernate使用者在 配置到 這一步 就以為 完事了
注意其實光這樣配根本就沒有使用hibernate的二級緩存同時因為他們在使用hibernate時大多時候是馬上關閉session所以一級緩存也沒有起到任何作用結果就是沒有使用任何緩存所有的hibernate操作都是直接操作的數據庫!!性能可以想見
正確的辦法是除了以上的配置外還應該配置每一個vo對象的具體緩存策略在影射文件中配置例如
<hibernatemapping>
<class name=comsobeysbmmodelentitySystemvoDataTypeVO table=dcm_datatype>
<cache usage=readwrite/>
<id name=id column=TYPEID type=javalangLong>
<generator class=sequence/>
</id>
<property name=name column=NAME type=javalangString/>
<property name=dbType column=DBTYPE type=javalangString/>
</class>
</hibernatemapping>
關鍵就是這個<cache usage=readwrite/>其有幾個選擇readonlyreadwritetransactional等
然後在執行查詢時 注意了 如果是條件查詢或者返回所有結果的查詢此時sessionfind()方法 不會獲取緩存中的數據只有調用erate()方法時才會調緩存的數據
同時 get 和 load方法 是都會查詢緩存中的數據
對於不同的緩存框架具體的配置方法會有不同但是大體是以上的配置(另外對於支持事務型以及支持集群的環境的配置我會爭取在後續的文章中中 發表出來)
From:http://tw.wingwit.com/Article/program/Java/ky/201311/28715.html