JAVA框架之Hibernate【Hibernate緩存詳解】

一、緩存介紹

Hibernate中提供了兩級Cache,第一級別的緩存是Session級別的緩存,它是屬於事務範圍的緩存。這一級別的緩存由hibernate管理的,通常狀況下無需進行干預;第二級別的緩存是SessionFactory級別的緩存,它是屬於進程範圍或羣集範圍的緩存。這一級別的緩存能夠進行配置和更改,而且能夠動態加載和卸載。 Hibernate還爲查詢結果提供了一個查詢緩存,它依賴於第二級緩存。

一. 一級緩存和二級緩存的比較:

一級緩存和第二級緩是存放數據的形式是以相互關聯的持久化對象,對象的散裝數據等方式存儲的。每一個事務都有單獨的第一級緩存,緩存被同一個進程或集羣範圍內的全部事務共享,因爲每一個事務都擁有單獨的第一級緩存,不會出現併發問題,無需提供併發訪問策略,因爲多個事務會同時訪問第二級緩存中相同數據,所以必須提供適當的併發訪問策略,來保證特定的事務隔離級別,沒有提供數據過時策略。處於一級緩存中的對象永遠不會過時,除非應用程序顯式清空緩存或者清除特定的對象,因此必須提供數據過時策略,數據過時策略如:基於內存的緩存中的對象的最大數目,容許對象處於緩存中的最長時間,以及容許對象處於緩存中的最長空閒時間,物理存儲介質內存,內存和硬盤。對象的散裝數據首先存放在基於內存的緩存中,當內存中對象的數目達到數據過時策略中指定上限時,就會把其他的對象寫入基於硬盤的緩存中。緩存的軟件實如今Hibernate的Session的實現中包含了緩存的實現由第三方提供,Hibernate僅提供了緩存適配器(CacheProvider)。用於把特定的緩存插件集成到Hibernate中。啓用緩存的方式只要應用程序經過Session接口來執行保存、更新、刪除、加載和查詢數據庫數據的操做,Hibernate就會啓用第一級緩存,把數據庫中的數據以對象的形式拷貝到緩存中,對於批量更新和批量刪除操做,若是不但願啓用第一級緩存,能夠繞過Hibernate API,直接經過JDBC API來執行指操做。用戶能夠在單個類或類的單個集合的粒度上配置第二級緩存。若是類的實例被常常讀但不多被修改,就能夠考慮使用第二級緩存。只有爲某個類或集合配置了第二級緩存,Hibernate在運行時纔會把它的實例加入到第二級緩存中。用戶管理緩存的方式:第一級緩存的物理介質爲內存,因爲內存容量有限,必須經過恰當的檢索策略和檢索方式來限制加載對象的數目。Session的 evit()方法能夠顯式清空緩存中特定對象,但這種方法不值得推薦。第二級緩存的物理介質能夠是內存和硬盤,所以第二級緩存能夠存放大量的數據,數據過時策略的maxElementsInMemory屬性值能夠控制內存中的對象數目。管理第二級緩存主要包括兩個方面:選擇須要使用第二級緩存的持久類,設置合適的併發訪問策略:選擇緩存適配器,設置合適的數據過時策略。

二. 一級緩存的管理:

當應用程序調用Session的save()、update()、savaeOrUpdate()、get()或load(),以及調用查詢接口的 list()、iterate()或filter()方法時,若是在Session緩存中還不存在相應的對象,Hibernate就會把該對象加入到第一級緩存中。當清理緩存時,Hibernate會根據緩存中對象的狀態變化來同步更新數據庫。 Session爲應用程序提供了兩個管理緩存的方法: evict(Object obj):從緩存中清除參數指定的持久化對象。 clear():清空緩存中全部持久化對象。

三. Hibernate二級緩存的管理:

1. Hibernate二級緩存策略的通常過程以下:
1) 條件查詢的時候,老是發出一條select * from table_name where …. (選擇全部字段)這樣的SQL語句查詢數據庫,一次得到全部的數據對象
2) 把得到的全部數據對象根據ID放入到第二級緩存中。
3) 當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,若是配置了二級緩存,那麼從二級緩存中查;查不到,再查詢數據庫,把結果按照ID放入到緩存。
4) 刪除、更新、增長數據的時候,同時更新緩存。
Hibernate二級緩存策略,是針對於ID查詢的緩存策略,對於條件查詢則毫無做用。爲此,Hibernate提供了針對條件查詢的Query Cache。

2. 什麼樣的數據適合存放到第二級緩存中?
1) 不多被修改的數據
2) 不是很重要的數據,容許出現偶爾併發的數據
3) 不會被併發訪問的數據
4) 參考數據,指的是供應用參考的常量數據,它的實例數目有限,它的實例會被許多其餘類的實例引用,實例極少或者歷來不會被修改。

3. 不適合存放到第二級緩存的數據?
1) 常常被修改的數據
2) 財務數據,絕對不容許出現併發
3) 與其餘應用共享的數據。

4. 經常使用的緩存插件 Hibernater二級緩存是一個插件,下面是幾種經常使用的緩存插件:
◆EhCache:可做爲進程範圍的緩存,存放數據的物理介質能夠是內存或硬盤,對Hibernate的查詢緩存提供了支持。
◆OSCache:可做爲進程範圍的緩存,存放數據的物理介質能夠是內存或硬盤,提供了豐富的緩存數據過時策略,對Hibernate的查詢緩存提供了支持。
◆SwarmCache:可做爲羣集範圍內的緩存,但不支持Hibernate的查詢緩存。
◆JBossCache:可做爲羣集範圍內的緩存,支持事務型併發訪問策略,對Hibernate的查詢緩存提供了支持。

5. 配置Hibernate二級緩存的主要步驟:
1) 選擇須要使用二級緩存的持久化類,設置它的命名緩存的併發訪問策略。這是最值得認真考慮的步驟。
2) 選擇合適的緩存插件,而後編輯該插件的配置文件。

2、緩存應用的場景:
一、對於新聞,論壇,博客等互聯網應用適合在前端作緩存,好比url作爲key來緩存整個頁面的內容。一條新聞a被如前所述的緩存起來了,在網站併發訪問量大時,會大大提升網站的吞吐能力。好了現再需要編輯這條新聞,如何同步更新緩存呢?需要當即同步更新緩存嗎?不需要,互聯網應用容許用戶在5-10分鐘以後再看到更新以後的新聞,這是能夠接受的。沒有較高的時效性,容許延遲。這樣咱們設定緩存對象的最大生命時間爲10分鐘,一個被緩存的對象存活時間超過10分鐘就被清理,當新的訪問請求到來時,再從數據庫中加載他,再次被緩存10分鐘。Hibernate二級緩存不適合這個場景,這個場景對緩存的鎖、事務沒有要求,對高併發,高數據量有要求。
    總結一下:被緩存的對象沒有較高的時效性,容許對象更新後延遲(10分鐘內)展現,容許(10分鐘內)的數據不一致。
前端

二、對於企業應用,要保證數據的一致性是第一位的,即便數據被修改,最終用戶看到的數據與數據庫中的數據要時時一致。適合在應用程序持久層上作緩存,Hibernate二級緩存就適合這個場景。
    總結一下:這個場景對緩存的鎖,事務要求是第一的。對高併發高數據量的要求是第二的,經過鎖保證數據的一致性。Hibernate對數據庫是獨佔的,修改給數據庫的操做都經過他.
java

 

3、頻繁更新的數據要不要被緩存:
網上有人說頻繁更新的數據不適合使用緩存。這樣說是不全面的,由於他少說了前提條件。
sql

 

數據一致性:本文章的數據一致性是指緩存中的數據與數據庫中的數據就保持一致,嚴格的一致。決對沒有髒數據。數據庫

 

當你需要數據一致性,而又不能保存數據一致性時,頻繁更新的數據就不能夠被緩存。不緩存直接操做數據庫,就一致了,沒有不一致的問題了。緩存

      你的Hibernate對數據庫不是獨佔的,有其它程序來修改數據庫中的記錄,這時Hibernate是不知道的,也會發生數據不一致.安全

      當使用url或sql語句作爲KEY來緩存時,一句select 語句查出n個對象,沒法在緩存中精準的找到被修改的某一個對象,當修改一個對象時就不能在緩存中精準的找到他,爲了保證數據一致性,就要清除緩存中全部的同類對象,使下次查詢時沒法命中緩存。而不清除,頗有可能發生數據不一致。這至關於Hibernate的查詢緩存。這時個不要使用緩存。服務器

     
當你需要數據一致性,而又能保存數據一致性時,頻繁更新的數據是能夠被緩存的。這裏咱們使用緩存的「鎖」機制來保證,你使用的Hibernate第三方緩存要支持「鎖」,就是read-write模式。這是重點啊。第三方緩存鎖的實現方法不一樣性能也不一樣,鎖是緩存性能降低第一緣由,必定要使用高性能的鎖,這就要了解多款Hibernate第三方緩存.
網絡

      由於常常被更新修改的對象,必定也更加常常的被查詢,須要緩存他來提升應用程序的性能。若是執行修改sql時,同時鎖住緩存中的這個對象並更新他,以後解鎖是最理想的。Hibernate的二級緩存策略,是針對於ID查詢的緩存策略,因此能夠作到精準的找到緩存中的目標,加之「鎖」的幫助,可實現數據一致性。 session

但限制也有好比使用HQL時就不能精準的找到緩存中的目標,只好清除緩存中全部的同類對象來保證數據的一致性(緩存中沒數據就沒一致性問題了)。 併發


4、Hibernate二級緩存中的對象何時會被清理:
在read-write模式下:
咱們有一個Order對象,是一個實體對象,對應數據庫中order表中的一條記錄,通過查詢已有n個Order對象被放入二級緩存中。如今咱們要修改order表中任意任x條記錄,執行如下HQL:

template.bulkUpdate("update Order set owner = ? where id in (?,?,?)");

 

 

 這時Hibernate會直接將二級緩存中的n個Order對象清除掉。 天啊,竟然不是你想像的修改誰就同步更新二級緩存中的誰,而是清除了二級緩存中所有的Order類型的對象。爲何?這一切是爲了保證「數據一致性」。你執行了HQL修改了order表中的x條記錄,這x條是哪幾條?若是sql是子查詢:update Order set owner =? where id in(select id from *** ),誰知道你修改了order表中的哪幾條記錄,你本身都不知道,Hibernate更不知道了。因此爲了保證二級緩存中的數據與order表中的數據一致,只能清除了二級緩存中所有的Order類型的對象。二級緩存頻繁的載入與清除,這樣緩存命中率就會降低。

試驗:
看到這裏後,我很擔憂,這樣命中率降低後,沒有起到緩存的做用。今天特地作一個實驗,看看被緩存的對象在被修改後會怎樣。

環境:Hibernate3.4 , OsCache(usage="read-write"),JUnit
緩存狀態:Hibernate二級緩存中已緩存了5個Order對象。
測試結果:
1 使用saveOrUpdate()方法更新一個實體對象a時,新的a對象被put到二級緩存中,同時寫入數據庫,二級緩存中的其它4個Order對象沒有變化。
這時再查詢這5個Order對象中的任意,是能夠命中二級緩存的。
2 使用HQL "update Order set name = ? where id =?" 方法更新一個實體對象a時,全部Order對象被從二級緩存中清除,同時a對象被寫入數據庫。
這時再查詢這5個Order對象中的任意,沒法命中二級緩存,會去查數據庫,查出來的對象又put進二級緩存。

Hibernate的二級緩存策略,是以ID作爲key 的緩存策略,在刪除、更新、增長數據的時候,同時更新緩存。
對於條件查詢,條件修改,條件刪除(通常是執行HQL)則起不到緩存的做用。條件修改,條件刪除時(通常是執行HQL)會清空全部在緩存中的同類對象。
爲此,Hibernate提供了針對條件查詢的Query Cache,其實它並很差用。

關因而否命中,是使用Statistics類監測的(經過SessionFactory的getStatistics()方法獲得)。

總結一下:若是你打算開啓hibernate的二級緩存,在修改與刪除時,就要使用session.update(),session.delete()方法按ID一條一條的操做,這樣對二級緩存是最優的。
    但循環中使用sesion.update(),session.delete()方法,會產生多條sql語句,本來使用一條HQL完成的工做,如今要執行多條,你擔憂Hibernate與數據庫服務器的網絡通訊次數嗎?其實這多條sql是使用JDBC的批處理一次髮送到數據庫服務器的,因此你不用擔憂。如今到了數據庫服務器端,咱們以oracle爲例,oracle要執行多條sql,就要進行屢次的「分析sql語句的正確性,並解析成oracle的原子操做,並制定執行計劃」,你擔憂這「屢次」分析會給oracle帶來性能的影響嗎?不用擔憂,請使用oracle的綁定參數,就是Hibernate中的?代替參數。


5、Hibernate二級緩存的併發策略你瞭解嗎:

1 只讀緩存 read only
不需要鎖與事務,由於緩存自數據從數據庫加載後就不會改變。

    若是數據是隻讀的,例如引用數據,那麼老是使用「read-only」策略,由於它是最簡單、最高效的策略,也是集羣安全的策略。是性能第一的策略 。

2 讀寫緩存 read write
對緩存的更新發生在數據庫事務完成後。緩存須要支持鎖。
在一個事務中更新數據庫,在這個事務成功完成後更新緩存,並釋放鎖。
鎖只是一種特定的緩存值失效表述方式,在它得到新數據庫值前阻止其餘事務讀寫緩存。那些事務會轉而直接讀取數據庫。
緩存必須支持鎖,事務支持則不是必須的。若是緩存是一個集羣,「更新緩存」的調用會將新值推送給全部副本,這一般被稱爲「推(push)」更新策略。

    若是你的數據是又讀又寫的,那麼使用「read-write」策略。這一般是性能第三的策略,由於它要求有緩存鎖,緩存集羣中使用重量級的「推」更新策略。

3 非嚴格讀寫緩存 nonstrict read write
在一個事務中更新數據庫,在這個事務完成前就清除緩存,爲了安全起見,不管事務成功與否,在事務完成後再次清除緩存。
既不須要支持緩存鎖,也不須要支持事務。若是是緩存集羣,「清除緩存」調用會讓全部副本都失效,這一般被稱爲「拉(pull)」更新策略。

    若是你的數據讀不少或者不多有併發緩存訪問和更新,那麼可使用「nonstrict-read-write」策略。感謝它的輕量級「拉」更新策略,它一般是性能第二好的策略。

4 事務緩存 transactional (必定要在JTA環境中)
對緩存和數據庫的更新被包裝在同一個JTA事務中,這樣緩存與數據庫老是保持同步的。數據庫和緩存都必須支持JTA。

    除非你真的想將緩存更新和數據庫更新放在一個JTA事務裏,不然不要使用「transactional」策略,由於JTA須要漫長的兩階段提交處理,這致使它基本是性能最差的策略。


6、緩存鎖的性能也要了解,知道加了鎖後性能會降低:
    爲了保證數據的安全性,不發生髒數據,各個緩存一般使用鎖來保證
在本地方式運行時,緩存最大的開銷就是使用鎖來在保證共享數據完整性。
在集羣環境中,RPC調用,鎖,是性能上大開銷。

 

下面以JBoss Cache爲例說一說鎖:
JBoss Cache1.* 和 2.* 時代,提供樂觀鎖,悲觀鎖,可是性能不高。
JBoss Cache3.0 MVCC鎖方案性能很高。

悲觀鎖:這些鎖的隔離級別和數據庫實施的隔離級別相同,這種方案簡單並且健壯,容許多用戶同時讀取數據。讀操做阻塞寫操做,悲觀鎖的讀寫是互斥的,沒法同時進行的,寫的性能很差。

樂觀鎖:這個方式則牽涉到數據版本,能夠得到高度併發性。那些請求讀取數據的用戶不會由於併發數據庫寫入操做而受到阻塞。並且,樂觀鎖定方式還能夠避免悲觀鎖定中有可能發生的死鎖。但它仍然有兩個主要的缺點:一是性能問題。由於不斷的將結點的狀態拷貝到每一個併發線程所形成的內存和 CPU 開銷是不容忽略的。二是儘管併發時容許了寫操做,可是一旦發現數據的版本不對,事務提交時不可避免的仍是會失敗。也就是說,此時寫事務雖然能夠不受限制的進行大量處理和寫操做,可是這樣在事務結束的時候容易出現提交失敗。

多版本併發控制(MVCC):在數據訪問速度上較以前者也勝出百倍。MVCC 提供了非阻塞 (non-blocking) 讀操做 ( 它並不會去阻塞 wirter threads) ,在避免死鎖的同時也提供了更高級的併發機制。更棒的是,咱們的 MVCC 實現甚至能夠對 reader threads 徹底不採用任何鎖 ( 對於像緩存這樣頻繁讀取的系統來講,意義太大了 ) ,

 

7、批量處理時請不要使用二級緩存
當你執行大量的 添加與修改時,而且這個實體對象被配置爲啓用二級緩存,你考慮過二級緩存會怎麼樣嗎?請看下面代碼:

Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); //若是你的 hibernate.cache.use_second_level_cache 是 true, 請在會話級別上關閉他 //向(任何一級)緩存中加載大量數據一般也意味着它們很快會被清除出去,這會增長GC開銷。 session.setCacheMode(CacheMode.IGNORE); session.save(customer); if ( i % 50 == 0 ) { //將本批插入的對象當即寫入數據庫並釋放內存 session.flush(); session.clear(); } } tx.commit(); session.close();

 

 批處理一般不須要數據緩存,不然你會將內存耗盡並大量增長GC開銷。若是內存有限,那這種狀況會很明顯。


8、瞭解幾種優秀緩存方案:一、Memcached 分佈式緩存系統,memcached 要求set的對象必須是可序列化對象,jboss cache等java obect cache是沒有這個說法的,這是本質的不一樣的,可是他能夠在網絡上用,因此必須序列化也可理解。 獨立服務器+java 客戶端。Memcached java 客戶端有:memcache-client-forjava,XMemcached,spymemcached,memcache-client-forjava

相關文章
相關標籤/搜索