2025-02-10

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技術架構”

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *