hibernate學習(緩存)
緩存,介於應用程序和永久數據存儲源之間,作用是為了降低應用程序對物理數據源訪問的頻率,從而提高應用的運行性能。
例如我們cpu執行效率每秒處理的數據高達上千兆,而我們的硬盤讀取速度卻沒那麽高,讀取幾百兆,這時候我們使用緩存來存儲數據,存儲滿後一次性交由cpu處理。
Hibernate中的緩存,同樣是為了提高效率。Hibernate的緩存包括Session的緩存和SessionFactory的緩存。
Session的緩存是內置的,不能被卸載,也被稱為Hibertnate的一級緩存。
SessionFactory有一個內置緩存和外置緩存。SessionFactory的外置緩存是一個可配置的緩存插件。默認情況下,Hibernate不會啟用這個緩存插件。被稱為Hibernate的二級緩存。
緩存的範圍
緩存的範圍決定了緩存的生命周期以及可以被誰訪問。
事務範圍:緩存只能被當前事務訪問。一級緩存是Session的緩存,Session對象生命周期通常對應一個事務,因此是事務範圍的緩存。
進程範圍:緩存被進程內的所有事務共享。二級緩存是可配置的緩存插件,由SessionFactory管理,SessionFactory生命周期和應用程序的進程對應,因此是進程範圍的緩存。
集群範圍:在集群環境中,緩存被同一個機器或者多個機器上的多個進程共享。
Hibernate一級緩存: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語句對數據庫進行更新。
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語句,在控制臺打印。
什麽時候會清理緩存呢?
- -默認情況下,在調用commit()方法時會先清理緩存。再向數據庫提交事務。
- -當執行查詢操作時,如果緩存中的持久化對象屬性已經發生了改變,就會先清理緩存,同步數據,保證查詢到的是正確的結果。
- -當應用程序顯式調用Session的flush()方法時
- -Session清理緩存的例外情況,如果對象使用的是native生成策略生成OID,那麽調用Session的save()方法來保存該對象時,會立刻執行向數據庫的插入語句。
如果不希望Session在以上默認的時間點清理緩存,可以通過Session的setFlushMode()方法來設定清理緩存的時間點。
FlushMode類定義了三種清理模式。
各種查詢方法 commit()方法 flush()方法
-FlushMode.AUTO(默認模式) 清理 清理 清理
-FlushMode.COMMIT 不清理 清理 清理
-FlushMode.NEVER 不清理 不清理 清理
例如Session.setFlushMode(FlushMode.AUTO)
Hibernate的二級緩存:SessionFactory
二級緩存簡介
二級緩存是一個可插拔的緩存插件,由SessionFactory管理,是進程範圍的緩存。
二級緩存有可能出現並發問題,因此需要采用適當的並發訪問策略。
該策略為緩存中的數據提供了事務隔離級別。
Hibernate還提供了查詢緩存,依賴於二級緩存。
二級緩存中存放什麽?
符合以下條件的數據適合存放在二級緩存中
- -很少被修改的數據
- -不是很重要的數據,允許偶然出現的並發問題
- -參考數據(指供應用程序參考的常量數據)
以下數據不適合存放到二級緩存中
- -經常被修改的數據
- -財務數據,絕對不允許出現並發文日
- -與其他應用共享的數據
二級緩存中緩存的並不是對象,而是對象的散裝數據。
常用二級緩存插件
二級緩存是可配置的插件,Hibernate允許選用以下的緩存插件
- -EHCache:可作為進程範圍內的緩存。存放數據的物理介質可以是硬盤或者內存,支持hibernate的查詢緩存。
- -OSCache:可作為進程範圍內的緩存,存放數據的物理介質可以是硬盤或者內存,支持hibernate的查詢緩存,提供了豐富的緩存數據過期策略。
- -SwarmCache:可作為集群範圍內的緩存,不支持Hibernate的查詢緩存。
- -JBossCache:可作為集群範圍內的緩存,支持事務並發訪問策略。支持Hibernate的查詢緩存。
二級緩存的事務隔離級別
transactional(事務型):
僅在受管理的環境中適用
提供Repeatable Read事務隔離級別
適用經常被讀,很少修改的數據
可以防止臟讀和不可重復讀的並發問題
緩存支持事務,發生異常的時候,緩存也能夠回滾
read-write(讀寫型);
提供Read Committed事務隔離級別
在非集群的環境中適用
適用經常被讀,很少修改的數據
可以防止臟讀
更新緩存的時候會鎖定緩存中的數據
nonstrict-read-write(非嚴格讀寫型):
適用極少被修改,偶爾允許臟讀的數據(兩個事務同時修改數據的情況很少見)
不保證緩存和數據庫中數據的一致性
為緩存數據設置很短的過期時間,從而盡量避免臟讀
不鎖定緩存中的數據
read-only(只讀型):
適用從來不會被修改的數據(如參考數據)
在此模式下,如果對數據進行更新操作,會有異常
事務隔離級別低,並發性能高
在集群環境中也能完美運作
為了把這些第三方緩存插件集成到Hibernate中,Hibernate提供了org.hibernate.cache.CacheProvider接口
它是緩存插件與Hibernate之間的適配器。Hibernate為以上四個緩存插件提供了內置的適配器實現類。
如果需要使用其他的緩存插件,只需要為這個插件提供實現了接口的類即可。
使用二級緩存
配置二級緩存
1.打開二級緩存
2.選擇需要使用的二級緩存的持久化類,設置二級緩存的並發訪問策略。
3.選擇合適的緩存插件,配置緩存插件的配置文件。
我們演示使用EHCache插件
(1)先導包
(2)在hibernate.cfg.xml中配置使用二級緩存
<property name="hibernate.cache.use_second_level_cache">true</property>
(3)配置使用EHcache的實現類
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.EhCacheRegionFactory</property>
Hibernate允許配置類和集合上設置二級緩存。還可以設置查詢緩存。
(一)在類上設置二級緩存
在hibernate.cfg.xml的<mapping>元素後面配置
<!--usage設置隔離級別,class設置哪個類--> <class-cache usage="read-only" class="com.cad.domain.Customer"/>
測試一下是否對象存到了二級緩存
public class Demo { private Session session; @Test public void test() { //讀取配置文件 Configuration conf=new Configuration().configure(); //根據配置創建factory SessionFactory sessionfactory=conf.buildSessionFactory(); session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); //獲取對象,打印select語句 Customer c1=session.get(Customer.class, 7); //清除一級緩存 session.clear(); //再獲取對象,沒有打印select語句,說明對象存放在了二級緩存中 Customer c2=session.get(Customer.class, 7); ts.commit(); session.close(); sessionfactory.close(); } }
(二) 在集合上設置二級緩存區
要把集合中的對象也給設置二級緩存區。
<class-cache usage="read-only" class="com.cad.domain.Customer"/> <class-cache usage="read-only" class="com.cad.domain.Order"/> <!--collection設置對象中的集合--> <collection-cache usage="read-only" collection="com.cad.domain.Customer.orders"/>
測試一下
public class Demo { private Session session; @Test public void test() { //讀取配置文件 Configuration conf=new Configuration().configure(); //根據配置創建factory SessionFactory sessionfactory=conf.buildSessionFactory(); session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); //打印select語句 Customer c1=session.get(Customer.class, 7); for(com.cad.domain.Order o:c1.getOrders()){ System.out.println(o.getName()); } //清空緩沖區 session.clear(); //再查找,不打印,說明集合中的對象都被放到了二級緩存中 Customer c2=session.get(Customer.class, 7); for(com.cad.domain.Order o:c2.getOrders()){ System.out.println(o.getName()); } ts.commit(); session.close(); sessionfactory.close(); } }
hibernate學習(緩存)