1.緩存概述
緩存(cache)在java應用程序中是一組內存中的集合示例,它保存着永久性存儲源(如硬盤上的文件或數據庫)中數據的備份,它的讀寫速度比讀寫硬盤的速度快。應用程序在運行時直接讀寫緩存中的數據,只在某些特定時刻按照緩存中的數據來同步更新數據存儲源。如果緩存中存放的數據量非常大,也會用硬盤作爲緩存的物理介質
緩存的作用就是降低應用程序直接讀寫永久性數據存儲源的頻率,從而增強應用的運行性能
緩存的實現不僅需要作爲物理介質的硬件(內存),同時還需要用於管理緩存的併發訪問和過期等策略的軟件
2.緩存範圍分類
緩存的範圍決定了緩存的聲明週期以及可以被誰訪問。總共分三類
1)事務範圍
事務範圍的緩存只能被當前事務訪問,每個事務都有各自的緩存,緩存內的數據通常採用相互關聯的對象形式.緩存的生命週期依賴於事務的生命週期,只有當事務結束時,緩存的生命週期纔會結束.事務範圍的緩存使用內存作爲存儲介質,一級緩存就屬於事務範圍.
2)應用範圍(也叫進程範圍)
應用程序的緩存可以被應用範圍內的所有事務共享訪問.緩存的生命週期依賴於應用的生命週期,只有當應用結束時,緩存的生命週期纔會結束.應用範圍的緩存可以使用內存或硬盤作爲存儲介質,二級緩存就屬於應用範圍.
3)集羣範圍
在集羣環境中,緩存被一個機器或多個機器的進程共享,緩存中的數據被複制到集羣環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致,緩存中的數據通常採用對象的鬆散數據形式.
對大多數應用來說,應該慎重地考慮是否需要使用集羣範圍的緩存,因爲訪問的速度不一定迴避直接訪問數據庫數據的速度快很多
3.緩存的併發訪問策略
當多個併發的失誤同時訪問持久化層的緩存的相同數據時,會引發起併發問題,必須採用必要的失誤隔離措施
在進程範圍或集羣範圍的緩存,會出現併發問題,因此可以設定一下四種類型的併發訪問策略,每一種策略對應一種事務隔離級別。事務型併發訪問策略是事務隔離級別最高,只讀型的隔離級別最低。事務隔離級別越高,併發性能就越低
1)事務型:僅僅在受管理環境中適用。它提供了Repeatable Read事務隔離級別。對於經常被讀但很少修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀和不可重複讀這類的併發問題。
2)讀寫型:提供了Read Committed事務隔離級別。僅僅在非集羣的環境中適用。對於經常被讀但很少修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀這類的併發問題。
3)非嚴格讀寫型:不保證緩存與數據庫中數據的一致性。如果存在兩個事務同時訪問緩存中相同數據的可能,必須爲該數據配置一個很短的數據過期時間,從而儘量避免髒讀。對於極少被修改,並且允許偶爾髒讀的數據,可以採用這種併發訪問策略。
4)只讀型:對於從來不會修改的數據,如參考數據,可以使用這種併發訪問策略。
Hibernate中的緩存
hibernate中提供兩級緩存,第一級別是Session級別的緩存,它是屬於事務範圍的緩存,第二級別的緩存是SessionFactory級別的緩存,它是屬於進出呢個範圍或集羣範圍的緩存。這一級別的緩存可以進行配置和更改,並且可以進行動態的加載和卸載。Hibernate還爲查詢結果提供了一個查詢緩存,它依賴於第二級緩存
一級緩存的管理:
Hibernate的一級緩存是由Session提供的,因此它只存在於Session的生命週期中,當程序調用save(),update(),saveorupdate()等方法 及調用查詢接口list,filter,iterate時,如session緩存中還不存在相應的對象,Hibernate會把該對象加入到一級緩存中,
當Session關閉的時候該Session所管理的一級緩存也會立即被清除
Hibernate的一級緩存是Session所內置的,不能被卸載,也不能進行任何配置
一級緩存採用的是key-value的Map方式來實現的,在緩存實體對象時,對象的主關鍵字ID是Map的key,實體對象就是對應的值。所以說,一級緩存是以實體對象爲單位進行存儲的,在訪問的時候使用的是主關鍵字ID
雖然,Hibernate對一級緩存使用的是自動維護的功能,沒有提供任何配置功能,但是可以通過Session中所提供的方法來對一級緩存的管理進行手工干預。Session中所提供的干預方法包括以下兩種
●evict() :用於將某個對象從Session的一級緩存中清除
evict()方法適用於以下二種情況:
1)不需要該對象進行同步的數據更新
2)在批量進行更新與刪除時,當更新刪除每一個對象後,要釋對此對象所佔用的內存.
●clear() :用於將一級緩存中的所有對象全部清除。</p>
<p class=MsoNormal> 在進行大批量數據一次性更新的時候,會佔用非常多的內存來緩存被更新的對象。這時就應該階段性地調用clear()方法來清空一級緩存中的對象,控制一級緩存的大小,以避免產生內存溢出的情況。
Hibernate大批量更新時緩存的處理方法:
(假設我們user表的age有5000條大於0的記錄,)
Session session =SessionFactory.openSession();
Transaction tx =session.beginTransaction();
Itertaor users=session.find("from User u where u.age>0").itertaor();//HSL語句就不做解釋了
while(user.hasNext()){
User user =(User)users.next();
user.setAge(user.getAge()+1);
//將本批插入的對象立即寫入數據庫並釋放內存
session.flush();
session.clear();
}
tx.commit();
session.close();
用Hibernate處理大批數據時..都必須先執行5000次的update語句,然後才能更新5000個user 對象..
這樣就影響到了操作上的性能....在項目當我們遇到性能與空間的問題時,,,要以性能爲主..這也就是說要犧牲空間
所以程序最好跳過Hibernate API 而直接通過JDBC API來執來...
我們改一下上面的代碼:
Session session=SessionFactory.openSession();
Transaction tx =session.beginTransaction();
Connection conn =session.connection();
PreparedStatement pstmt = conn.prepareStatement("update users set age=age+1 "+"where age >0");
pstmt.executeUpdate();
tx.commit();
雖說這是通過JDBC API搞作的..但本質上還是通過Hibernater Transaction的事務這個接口來聲明事務的邊界的...
其實最好的解決方法就是以創建存儲過程,,用底層的數據庫運行..這樣性能好,速度快....
我就簡單的以Oracle數據庫爲例子.創建一個名爲UserUpdate的存儲過程...然後在程序中進行調用...
UserUpdate的存儲過程代碼:
create or replace procadure UserUpdate(u_age in number) as
begin
update users set age=age+1 where age>u_age;
end;
下面的是在程序中如何調用我們命名的存儲過程
Session session =SessionFactory.openSession();
Transaction tx =session.beginTransaction();
Connection conn=session.connection();
String str="{call UserUpdate(?)}";
CallableStatement cstmt= conn.prepareCall(str);
cstmt.setInt(1,0);
cstmt.executeUpdate();
tx.commit(); 注意.開源的MySQL中不支持存儲過程的..
用JDBC API的好處是這樣的..
它不用把大批量的數據事先加載到內存中,然後再進行更新與修改..所以不會消耗大量內存....
(小程序中是看不出什麼差別的..當數據的記錄達到一定的數據量的時候自然會發現用Hibernate API 與JDBC API的差別)
在一個就是隻能一條記錄進行批量更新..不像Hibernate中更新每一條的..
第一級是Session的緩存。由於Session對象的生命週期通常對應一個數據庫事務或者一個應用事務,因此它的緩存是事務範圍的緩存。第一級緩存是必需的,不允許而且事實上也無法比卸除。在第一級緩存中,持久化類的每個實例都具有唯一的OID。
二級緩存管理
第二級緩存是一個可插拔的的緩存插件,它是由SessionFactory負責管理。由於SessionFactory對象的生命週期和應用程序的整個過程對應,因此第二級緩存是進程範圍或者集羣範圍的緩存。這個緩存中存放的對象的鬆散數據。第二級對象有可能出現併發問題,因此需要採用適當的併發訪問策略,該策略爲被緩存的數據提供了事務隔離級別。緩存適配器用於把具體的緩存實現軟件與Hibernate集成。第二級緩存是可選的,可以在每個類或每個集合的粒度上配置第二級緩存。
Hibernate的二級緩存策略的一般過程如下:
1) 條件查詢的時候,總是發出一條select * from table_name where …. (選擇所有字段)這樣的SQL語句查詢數據庫,一次獲得所有的數據對象。
2) 把獲得的所有數據對象根據ID放入到第二級緩存中。
3) 當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那麼從二級緩存中查;查不到,再查詢數據庫,把結果按照ID放入到緩存。
4) 刪除、更新、增加數據的時候,同時更新緩存。
Hibernate的二級緩存策略,是針對於ID查詢的緩存策略,對於條件查詢則毫無作用。爲此,Hibernate提供了針對條件查詢的Query緩存。
Hibernate的Query緩存策略的過程如下:
1) Hibernate首先根據這些信息組成一個Query Key,Query Key包括條件查詢的請求一般信息:SQL, SQL需要的參數,記錄範圍(起始位置rowStart,最大記錄個數maxRows),等。
2) Hibernate根據這個Query Key到Query緩存中查找對應的結果列表。如果存在,那麼返回這個結果列表;如果不存在,查詢數據庫,獲取結果列表,把整個結果列表根據Query Key放入到Query緩存中。
3) Query Key中的SQL涉及到一些表名,如果這些表的任何數據發生修改、刪除、增加等操作,這些相關的Query Key都要從緩存中清空。
適合存放到二級緩存中的數據有以下四種:
1)很少被修改的數據
2)不是很重要的數據,允許偶爾併發的數據
3)不會被併發反問的數據
4)參考數據,指的是供應用參考的常量數據,它的實例數目有限,它的實例會被許多其他類的實例引用。它的實例極少或從來不會被修改
對於那些常被修改的數據,如財務數據(絕對不允許出現併發)和其他應用共享的數據,這些都不能放到第二級緩存中
常用的緩存插件
Hibernate的二級緩存是一個插件,下面是幾種常用的緩存插件
1)EhCache:可作爲進程訪問的緩存,存放的物理介質可以是內存或硬盤,對hibernate的查詢緩存提供了支持
2)OSCache:可作爲進程範圍的緩存,存放數據的物理介質可以使內存或硬盤,體統豐富的緩存數據過期策略,hibernate的查詢緩存提供了支持
3)SwarmCache:可作爲集羣範圍內的緩存,但不支持hibernate查詢緩存
4)TreeCache:可作爲集羣範圍內的緩存,支持事務性併發訪問策略,對hibernate的查詢緩存提供了支持
二級緩存示例
配置一:
hibernate.cfg.xml文件中增加
- <span style="font-size: large;"><!--開啓二級緩存-->
- <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
- <!--啓用查詢緩存-->
- <property name="hibernate.cache.use_query_cache">true</property></span>
配置二:
工程項目src文件下新建一個ehcache.xml文件,其內容爲
- <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8"?>
- <ehcache>
- <diskStore path="java.io.tmpdir" />
- <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
- </ehcache></span>
爲了緩存某類的對象,其hbm文件中需添加<cache usage="read-only"/>屬性例如:
- <span style="font-size: large;"><?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <!--
- Mapping file autogenerated by MyEclipse - Hibernate Tools
- -->
- <hibernate-mapping>
- <class name="com.vogue.bbsphoto.entity.Forum"
- table="cdb_forums">
- <cache usage="read-only"/>
- <id name="ID" column="fid" unsaved-value="null">
- <generator class="increment" />
- </id>
- <property name="name" column="name" type="string" />
- <property name="type" column="type" type="string" />
- </class>
- </hibernate-mapping>
- </span>
爲了使用查詢緩存,Query必須設置cacheable爲true,query.setCacheable(true);
例如dao父類中用於hql查詢的方法修改後爲:
- <span style="font-size: large;">/**
- * 執行hql語句的查詢
- * @param sql
- * @return
- */
- public List executeQuery(String hql){
- List list = new ArrayList();
- Session session = HibernateSessionFactory.currentSession();
- Transaction tx = null;
- Query query = session.createQuery(hql);
- query.setCacheable(true);
- try {
- tx = session.beginTransaction();
- list = query.list();
- tx.commit();
- } catch (Exception ex) {
- ex.printStackTrace();
- HibernateSessionFactory.rollbackTransaction(tx);
- } finally {
- HibernateSessionFactory.closeSession();
- }
- return list;
- }
- </span>