(十六)Hibernate之性能優化之緩存管理

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代碼  收藏代碼
  1. <span style="font-size: large;"><!--開啓二級緩存-->  
  2. <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>  
  3. <!--啓用查詢緩存-->  
  4. <property name="hibernate.cache.use_query_cache">true</property></span>  
 

 配置二:

工程項目src文件下新建一個ehcache.xml文件,其內容爲

Java代碼  收藏代碼
  1. <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8"?>  
  2. <ehcache>  
  3. <diskStore path="java.io.tmpdir" />  
  4. <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />  
  5.   
  6. </ehcache></span>  
 配置三:

爲了緩存某類的對象,其hbm文件中需添加<cache usage="read-only"/>屬性例如:

Xml代碼  收藏代碼
  1. <span style="font-size: large;"><?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  3. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  4. <!--  
  5.     Mapping file autogenerated by MyEclipse - Hibernate Tools 
  6. -->  
  7. <hibernate-mapping>  
  8.   
  9.    <class name="com.vogue.bbsphoto.entity.Forum"   
  10.    table="cdb_forums">  
  11.         <cache usage="read-only"/>  
  12.         <id name="ID" column="fid" unsaved-value="null">  
  13.             <generator class="increment" />  
  14.        </id>  
  15.   
  16.        <property name="name" column="name" type="string" />  
  17.        <property name="type" column="type" type="string" />  
  18.     </class>  
  19. </hibernate-mapping>  
  20. </span>  
 配置四:

爲了使用查詢緩存,Query必須設置cacheable爲true,query.setCacheable(true);

例如dao父類中用於hql查詢的方法修改後爲:

Java代碼  收藏代碼
  1. <span style="font-size: large;">/** 
  2.      * 執行hql語句的查詢 
  3.  
  4.      * @param sql 
  5.      * @return 
  6.      */  
  7.    public List executeQuery(String hql){  
  8.         List list = new ArrayList();  
  9.         Session session = HibernateSessionFactory.currentSession();  
  10.        Transaction tx = null;  
  11.         Query query = session.createQuery(hql);  
  12.       query.setCacheable(true);  
  13.        try {  
  14.             tx = session.beginTransaction();  
  15.             list = query.list();  
  16.            tx.commit();  
  17.         } catch (Exception ex) {  
  18.            ex.printStackTrace();  
  19.             HibernateSessionFactory.rollbackTransaction(tx);  
  20.               
  21.         } finally {  
  22.   
  23.             HibernateSessionFactory.closeSession();  
  24.        }  
  25.         return list;  
  26.    }  
  27. </span>  
 補充一下:當要緩存的對象處於級聯關係中時。如果和他存在級聯關係的對象都有屬性 <cache usage="read-only"/>那麼,在第一次get後該對象所處的對象圖中的所有對象都會保存到hibernate的二級緩存中,在第二次get該對象時,直接從二級緩存中找到所有級聯的對象;如果其中某個級聯對象沒有<cache usage="read-only"/>屬性,則不會被保存到二級緩存中,以後每次get時仍然會執行sql去數據庫中找該級聯對象
發佈了7 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章