Hibernate緩存機制:
緩存范圍:
應用程序中根據緩存的范圍,可以將緩存分為三類:
(1)事務范圍緩存(單Session,即一級緩存)
事務范圍的緩存只能被當前事務訪問,每個事務都有各自的緩存。緩存的生命周期依賴於事務的生命周期:當事務結束時,緩存的生命周期也會結束。事務范圍的緩存使用內存作為存儲介質。Hibernate中的一級緩存就屬於事務范圍。
(2)應用范圍緩存(單SessionFactory,即二級緩存)
應用范圍的緩存可以被應用程序內的所有事務共享訪問。緩存的生命周期依賴於應用的生命周期,當應用結束時,緩存的生命周期同時結束。應用范圍的緩存可以使用內存或硬盤作為存儲介質。Hibernate的二級緩存就屬於應用范圍。
(3)集群范圍緩存(多SessionFactory)
在集群環境中,緩存被一個機器或多個機器的進程共享,緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致性,緩存中的數據通常采用對象的松散數據形式。有些Hibernate的二級緩存第三方插件支持集群范圍緩存。
(1) 一級緩存:
一級緩存,就是Session緩存,其實就是內存中的一塊空間,在這個內存空間存放了相互關聯的Java對象。Session緩存是事務級緩存。伴隨着事務的開啟而開啟,伴隨着事務的關閉而關閉。Session緩存由Hibernate進行管理。Session緩存,是Hibernate內置的。是不能被程序員取消的。即,只要使用Hibernate,就一定要使用,更確切地說,就一定在使用Session緩存。
當程序調用Session的load()方法、get()方法、save()方法、saveOrUpdate()方法、update()方法或查詢接口方法時,Hibernate會對實體對象進行緩存。當通過load()或get()方法查詢實體對象時,Hibernate會首先到緩存中查詢,在找不到實體對象的情況下,Hibernate才會發出SQL語句到DB中查詢。從而提高了Hibernate的使用效率。
一級緩存相關的方法:
evict(Object o):從Session中刪除指定對象
clear():無參數,將Session緩存清空
contains(Object o):判斷指定對象是否在Session中存在
flush():無參數,將Session中對象狀態同步到DB中
快照(引用自北京動力節點):

代碼說明:
1 @Test 2 public void test01_SQL() { 3 //1. 獲取Session 4 Session session = HbnUtils.getSession(); 5 try { 6 //2. 開啟事務 7 session.beginTransaction(); 8 //3. 操作 9 //session.get的時候,hibernate就將數據庫中的數據加載到session緩存中,同時也會備份到快照中 10 Student student = session.get(Student.class, 2); 11 student.setName("n_2"); 12 session.update(student); 13 //4. 事務提交 14 //commit是唯一的同步時間點,當程序運行到同步時間點時, 15 //hibernate先判斷session緩存中的數據和快照中的數據是否相同, 16 //如果不相同,則更新數據庫,如果相同,則不更新數據庫, 17 //以上,兩種情況,無論是否寫了update,都成立 18 session.getTransaction().commit(); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 //5. 事務回滾 22 session.getTransaction().rollback(); 23 } 24 }
Session的刷新與同步:
Session的刷新是指,Session緩存中的數據的更新。Session的同步是指,將Session緩存中的數據同步更新到DB中。執行同步的時間點只有一個:事務的提交。
當代碼中執行了對Session中現有數據的修改操作,即update()與delete()語句后,Session緩存並不會馬上刷新,即並不會馬上執行update與delete的SQL語句,而是在某個時間點到來時,才會刷新緩存,更新緩存中的數據。刷新的時間點主要有三個:
(1)執行Query查詢
(2)執行session.flush()
(3)執行事務的提交
通過Session的setFlushMode()方法,可以設置緩存刷新模式:

注意:增刪改操作,當刷新時間點到來時是否馬上進行緩存更新,各自情況還是不同的。
刪除操作,一到達刷新時間點,馬上執行delete語句,更新Session中數據;
更新操作,到達刷新時間點后,是否馬上執行update語句,更新Session中數據,還要看該修改內容是否與快照一致若一致,則執行,否則,則不執行;
插入操作,無需等到刷新時間點的到達,見到save()后馬上執行insert語句。因為插入操作不是修改Session中已經的存在數據,而是給Session中添加數據。
(2) 二級緩存:
二級緩存是SessionFactory級的緩存,其生命周期與SessionFactory一致。SessionFactory緩存可以依據功能和目的的不同而划分為內置緩存和外置緩存。
SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句,映射元數據是映射文件中數據的副本,而預定義SQL語句是在Hibernate初始化階段根據映射元數據推導出來的SQL。SessionFactory的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。
SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啟用這個插件。外置緩存的數據是數據庫數據的副本,外置緩存的介質可以是內存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的二級緩存。
Hibernate本身只提供了二級緩存的規范,但並未實現,故需要第三方緩存產品的支持。常用的二級緩存第三方插件有:
EHCache、Memcached、OSCache、SwarmCache、JBossCache等。這些插件的功能各有側重,各有特點。

二級緩存的執行:當Hibernate根據ID訪問數據對象時,首先會從一級緩存Session中查找。若查不到且配置了二級緩存,則會從二級緩存中查找;若還查不到,就會查詢數據庫,把結果按照ID放入到緩存中。執行增、刪、改操作時,會同步更新緩存。
二級緩存內容分類,根據緩存內容的不同,可以將Hibernate二級緩存分為四類:
(1)類緩存:緩存對象為實體類對象
(2)集合緩存:緩存對象為集合類對象
(3)更新時間戳緩存:
(4)查詢緩存:緩存對象為查詢結果
二級緩存的並發訪問策略:
事務型(transactional):隔離級別最高,對於經常被讀但很少被改的數據,可以采用此策略。因為它可以防止臟讀與不可重復讀的並發問題。發生異常的時候,緩存也能夠回滾(系統開銷大)。
讀寫型(read-write):對於經常被讀但很少被改的數據,可以采用此策略。因為它可以防止臟讀的並發問題。更新緩存的時候會鎖定緩存中的數據。
非嚴格讀寫型(nonstrict-read-write):不保證緩存與數據庫中數據的一致性。對於極少被改,並且允許偶爾臟讀的數據,可采用此策略。不鎖定緩存中的數據。
只讀型(read-only):對於從來不會被修改的數據,可使用此策略。
二級緩存管理相關的方法:
與二級緩存管理相關的方法,一般都定義在Cache接口中。而Cache對象的獲取,需要通過SessionFactory的getCache()方法:
Cache cache = sessionFactory.getCache();
二級緩存的配置(應用EHCache插件):
(1)導入ehcache的jar包,略
(2)在hibernate.cfg.xml中的<session-factory>元素中加入如下內容,開啟二級緩存和注冊二級緩存區域工廠:
1 <!-- 開啟二級緩存 --> 2 <property name="hibernate.cache.use_second_level_cache"> 3 true 4 </property> 5 <!-- 注冊二級緩存區域工廠 --> 6 <property name="hibernate.cache.region.factory_class"> 7 org.hibernate.cache.ehcache.EhCacheRegionFactory 8 </property>
(3)解壓EHCache的核心Jar包ehcache-core-2.4.3.jar,將其中的一個配置文件ehcache-failsafe.xml直接放到項目的src目錄下,並更名為ehcache.xml,注意其中的配置可以修改:
1 <defaultCache 2 maxElementsInMemory="10000" 3 eternal="false" 4 timeToIdleSeconds="120" 5 timeToLiveSeconds="120" 6 maxElementsOnDisk="10000000" 7 diskExpiryThreadIntervalSeconds="120" 8 memoryStoreEvictionPolicy="LRU"> 9 <persistence strategy="localTempSwap"/> 10 </defaultCache>
(4)指定緩存內容:
指定緩存內容,即指定哪個類或哪個集合要進行二級緩存。指定的位置有兩處:映射文件、主配置文件。這兩種任選其一即可。它們的效果是相同的,但各有利弊:
在映射文件中指定緩存內容,查看其類的映射文件時,一眼就可看到類為緩存類,集合為緩存集合。但,弊端是,緩存的指定位置分散,缺乏項目的整體性。
在主配置文件中指定緩存內容,可一眼看到整個項目中所有緩存類與緩存集合。但,弊端是,緩存內容的指定與類映射分離。
映射文件中指定緩存內容:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC 3 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 5 6 <hibernate-mapping package="com.tongji.beans"> 7 <class name="Country"> 8 <!-- 指定當前類為類緩存對象 --> 9 <!-- <cache usage="read-only"/> --> 10 <id name="cid"> 11 <generator class="native"/> 12 </id> 13 <property name="cname"/> 14 <set name="ministers" cascade="save-update"> 15 <!-- 指定當前集合為集合緩存對象 --> 16 <!-- <cache usage="read-only"/> --> 17 <key column="countryId"/> 18 <one-to-many class="Minister"/> 19 </set> 20 </class> 21 </hibernate-mapping>
在主配置文件中指定緩存內容:在<mapping/>標簽的后面指定類緩存與集合緩存
1 <!-- 指定類緩存 --> 2 <class-cache usage="read-only" class="com.tongji.beans.Minister" /> 3 <class-cache usage="read-only" class="com.tongji.beans.Country" /> 4 <!-- 指定集合緩存 --> 5 <collection-cache usage="read-only" 6 collection="com.tongji.beans.Country.ministers" />
補充:
(1)類緩存,緩存的是類的詳情;集合緩存,在沒有對集合中元素對應的類進行類緩存的時候,緩存的是所有元素的id。
(2)Query查詢緩存,即session.createQuery(hql)時進行緩存。說明以下三點:
(1)Query查詢的結果也會存放到一、二級緩存中
(2)Query查詢默認不會從一、二級緩存中讀取數據,但可以改變:
先在主配置文件中,開啟Query查詢總開關:<property name="hibernate.cache.use_query_cache">true</property>
查詢時的代碼:
1 @Test 2 public void test02() { 3 //1. 獲取Session 4 Session session = HbnUtils.getSession(); 5 try { 6 //2. 開啟事務 7 session.beginTransaction(); 8 //3. 操作 9 //第一次查詢 10 String hql = "from Country where cid=1"; 11 Country country1 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 12 System.out.println("第一次查詢:Country = " + country1); 13 //第二次查詢 14 Country country2 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 15 System.out.println("第二次查詢:Country = " + country2); 16 17 //將一級緩存數據清空 18 session.clear(); 19 20 //第三次查詢,從二級緩存中讀取 21 Country country3 = (Country) session.createQuery(hql).setCacheable(true).uniqueResult(); 22 System.out.println("第三次查詢:Country = " + country3); 23 //4. 事務提交 24 session.getTransaction().commit(); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 //5. 事務回滾 28 session.getTransaction().rollback(); 29 } 30 }
(3)Query查詢要從緩存中讀取數據,必須保證Query所執行的HQL語句完全相同。因為Query查詢,不僅將數據存放到了緩存中,還將HQL語句存放到了緩存中。
(4)修改時間戳:在二級緩存存放的對象中,比一級緩存中多出一個屬性,updateTimeStamp,修改時間戳。只要這個屬性發生改變,就說明有操作修改了DB中的數據,二級緩存中的該緩存對象已經不是最新數據,需要從DB中再次查詢更新。
而Query接口的executeUpdate()方法所進行的更新,可以繞過一級緩存,但會修改二級緩存中緩存對象的updateTimeStamp值,由於該值的改變,二級緩存就會通過新的查詢來更新緩存中的數據(一、二級緩存都更新了)。
