Hibernate一級緩存 & 二級緩存
一、一級緩存Session的操作與維護
1.Hibernate對象的三種狀態: transient, persistent, detached
1) transient:瞬時狀態
利用new關鍵字創建的對象,沒有與Hibernate實施交互的,也無法保證與數據庫中某條記錄對應的對象被稱為瞬時狀態,也就是生命周期非常短的意思,因為沒有任何組件管理的對象非常容易被Java虛擬機回收。
例:Customer cus = new Customer();//瞬時狀態對象
2) persistent:持久化狀態
將瞬時狀態的對象保存到Hibernate緩存中,受Hibernate管理的對象被稱為持久化狀態Hibernate在調用flush, close ,clear方法的時候會清理緩存保證持久化對象與底層數據庫的同步,所有的持久化對象,Hibernate均會為它設置一個唯一性標識符保證其在緩存中的唯一性,這個標識符可能是hashCode,帶主鍵查詢的sql語句或者其他保證唯一的字符。
save(new object),update(new object),saveorupdate(new object),persisnt(new object)
可以將一個瞬時狀態轉變為持久化對象
save(new object), persistent (new object):利用select sql where id 加載一個對象
如果加載成功那么直接顯示異常(插入不允許帶相同標識符的組件在緩存中存在)
update(new object):不執行SQL語句,直接將對象加載入內存做更新準備
如果緩存中已經擁有一個同標識符的組件,那么顯示異常,因為update只能做更新處理
無法處理兩個完全一致的對象(merge(new object)可以克服這個問題)
saveorupdate(new object):利用select sql where id 加載一個對象
如果加載成功那么直接更新,否則插入
注意:業務層/表示層/應用層對于持久化狀態對象的更改都會引起Hibernate的同步處理(反映到數據庫中)
3) detached:游離托管狀態
緩存中已經失去該對象或者該對象的標識符,雖然對象仍然存在,對象也和數據表中的記錄有對應但是由于失去了Hibernate的控制,因此該對象被稱為游離托管狀態。
注意:業務層/表示層/應用層對于托管狀態對象的更改不會會引起Hibernate的同步處理
游離托管狀態的對象是指:已經經過Hibernate管理后,
因為Session.delete(),Session.close(),Session.clear(),Session.evict()
方法的執行而失去標識符的,所以托管狀態和瞬時狀態的對的區別是是否受過Hibernate的管理,數據表中是否有對應的記錄,托管對象只有通過lock(),update(),saveorupdate()被重新加入緩存變成持久化對象才能實施數據同步
2、特殊方法:persisnt(),merge()
persisnt和save方法是差不多的,唯一的區別是針對sqlserver的identity字段的處理save為了保證持久化標識符,所以會在save的過程中就直接執行insert into....select identity();以獲取最新的主鍵信息。
persisnt執行的時機是Transaction.commit(),Session.clear(),Session.flush()的時候
merge()與update()類似,但是區別是對于瞬時狀態的理解。
假設現在有一個瞬時狀態的new Customer(1),同時Session利用get(),load()方法
產生一個持久化狀態的對象Session.get(Customer.class,1),這個時候使用update方法會拋出異常,而merge會將瞬時狀態Customer中的屬性復制到持久化狀態的Customer
中。
3、查詢對于一級緩存的使用
1)Session.get, Session.load方法
Session.get:迫切加載
第一查詢:查詢一級緩存Session緩存,如果沒有那么查詢二級緩存SessionFactory緩存(如果沒有配置,此步省略),如果找不到那么執行Select…..From…..Where id = ?,如果數據被查到那么將查到的數據封裝到對象中,對象被分別保存在一級緩存Session緩存中,和二級緩存SessionFactory緩存(如果沒有配置,此步省略)。
第二查詢:查詢一級緩存Session緩存,,如果找不到那么執行Select…..From…..Where id = ?,如果數據被查到那么將查到的數據封裝到對象中,對象被保存在一級緩存Session緩存中。
Session.load:延遲加載
Session.load認為加載始終是成功的,所以它始終不會與數據庫交互,因為load認為查詢一定會成功,因此只有當需要訪問被加載實體屬性的時候,Session.load才會按照Session.get的查詢軌跡實施搜尋,不同的是Session.load始終會查詢一,二級緩存再執行SQL語句
如果SQL語句無法返回對象,那么Session.load直接拋出異常。
2)Query.list, Query.iterator方法
Query.list:查詢過程中不會讀取任何緩存尤其是一級緩存,而是直接執行SQL語句,SQL語句有Query的HQL轉換而得,執行結果會被保存在一級緩存中。
我們可以通過ehcache緩存組件為Hibernate配置查詢緩存(query cache[hibernate 3.x以上版本]),這Query.list會每次查詢的過程中先訪問二級緩存中的查詢緩存,如果沒有再執行SQL語句,查詢返回的結果會分別保存在一,二級緩存中。
Query.iterator:與Query.list的不同在于,它會每次訪問均查詢一級緩存,但是
Query.iterator記載數據的方式不是完整的SQL語句,而是N+1條SQL語句
例如:Query.list 對于一張5條記錄的表的檢索方式是Select ….. From Customer
而Query.iterator的檢索方式是:
執行5句Select ….. From Customer where id = ?(單個對象實施記載)
二、二級緩存SessionFactory 的配置與測試
1. 利用ehcache配置Second level cache 和 Query cache
在src目錄下建立ehcache.xml文件內容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache>
<!—
設置對象鈍化目錄,二級緩存中長時間不使用或者超時的對象
會被保存在當前目錄\java\io\tmpdir目錄中,這樣可以節省空間
-->
<diskStore path="java.io.tmpdir"/>
</ehcache>
<!-- 默認二級緩存工作模式
maxElementsInMemory:緩存中最大對象數
eternal:緩存中的對象是否永久保留
timeToIdleSeconds:多少毫秒后可以考慮一個對象的放入鈍化目錄中
timeToLiveSeconds:多少毫秒后可以考慮一個對象從激活狀態-閑置狀態
overflowToDisk:是否允許將閑置對象鈍化入硬盤
diskPersistent:鈍化后該對象是否允許永久無法反鈍化
diskExpiryThreadIntervalSeconds:鈍化線程間隔處理時間(毫秒)
memoryStoreEvictionPolicy:鈍化對象選擇模型(LRU:使用最少的先鈍化技術)
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<!-- 默認二級查詢緩存工作模式-->
<cache name="org.hibernate.cache.StandardQueryCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="0"
overflowToDisk="true"/>
<!-- 為時間郵差準備的二級查詢緩存工作模式
主要有同步數據方法調用,例如lock(LockMode.READ)
-->
<cache name="org.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000"
eternal="true"
timeToIdleSeconds="1800"
timeToLiveSeconds="0"
overflowToDisk="true"/>
2. 配置h 注意:緩存監控方法如果要能夠執行,需要在hibernate.cfg.xml中設置以下配置
ibernate.cfg..xml
<!-- 使用ehcache第三方緩存技術 -->
<property name="cache.provider_class">
net.sf.ehcache.hibernate.EhCacheProvider
</property>
<!-- 啟用運行查詢緩存 -->
<property name="cache.use_query_cache">true</property>
<!-- 啟用二級緩存(SessionFactory緩存) -->
<property
name="cache.use_second_level_cache">true</property> 3. 為需要二級緩存管理的對象設置標識列(*.hbm.xml)
<hibernate-mapping>
<class name="cn.newtouch.myhibernate.po.Customer" schema="HIBERNATE" table="CUSTOMER">
<!— 表示Customer需要受到緩存管理 -->
<cache usage="read-write"/>
<id name="id" type="java.lang.Long">
<column name="ID" precision="8" scale="0"/>
<generator class="assigned"/>
</id>
4. 測試實體查詢對于二級緩存的執行模式:
public void testSecondLevelCacheForEntityQuery() {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session cnn = factory.openSession();
SessionStatistics ss = cnn.getStatistics();
Statistics s = factory.getStatistics();
cnn.get(Customer.class, 1l);
System.out.println("FIRST GET:" +ss.getEntityCount());
System.out.println("FIRST GET PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("FIRST GET MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("FIRST GET HIT:" +
s.getSecondLevelCacheHitCount());
cnn.get(Customer.class, 1l);
System.out.println("SECOND GET:" +ss.getEntityCount());
System.out.println("SECOND GET PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("SECOND GET MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("HIT:" +
s.getSecondLevelCacheHitCount());
cnn.clear();
System.out.println("BEFORE CLEAR:" + ss.getEntityCount());
System.out.println("BEFORE CLEAR PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("BEFORE CLEAR MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("BEFORE CLEAR HIT:" +
s.getSecondLevelCacheHitCount());
Session anotherSession = factory.openSession();
anotherSession.get(Customer.class, 1l);
System.out.println("ANOTHER SESSION:" +
ss.getEntityCount());
System.out.println("ANOTHER SESSION PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("ANOTHER SESSION MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("ANOTHER SESSION HIT:" +
s.getSecondLevelCacheHitCount());
anotherSession.clear();
}
測試二級緩存的結果是
FIRST GET:1 FIRST GET PUT:1 FIRST GET MISS:1 FIRST GET HIT:0 ======================================= SECOND GET:1 SECOND GET PUT:1 SECOND GET MISS:1 SECOND GET HIT:0 ======================================= BEFORE CLEAR:0 BEFORE CLEAR PUT:1 BEFORE CLEAR MISS:1 BEFORE CLEAR HIT:0 ======================================= ANOTHER SESSION:0 ANOTHER SESSION PUT:1 ANOTHER SESSION MISS:1 ANOTHER SESSION HIT:1
5. 測試HQL查詢對于二級緩存的執行模式
public void testSecondLevelCacheForQueries() {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session cnn = factory.openSession();
SessionStatistics ss = cnn.getStatistics();
Statistics s = factory.getStatistics();
Query query =
cnn.createQuery("From Customer A");
query.setCacheable(true);
query.setCacheRegion(
"cn.newtouch.myhibernate.po.Customer");
query.list();
System.out.println("FIRST Query:" +ss.getEntityCount());
System.out.println("Level's PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("Level's MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("Level's HIT:" +
s.getSecondLevelCacheHitCount());
System.out.println("Queries's PUT:" +
s.getQueryCachePutCount());
System.out.println("Queries's MISS:" +
s.getQueryCacheMissCount());
System.out.println("Queries's HIT:" +
s.getQueryCacheHitCount());
System.out.println("=======================================");
query = cnn.createQuery("From Customer A");
query.setCacheable(true);
query.setCacheRegion(
"cn.newtouch.myhibernate.po.Customer");
query.list();
System.out.println("SECOND Query:" +ss.getEntityCount());
System.out.println("Level's PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("Level's MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("Level's HIT:" +
s.getSecondLevelCacheHitCount());
System.out.println("Queries's PUT:" +
s.getQueryCachePutCount());
System.out.println("Queries's MISS:" +
s.getQueryCacheMissCount());
System.out.println("Queries's HIT:" +
s.getQueryCacheHitCount());
System.out.println("=======================================");
cnn.clear();
System.out.println("BEFORE CLEAR:" +ss.getEntityCount());
System.out.println("Level's PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("Level's MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("Level's HIT:" +
s.getSecondLevelCacheHitCount());
System.out.println("Queries's PUT:" +
s.getQueryCachePutCount());
System.out.println("Queries's MISS:" +
s.getQueryCacheMissCount());
System.out.println("Queries's HIT:" +
s.getQueryCacheHitCount());
System.out.println("=======================================");
Session anotherSession = factory.openSession();
query =
anotherSession.createQuery("From Customer A");
query.setCacheable(true);
query.setCacheRegion(
"cn.newtouch.myhibernate.po.Customer");
query.list();
System.out.println("ANOTHER SESSION:" +ss.getEntityCount());
System.out.println("Level's PUT:" +
s.getSecondLevelCachePutCount());
System.out.println("Level's MISS:" +
s.getSecondLevelCacheMissCount());
System.out.println("Level's HIT:" +
s.getSecondLevelCacheHitCount());
System.out.println("Queries's PUT:" +
s.getQueryCachePutCount());
System.out.println("Queries's MISS:" +
s.getQueryCacheMissCount());
System.out.println("Queries's HIT:" +
s.getQueryCacheHitCount());
System.out.println("=======================================");
anotherSession.clear();
}
測試二級緩存的結果是:
FIRST Query:2 Level's PUT:2 Level's MISS:0 Level's HIT:0 Queries's PUT:1 Queries's MISS:1 Queries's HIT:0 ======================================= SECOND Query:2 Level's PUT:2 Level's MISS:0 Level's HIT:0 Queries's PUT:1 Queries's MISS:1 Queries's HIT:1 ======================================= BEFORE CLEAR:0 Level's PUT:2 Level's MISS:0 Level's HIT:0 Queries's PUT:1 Queries's MISS:1 Queries's HIT:1 ======================================= ANOTHER SESSION:0 Level's PUT:2 Level's MISS:0 Level's HIT:2 Queries's PUT:1 Queries's MISS:1 Queries's HIT:2
注意:緩存監控方法如果要能夠執行,需要在hibernate.cfg.xml中設置以下配置
<property name="generate_statistics">true</property>
以下情況適合使用二級緩存:
1、很少被修改的數據
2、不是很重要的數據,允許出現偶爾并發的數據
3、不會被并發訪問的數據
4、參考數據,指的是供應用參考的常量數據,它的實例數目有限,它的實例會被許多其他類的實例引用,實例極少或者從來不會被修改。