1. 關於hibernate緩衝的問題:
1.1.1. 基本的緩衝原理
Hibernate緩衝分為二級,第一級存放於session中稱為一級緩衝,預設帶有且不能卸載。
第二級是由sessionFactory控制的進程級緩衝。是全域共用的緩衝,凡是會調用二級緩衝的查詢方法 都會從中受益。只有經正確的配置後二級緩衝才會發揮作用。同時在進行條件查詢時必須使用相應的方法才能從緩衝中擷取資料。比如Query.iterate()方法、load、get方法等。必須注意的是session.find方法永遠是從資料庫中擷取資料,不會從二級緩衝中擷取資料,即便其中有其所需要的資料也是如此。
查詢時使用緩衝的實現過程為:首先查詢一級緩衝中是否具有需要的資料,如果沒有,查詢二級緩衝,如果二級緩衝中也沒有,此時再執行查詢資料庫的工作。要注意的是:此3種方式的查詢速度是依次降低的。
1.2. 存在的問題
1.2.1. 一級緩衝的問題以及使用二級緩衝的原因
因為Session的生命期往往很短,存在於Session內部的第一級最快緩衝的生命期當然也很短,所以第一級緩衝的命中率是很低的。其對系統效能的改善也是很有限的。當然,這個Session內部緩衝的主要作用是保持Session內部資料狀態同步。並非是hibernate為了大幅提高系統效能所提供的。
為了提高使用hibernate的效能,除了常規的一些需要注意的方法比如:
使用消極式載入、迫切外串連、查詢過濾等以外,還需要配置hibernate的二級緩衝。其對系統整體效能的改善往往具有立竿見影的效果!
(經過自己以前作項目的經驗,一般會有3~4倍的效能提高)
1.2.2. N+1次查詢的問題
執行條件查詢時,iterate()方法具有著名的 “n+1”次查詢的問題,也就是說在第一次查詢時iterate方法會執行滿足條件的查詢結果數再加一次(n+1)的查詢。但是此問題只存在於第一次查詢時,在後面執行相同查詢時效能會得到極大的改善。此方法適合於查詢資料量較大的業務資料。
但是注意:當資料量特別大時(比如流水線資料等)需要針對此持久化對象配置其具體的緩衝策略,比如設定其存在於緩衝中的最大記錄數、緩衝存在的時間等參數,以避免系統將大量的資料同時裝載入記憶體中引起記憶體資源的迅速耗盡,反而降低系統的效能!!!
1.3. 使用hibernate二級緩衝的其他注意事項:
1.3.1. 關於資料的有效性
另外,hibernate會自我維護二級緩衝中的資料,以保證緩衝中的資料和資料庫中的真實資料的一致性!無論何時,當你調用save()、update()或 saveOrUpdate()方法傳遞一個對象時,或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個對象時, 該對象都將被加入到Session的內部緩衝中。 當隨後flush()方法被調用時,對象的狀態會和資料庫取得同步。
也就是說刪除、更新、增加資料的時候,同時更新緩衝。當然這也包括二級緩衝!
只要是調用hibernate API執行資料庫相關的工作。hibernate都會為你自動保證 快取資料的有效性!!
但是,如果你使用了JDBC繞過hibernate直接執行對資料庫的操作。此時,Hibernate不會/也不可能自行感知到資料庫被進行的變化改動,也就不能再保證緩衝中資料的有效性!!
這也是所有的ORM產品共同具有的問題。幸運的是,Hibernate為我們暴露了Cache的清除方法,這給我們提供了一個手動保證資料有效性的機會!!
一級緩衝,二級緩衝都有相應的清除方法。
其中二級緩衝提供的清除方法為:
按對象class清空緩衝
按對象class和對象的主鍵id清空緩衝
清Null 物件的集合中的快取資料等。
1.3.2. 適合使用的情況
並非所有的情況都適合於使用二級緩衝,需要根據具體情況來決定。同時可以針對某一個持久化對象配置其具體的緩衝策略。
適合於使用二級緩衝的情況:
1、資料不會被第三方修改;
一般情況下,會被hibernate以外修改的資料最好不要配置二級緩衝,以免引起不一致的資料。但是如果此資料因為效能的原因需要被緩衝,同時又有可能被第3方比如SQL修改,也可以為其配置二級緩衝。只是此時需要在sql執行修改後手動調用cache的清除方法。以保證資料的一致性
2、資料大小在可接收範圍之內;
如果資料表資料量特別巨大,此時不適合於二級緩衝。原因是緩衝的資料量過大可能會引起記憶體資源緊張,反而降低效能。
如果資料表資料量特別巨大,但是經常使用的往往只是較新的那部分資料。此時,也可為其配置二級緩衝。但是必須單獨配置其持久化類的緩衝策略,比如最大緩衝數、緩衝到期時間等,將這些參數降低至一個合理的範圍(太高會引起記憶體資源緊張,太低了緩衝的意義不大)。
3、資料更新頻率低;
對於資料更新頻率過高的資料,頻繁同步緩衝中資料的代價可能和 查詢快取中的資料從中獲得的好處相當,壞處益處相抵消。此時緩衝的意義也不大。
4、非關鍵資料(不是財務資料等)
財務資料等是非常重要的資料,絕對不允許出現或使用無效的資料,所以此時為了安全起見最好不要使用二級緩衝。
因為此時 “正確性”的重要性遠遠大於 “高效能”的重要性。
2. 目前系統中使用hibernate緩衝的建議
1.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.5. 建議
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方法 是都會查詢快取中的資料 .
對於不同的緩衝架構具體的配置方法會有不同,但是大體是以上的配置
(另外,對於支援事務型,以及支援叢集的環境的配置我會爭取在後續的文章中中 發表出來)
3. 總結
總之是根據不同的業務情況和項目情況對hibernate進行有效配置和正確的使用,揚長避短。不存在適合於任何情況的一個“萬能”的方案。