Hibernate二級緩存簡述及基於Spring4,Hibernate5,Ehcache3的二級緩存配置


目前的項目是一個極少寫沖突,多讀,多重復HQL語句的項目,因此非常適合使用Hibernate的二級緩存進行查詢優化。目前項目使用的均是最新版本的框架,配置成功后很快就成功使用了,大概講講配置方法。

1. Hibernate L2緩存

1.1. 緩存的分類

  • 事務緩存:作用於事務范圍,session結束則緩存清除,Hibernate的L1緩存為事務緩存,默認開啟,我們在純Hibernate項目中手寫過回滾的代碼,能夠回滾就是因為事務緩存。
  • 應用緩存:作用於應用范圍,被所有事務共享,依賴於應用的生命周期。所以,非常適合使用一個同樣依賴於應用生命周期的輕量級緩存來實現,ehcache幾乎是最好的選擇。
  • 集群緩存:該緩存類似於真正的數據庫被一個集群共享,典型的如Redis就很適合做集群緩存。

1.2. L2緩存工作原理

Hibernate的L1,L2緩存均是通過id進行工作,當Hibernate根據id訪問對象時會先在一級緩存中查找,如果查不到則在二級緩存中查找。

SessionFactory二級緩存根據功能和目的又可以划分為內置緩存和外置緩存,內置緩存存放映射元數據和預定義SQL語句,前者為映射文件中數據的副本,后者為根據副本推導出的SQL語句。內置緩存是只讀的,因此不需要與映射文件進行同步。外置緩存是Hibernate的一個插件,默認不啟用,即Hibernate的L2緩存。外置緩存的數據是數據庫數據的副本,外置緩存的介質可以是內存或者硬盤。

1.3. 放入二級緩存的數據

一般包含以下幾種:

  • 很少被修改的數據
  • 不是很重要的數據,允許出現偶爾並發的數據。
  • 不會被並發訪問的數據。
  • 常量數據。
  • 不會被第三方修改的數據。

2. Ehcache

Ehcache是一個健壯的簡潔的輕量的純Java進程的內存緩存框架,因此其存在與Java進程直接相關聯。通過在硬盤和內存里對數據進行拷貝,實現了數據庫的緩存。由於Apache的支持,Ehcache非常穩健。

2.1. 依賴

<!--ehcache依賴slf4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
<!--slf4j依賴log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
<!--ehcache-->
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.3.1</version>
        </dependency>
<!--hibernate.ehcache-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${org.hibernate.version}</version>
        </dependency>

2.2. ehcache.xml

該文件需要放置src中(Maven項目的resources中),以便編譯后在根目錄內,也可以顯示指定位置。這個文件給出了ehcache的基本配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <!--沒有特殊設置時系統默認使用此設置-->
    <defaultCache
            maxElementsInMemory="1000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />
    <!--想使用查詢緩存,這兩個類需要添加-->
    <cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
           maxElementsInMemory="5000"
           eternal="true"
           overflowToDisk="true" />
    <cache name="org.hibernate.cache.internal.StandardQueryCache"
           maxElementsInMemory="10000"
           eternal="false"
           timeToLiveSeconds="120"
           overflowToDisk="true" />
    <cache name="javaClassName" maxElementsInMemory="2000" eternal="false"
           timeToIdleSeconds="120" timeToLiveSeconds="120"
           overflowToDisk="true" />
</ehcache>

ehcache的各屬性介紹如下:

  • name:緩存名稱。
  • maxElementsInMemory:緩存最大個數。
  • eternal:對象是否永久有效,一但設置了,timeout將不起作用。
  • timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。
  • timeToLiveSeconds:設置對象在失效前允許存活時間,最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0,也就是對象存活時 間無窮大。
  • overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。
  • diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。
  • maxElementsOnDisk:硬盤最大緩存個數。
  • diskPersistent:是否緩存虛擬機重啟期數據,默認false。
  • diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
  • memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU。你可以設置為 FIFO或是LFU。
  • clearOnFlush:內存數量最大時是否清除。

2.3. 常用的memoryStoreEvictionPolicy(緩存算法)

關於常用的緩存算法主要有三種:

  • LRU:(Least Rencently Used)新來的對象替換掉使用時間算最近很少使用的對象。
  • LFU:(Least Frequently Used)替換掉按命中率高低算比較低的對象。
  • FIFO: (First In First Out)把最早進入二級緩存的對象替換掉。

2.4. ehcache使用

ehcache不支持事務,有三種模式:

  • READ_ONLY: 適用於僅讀取,如果有數據的更新操作則會異常。
  • READ_WRITE: 用讀寫鎖控制緩存
  • NON_STRICT_READ_WRITE: 不加鎖控制緩存,寫寫會有沖突,適用於很難發生寫沖突的系統。

具體使用時,在hibernate持久化生成的Entity上使用類似這樣的標簽,即可為該數據庫添加二級緩存。

@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

通常情況下,緩存用於多讀少寫的表,在這種表中,最高效,最符合緩存本身行為的應該是READ_ONLY模式,即,在讀取時使用緩存,發生寫操作時清空緩存。

3. Spring配置

3.1. sessionFactory配置

當我們使用Spring的hibernateTemplate時,需要對sessionFactory進行配置,其中有無關於ehcache的部分可以參考Spring4托管Hibernate5並利用HibernateTemplate進行數據庫操作,這里主要講解和ehcache相關的設置。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        ……
        <property name="hibernateProperties">
            <props>
                ……
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.use_query_cache">true</prop>
                <prop key="hibernate.cache.region.factory_class">
                org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>
            </props>
        ……
  • hibernate.cache.use_second_level_cache 是hibernate中L2緩存的開關,必須為true。
  • hibernate.cache.use_query_cache 是hibernate的查詢緩存的開關,可以自己決定是否開啟。
  • hibernate.cache.region.factory_class 承載L2緩存的方法,即選擇L2緩存數據庫。官方很坑的從hibernate4開始就存在文檔問題,文檔中仍為provider_class,實際上早已換為了這個方法(idea的默認提示中找不到,但運行后如果沒添加,錯誤日志里可以顯示出)。需要注意的是,需要使用Singleton模式的Factory,否則會有沖突問題。具體原因還不明了。

另外有幾個可以開啟的選項,包括

  • hibernate.generate_statistics 生成統計日志,如果項目在調試,這是一個很好的開發選項。記得實際運行時關閉掉。
  • hibernate.cache.provider_configuration_file_resource_path 提供配置文件的路徑,如果你不想使用默認路徑,那么需要在這里配置,其格式和web.xml中的路徑一致。

3.2. hibernateTemplate配置

其實就是開啟一下查詢緩存,一條

    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory" />
        <property name="cacheQueries" value="true"/>
    </bean>

4. Hiberante二級緩存的使用

Hibernate的所有查詢方法均用到事務緩存,但對於SessionFactory緩存,只有部分方法會使用。

4.1. 不使用二級緩存的方法

Hibernate的各種查詢方式中,以下幾種方式不使用緩存,直接從數據庫讀寫:

  • get()
  • find()
  • list()

其中后兩者在使用hibernateTemplate時均為find()方法。但當開啟了查詢緩存后,使用這些方法時,同樣也會把查詢的結果存入緩存,這會造成一定的時間消耗,但是可以有效的避免使用緩存時的N+1問題。

4.2. 使用二級緩存的方法

Hibernate的以下方法使用二級緩存

  • load()
  • iterate()

這里面特別說明一下iterate()方法,該方法返回的是一個指向查詢結果的指針,當方法返回指針后,如果想通過指針獲取整個查詢結果,則需要使用事務,並在表上加如下標簽:

@Proxy(lazy = false)

關閉hibernate的懶加載。否則,當想要通過返回的iterator獲取其下一方法,iterator.next(),則會因為變量已經進入游離態,無法找到下一方法。即使如此,尋找下一指針的方法也需要和返回iterator的方法處於同一事務內才能成功。

一個對lazy=false產生的損耗的補救方案是使用Spring的OpenSessionInViewFilter來管理session,在web.xml中添加

    <filter>
        <filter-name>OpenSessionInViewFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>OpenSessionInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

即可。

4.3. iterate()查詢原理和N+1問題

使用iterate()方法時,Hibernate會先訪問數據庫,查詢所有要查詢對象的id,再訪問緩存,通過id查詢所有要查詢對象,當對象在緩存中時,直接返回結果,當對象不再緩存中時,訪問數據庫查詢該對象。因此,當緩存沒有建立時,這樣的查詢方法會產生N+1次查詢,遠比find()方法的1次數據庫查詢效率低下。所以,簡單的使用iterator對數據進行查詢是十分不合理的,兩種方案可以考慮。

  • 在用戶訪問前,對數據庫中常用數據進行緩存,比如,在程序啟動后自動執行一次find()行為把常用數據進行存儲。
  • 用戶的第一次訪問使用find()方法,並獲取緩存,之后的訪問使用iterate()方法。

5. 參考文檔

CacheConcurrencyStrategy的五種緩存方式的簡單介紹

ehcache memcache redis 三大緩存男高音

Hibernate4之二級緩存配置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM