(五)Hibernate一級緩存
一、簡介
緩存,介於應用程序和永久數據存儲源之間,作用是為了降低應用程序對物理數據源訪問的頻率,從而提高應用的運行性能。
例如我們cpu執行效率每秒處理的數據高達上千兆,而我們的硬盤讀取速度卻沒那麽高,讀取幾百兆,這時候我們使用緩存來存儲數據,存儲滿後一次性交由cpu處理。
Hibernate中也存在緩存,同樣是為了提高效率。Hibernate的緩存包括Session的緩存和SessionFactory的緩存。
Session的緩存是內置的,不能被卸載,也被稱為Hibertnate的一級緩存。
SessionFactory有一個內置緩存和外置緩存。SessionFactory的外置緩存是一個可配置的緩存插件。默認情況下,Hibernate不會啟用這個緩存插件。被稱為Hibernate的二級緩存。
二、緩存範圍
事務範圍:緩存只能被當前事務訪問。一級緩存是Session的緩存,Session對象生命周期通常對應一個事務,因此是事務範圍的緩存。
進程範圍:緩存被進程內的所有事務共享。二級緩存是可配置的緩存插件,由SessionFactory管理,SessionFactory生命周期和應用程序的進程對應,因此是進程範圍的緩存。
集群範圍:在集群環境中,緩存被同一個機器或者多個機器上的多個進程共享。
三、Session緩存
Session緩存是Hibernate的一級緩存。Session對象中具有一個緩存。Session的緩存是一塊內存空間,存放的是持久化對象。
當Session通過save方法持久化一個對象時,該對象被加入到Session緩存中。
當Session通過get方法獲取一個持久化對象時,Session會先判斷Session緩存中是否存在這個對象,如果存在,就不需要再從數據庫中查找。
========我們來測試一下緩存的存在==============
//開啟事務 Transaction ts=session.beginTransaction(); //加上斷點,當我們執行完這一步,會打印select語句,而後面的都不會打印,說明並沒有從數據庫中獲取 User user1=session.get(User.class, 5); //這次get方法會先從session緩存中查找,由於已經存在,直接返回引用 User user2=session.get(User.class, 5); User user3=session.get(User.class, 5); System.out.println(user1==user2);//true System.out.println(user1==user3);//true session.close();
四、臟檢查及清理緩存的機制
我們先來看下面的例子
Transaction ts=session.beginTransaction(); User user1=session.get(User.class, 5); user1.setName("swaggy"); ts.commit();
我們發現我們改變了Name屬性,這時候session緩存中的對象的name屬性和數據庫表中的NAME字段不一致了。但是我們並沒有進行更新操作,而是直接提交了事務
幸運的是,Session中在清理緩存的時候,會自動進行臟檢查。如果發現Session緩存中的持久化對象和數據庫中的記錄不一致,就會根據對象的最新屬性去更新數據庫。
所以在本例中,Session會自動提交一個update語句對數據庫進行更新。
(1)Session是怎樣進行臟檢查的呢?
當一個對象被加入到Sesion緩存中時,Session會為該對象復制一份快照。當Session清理緩存時,會比較當前對象的屬性和快照來判斷是否發生變化,如果發生變化,就會根據最新屬性來執行相關的更新操作。
我們看下面一個例子加深對快照的理解
//我們從數據庫中取出 id為5,name為tom,password為123456的對象 Transaction ts=session.beginTransaction(); User user1=session.get(User.class, 5); session.update(user1); session.close(); 過程:獲取了持久化對象,放入緩存中,並創建了快照,我們執行更新,Session緩存中的對象會和快照進行比較,沒有任何變化,所以不會執行update語句。
//我們自己設置一個和數據庫中一模一樣的對象,這時候會打印update語句 Transaction ts=session.beginTransaction(); User user=new User(); user.setId(5); user.setName("tom"); user.setPassword("123456"); session.update(user); ts.commit(); session.close(); 過程:因為此時我們執行update語句時會將對象直接放入緩存中,但是沒有持久化對象的快照,所以進行對比結果就是不一致,所以盡管什麽都沒更改,還是會執行update語句,在控制臺打印。
(2)什麽時候會清理緩存呢?
-默認情況下,在調用commit()方法時會先清理緩存。再向數據庫提交事務。 -當執行查詢操作時,如果緩存中的持久化對象屬性已經發生了改變,就會先清理緩存,同步數據,保證查詢到的是正確的結果。 -當應用程序顯式調用Session的flush()方法時 -Session清理緩存的例外情況,如果對象使用的是native生成策略生成OID,那麽調用Session的save()方法來保存該對象時,會立刻執行向數據庫的插入語句。
(3)如果不希望Session在以上默認的時間點清理緩存,可以通過Session的setFlushMode()方法來設定清理緩存的時間點。
FlushMode類定義了三種清理模式。
各種查詢方法 commit()方法 flush()方法 -FlushMode.AUTO(默認模式) 清理 清理 清理 -FlushMode.COMMIT 不清理 清理 清理 -FlushMode.NEVER 不清理 不清理 清理 例如Session.setFlushMode(FlushMode.AUTO)
五、Session接口的其他API
--Session的save()和persist()方法 兩個方法都是用來保存對象,能把臨時狀態轉變為持久化狀態。 兩個方法的區別在於: save方法持久化對象時,會返回持久化對象的OID。所以程序執行save時會立刻執行insert語句,來返回數據庫生成的OID。 persist方法持久化對象時,不會保證立即為持久化對象的OID賦值,不會立即生成insert語句,而是有可能在Session清理緩存時才為OID賦值。 --Session的clear()方法 清空一級緩存 --Session的update方法 update()方法可以將遊離對象轉變為持久化對象。用來執行修改操作。 update()方法完成以下操作 -把遊離對象加入到當前緩存中,變為持久化對象 -然後計劃執行update語句 只要通過update使遊離對象轉變為持久化對象,即使沒有修改任何屬性,在清理緩存時還是會執行update語句。 如果希望Session僅當修改了屬性時才執行update語句,可以在映射文件中的<class>元素中設置select-before-update="true",默認為false 這樣當Session清理緩存時,會先發送一條查詢語句,然後判斷緩存中的對象和記錄是否一致,不一致才執行update語句。 當update()方法將遊離對象轉變為持久化對象時,如果Session緩存中已經存在相同的OID持久化對象,那麽會拋出異常。 例如: Transaction ts=session.beginTransaction(); User user1=session.get(User.class, 5); session.evict(user1); User user2=session.get(User.class, 5); session.update(user1); ts.commit(); 因為Session的緩存是一個Map結構,OID為key,對象為value。 當執行session的update方法時,由於緩存中已經存在了OID為5的持久化對象,因此會拋出異常。 -Session的saveOrUpdate()方法 Session的saveOrUpdate()方法同時包含了save()和update()方法的功能 如果傳入的參數是臨時對象(OID為null),就調用save方法。 如果傳入的參數是遊離對象(OID不為null),就執行update方法。
(五)Hibernate一級緩存