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文件中增加
Java代碼 收藏代碼
<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文件,其內容為
Java代碼 收藏代碼
<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"/>屬性例如:
Xml代碼 收藏代碼
<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查詢的方法修改後為:
Java代碼 收藏代碼
<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>
補充一下:當要緩存的對象處於級聯關系中時。如果和他存在級聯關系的對象都有屬性 <cache usage="read-only"/>那麼,在第一次get後該對象所處的對象圖中的所有對象都會保存到hibernate的二級緩存中,在第二次get該對象時,直接從二級緩存中找到所有級聯的對象;如果其中某個級聯對象沒有<cache usage="read-only"/>屬性,則不會被保存到二級緩存中,以後每次get時仍然會執行sql去數據庫中找該級聯對象
作者“ERDP技術架構”