一、hibernate緩存簡介
緩存的范圍分為3類:
1.事務范圍(單Session即一級緩存)
事務范圍的緩存只能被當前事務訪問,每個事務都有各自的緩存,緩存內的數據通常采用相互關聯的對象形式.緩存的生命周期依賴於事務的生命周期,只有當事務結束時,緩存的生命周期才會結束.事務范圍的緩存使用內存作為存儲介質,一級緩存就屬於事務范圍.
2.應用范圍(單SessionFactory即二級緩存)
應用程序的緩存可以被應用范圍內的所有事務共享訪問.緩存的生命周期依賴於應用的生命周期,只有當應用結束時,緩存的生命周期才會結束.應用范圍的緩存可以使用內存或硬盤作為存儲介質,二級緩存就屬於應用范圍.
3.集群范圍(多SessionFactory)
在集群環境中,緩存被一個機器或多個機器的進程共享,緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致,緩存中的數據通常采用對象的松散數據形式.
一級緩存(session):內部緩存
事務范圍:緩存只能被當前事務訪問。緩存的生命周期依賴於事務的生命周期,當事務結束時,緩存也就結束生命周期。
二級緩存(sessionFactory):
緩存被應用范圍內的所有事務共享。 這些事務有可能是並發訪問緩存,因此必須對緩存進行更新。緩存的生命周期依賴於應用的生命周期,應用結束時, 緩存也就結束了生命周期,二級緩存存在於應用范圍。集群范圍:在集群環境中,緩存被一個機器或者多個機器的進程共享。緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致性, 緩存中的數據通常采用對象的松散數據形式,二級緩存也存在與應用范圍。
二、二級緩存如何工作的
Hibernate的二級緩存同一級緩存一樣,也是針對對象ID來進行緩存。所以說,二級緩存的作用范圍是針對根據ID獲得對象的查詢。
● 在執行各種條件查詢時,如果所獲得的結果集為實體對象的集合,那么就會把所有的數據對象根據ID放入到二級緩存中。
● 當Hibernate根據ID訪問數據對象的時候,首先會從Session一級緩存中查找,如果查不到並且配置了二級緩存,那么會從二級緩存中查找,如果還查不到,就會查詢數據庫,把結果按照ID放入到緩存中。
● 刪除、更新、增加數據的時候,同時更新緩存。
與Hibernate一級緩存Session范圍相對的是SessionFactory范圍的二級緩存,SessionFactory也提供了相應的緩存機制。SessionFactory緩存可以依據功能和目的的不同而划分為內置緩存和外置緩存。
SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句,映射元數據是映射文件中數據的副本,而預定義SQL語句是在Hibernate初始化階段根據映射元數據推導出來的。SessionFactory的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。
SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啟用這個插件。外置緩存的數據是數據庫數據的副本,外置緩存的介質可以是內存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的二級緩存。
Hibernate的二級緩存的實現原理與一級緩存是一樣的,也是通過以ID為key的Map來實現對對象的緩存。
二級緩存是緩存實體對象的,由於Hibernate的二級緩存是作用在SessionFactory范圍內的,因而它比一級緩存的范圍更廣,可以被所有的Session對象所共享。
在通常情況下會將具有以下特征的數據放入到二級緩存中:
● 很少被修改的數據。
● 不是很重要的數據,允許出現偶爾並發的數據。
● 不會被並發訪問的數據。
● 常量數據。
● 不會被第三方修改的數據
而對於具有以下特征的數據則不適合放在二級緩存中:
● 經常被修改的數據。
● 財務數據,絕對不允許出現並發。
● 與其他應用共享的數據。
在這里特別要注意的是對放入緩存中的數據不能有第三方的應用對數據進行更改(其中也包括在自己程序中使用其他方式進行數據的修改,例如,JDBC),因為那樣Hibernate將不會知道數據已經被修改,也就無法保證緩存中的數據與數據庫中數據的一致性。
常見的緩存組件
在默認情況下,Hibernate會使用EHCache作為二級緩存組件。但是,可以通過設置hibernate.cache.provider_class屬性,指定其他的緩存策略,該緩存策略必須實現org.hibernate.cache.CacheProvider接口。
通過實現org.hibernate.cache.CacheProvider接口可以提供對不同二級緩存組件的支持,此接口充當緩存插件與Hibernate之間的適配器。
組件 |
Provider類 |
類型 |
集群 |
查詢緩存 |
Hashtable |
org.hibernate.cache.HashtableCacheProvider |
內存 |
不支持 |
支持 |
EHCache |
org.hibernate.cache.EhCacheProvider |
內存,硬盤 |
不支持 |
支持 |
OSCache |
org.hibernate.cache.OSCacheProvider |
內存,硬盤 |
支持 |
支持 |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
集群 |
支持 |
不支持 |
JBoss TreeCache |
org.hibernate.cache.TreeCacheProvider |
集群 |
支持 |
支持 |
Hibernate已經不再提供對JCS(Java Caching System)組件的支持了。
集群緩存的概念:
當一台服務器上的執行了update方法修改了一條數據,那么只有這一台服務器上的二級緩存會同步於數據庫,其他服務器上的二級緩存里面這條數據就沒意義了。這個時候用OSCache緩存機制,只要有一台服務器上有數據修改了,馬上會從配置文件中找到配置好的其他服務器IP地址,進行廣播,告訴他們我這條數據修改了,你們也更新同步一下。(是不是有點像手機上微博的推送功能)
三、二級緩存配置步驟
1.添加jar包,如ehcache-core.jar
2.在hibernate.cfg.xml開啟二級緩存
Xml代碼
<hibernate-configuration> <session-factory> ...... <!-- 開啟二級緩存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 啟動"查詢緩存"如果想緩存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法獲得的數據結果集,必須配置此項--> <property name="hibernate.cache.use_query_cache">true</property> <!-- 設置二級緩存插件EHCache的Provider類--> <!-- <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> --> <!-- 二級緩存區域名的前綴 --> <!--<property name="hibernate.cache.region_prefix">test</property>--> <!-- 高速緩存提供程序 --> <property name="hibernate.cache.region.factory_class"> net.sf.ehcache.hibernate.EhCacheRegionFactory </property> <!-- Hibernate4以后都封裝到org.hibernate.cache.ehcache.EhCacheRegionFactory --> <!-- 指定緩存配置文件位置 --> <!-- <property name="hibernate.cache.provider_configuration_file_resource_path"> ehcache.xml </property> --> <!-- 強制Hibernate以更人性化的格式將數據存入二級緩存 --> <property name="hibernate.cache.use_structured_entries">true</property> <!-- Hibernate將收集有助於性能調節的統計數據 --> <property name="hibernate.generate_statistics">true</property> ...... </session-factory> </hibernate-configuration>
此處配置的是
<!-- 指定二級緩存的外部實現類 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
3.ehcache.xml配置
cache參數詳解:
● name:指定區域名
● maxElementsInMemory :緩存在內存中的最大數目
● maxElementsOnDisk:緩存在磁盤上的最大數目
● eternal :設置是否永遠不過期
● overflowToDisk : 硬盤溢出數目
● timeToIdleSeconds :對象處於空閑狀態的最多秒數后銷毀
● timeToLiveSeconds :對象處於緩存狀態的最多秒數后銷毀
● memoryStoreEvictionPolicy:緩存算法,有LRU(默認)、LFU、LFU
關於緩存算法,常見有三種:
● LRU:(Least Rencently Used)新來的對象替換掉使用時間算最近很少使用的對象
● LFU:(Least Frequently Used)替換掉按命中率高低算比較低的對象
● LFU:(First In First Out)把最早進入二級緩存的對象替換掉
Xml代碼
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!--如果緩存中的對象存儲超過指定的緩存數量的對象存儲的磁盤地址--> <diskStore path="D:/ehcache"/> <!-- 默認cache:如果沒有對應的特定區域的緩存,就使用默認緩存 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="false"/> <!-- 指定區域cache:通過name指定,name對應到Hibernate中的區域名即可--> <cache name="cn.javass.h3test.model.UserModel" eternal="false" maxElementsInMemory="100" timeToIdleSeconds="1200" timeToLiveSeconds="1200" overflowToDisk="false"> </cache> </ehcache>
4.實體類的映射文件配置,如user.hbm.xml的配置
<?xml version="1.0" encoding='UTF-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping> <class> <!-- 設置該持久化類的二級緩存並發訪問策略 read-only read-write nonstrict-read-write transactional--> <class name="cn.java.test.model.User" table="TBL_USER"> <cache usage="read-write"/> ...... </class> </hibernate-mapping>
其中二級緩存並發訪問策略有三種:
a.在user.hbm.xml中添加<cache usage="read-write"/>
b.在hibernate.cfg.xml中添加<class-cache usage="read-write" class="com.hb.test.User"/>
c.用Hibernate注解配置緩存實體類
Java代碼
@Entity @Table @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class User implements Serializable { private static final long serialVersionUID = -5121812640999313420L; private Integer id; private String name; ...... }
設置二級緩存策略
● READ_ONLY:實體只讀緩存
只讀緩存不允許更新,將報錯Can't write to a readonly object。
允許新增,(從2.0以后新增直接添加到二級緩存)
Java代碼
//確保數據庫中有標識符為1的FarmModel FarmModel farm = (FarmModel) session.get(FarmModel.class, 1); //如果修改將報錯,只讀緩存不允許修改 //farm.setName("aaa");
● NONSTRICT_READ_WRITE:實體非嚴格讀/寫緩存
允許更新,更新后緩存失效,需再查詢一次。
允許新增,新增記錄自動加到二級緩存中。
整個過程不加鎖。
● READ_WRITE:實體讀/寫緩存
允許更新,更新后自動同步到緩存。
允許新增,新增記錄后自動同步到緩存。
保證read committed隔離級別及可重復讀隔離級別(通過時間戳實現)
整個過程加鎖,如果當前事務的時間戳早於二級緩存中的條目的時間戳,說明該條目已經被別的
事務修改了,此時重新查詢一次數據庫,否則才使用緩存數據,因此保證可重復讀隔離級別。
讀寫緩存和不嚴格讀寫緩存在實現上的區別在於,讀寫緩存更新緩存的時候會把緩存里面的數據換成一個鎖
● TRANSACTIONAL:實體事務緩存
緩存支持事務,發生異常的時候,緩存也能夠回滾,只支持jta環境
● Collection集合緩存
Java代碼
<hibernate-mapping> <class name="cn.java.test.model.UserModel" table="TBL_USER"> <cache usage="read-write" /> <set name="farms" cascade="all" inverse="true" lazy="false"> <cache usage="read-write"/> <key column="fk_user_id"/> <one-to-many class="cn.java.test.model.FarmModel"/> </set> </class> </hibernate-mapping>
和實體並發策略有相同含義;
但集合緩存只緩存集合元素的標識符,在二級緩存中只存放相應實體的標識符,然后再通過標識符去二級緩存查找相應的實體最后組合為集合返回
Collection的緩存和前面查詢緩存的list一樣,也是只保持一串id,但它不會因為這個表更新過就失效,一個collection緩存僅在這個collection里面的元素有增刪時才失效。
這樣有一個問題,如果你的collection是根據某個字段排序的,當其中一個元素更新了該字段時,導致順序改變時,collection緩存里面的順序沒有做更新
高速緩存區域
Hibernate在不同的高速緩存區域保存不同的類(實體)/集合,如果不配置區域默認都保存到“默認緩存”(defaultCache)中。
●每一個區域可以設置過期策略、緩存條目大小等等。
●對於類緩存,默認區域名是全限定類名,如cn.javass.h3test.model.UserModel。
●對於集合而言,默認區域名是全限定類名+屬性名,如cn.javass.….UserModel.farms。
●可通過hibernate.cache.region_prefix指定特定SessionFactory的區域前綴,如前綴是h3test,則如類緩存的區域名就是h3test.cn.javass.h3test.model.UserModel。如果應用程序使用多個SessionFactory這可能是必須的。
可通過<cache usage="read-write" region="區域名"/>自定義區域名,不過默認其實就可以了。
5.測試
package com.lanhuigu.hibernate.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.lanhuigu.hibernate.entity.Customer; public class TestHibernate { public static void main(String[] args) { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session1 = sessionFactory.openSession(); Transaction tr1 = session1.beginTransaction(); //加載一個OID為1的對象 Customer customer1 = (Customer) session1.get(Customer.class, new Long(1)); System.out.println("session1"+customer1.getName()); tr1.commit(); session1.close(); //加載同樣OID為2的對象 Session session2 = sessionFactory.openSession(); Transaction tr2 = session2.beginTransaction(); Customer customer2 = (Customer) session2.get(Customer.class, new Long(1)); System.out.println("session2"+customer2.getName()); tr2.commit(); session2.close(); } }
四、緩存管理
如果開啟了二級緩存,由於session是共享二級緩存的,只要緩存里面有要查詢的對象,就不會向數據庫發出sql,如果在二級緩存里沒有找到需要的數據就會發出sql語句去數據庫拿。
一級緩存的管理:
● evit(Object obj)將指定的持久化對象從一級緩存中清除,釋放對象所占用的內存資源,指定對象從持久化狀態變為脫管狀態,從而成為游離對象.
● clear()將一級緩存中的所有持久化對象清除,釋放其占用的內存資源
● contains(Object obj)判斷指定的對象是否存在於一級緩存中.
● flush()刷新一級緩存區的內容,使之與數據庫數據保持同步.
二級緩存的管理:
● evict(Class arg0, Serializable arg1)將某個類的指定ID的持久化對象從二級緩存中清除,釋放對象所占用的資源.
Java代碼
sessionFactory.evict(Customer.class, new Integer(1));
evict(Class arg0)將指定類的所有持久化對象從二級緩存中清除,釋放其占用的內存資源
Java代碼
sessionFactory.evict(Customer.class);
evictCollection(String arg0)將指定類的所有持久化對象的指定集合從二級緩存中清除,釋放其占用的內存資源.
Java代碼
sessionFactory.evictCollection("Customer.orders");
設置一級緩存和二級緩存的交互權限
Java代碼
session = HibernateUtils.getSession(); session.beginTransaction(); //僅向二級緩存讀數據,而不向二級緩存寫數據,這里load的數據就不會放入二級緩存,下次再查還是會去數據庫拿 session.setCacheMode(CacheMode.GET); //只向二級緩存寫數據,而不從二級緩存讀數據 //session.setCacheMode(CacheMode.PUT); //不與二級緩存交互 //session.setCacheMode(CacheMode.IGNORE); //可以與二級緩存交互 //session.setCacheMode(CacheMode.NORMAL); Student student = (Student)session.load(Student.class, 1); session.getTransaction().commit();