緩存:緩存是什麼,解決什麼問題?
位於速度相差較大的兩種硬件/軟件之間的,用於協調二者數據傳輸速度差別的結構,都可稱之爲緩存Cache。緩存目的:讓數據更接近於應用程序,協調速度不匹配,使訪問速度更快。
java
緩存的範圍分爲3類:
1.事務範圍(單Session即一級緩存)
事務範圍的緩存只能被當前事務訪問,每一個事務都有各自的緩存,緩存內的數據一般採用相互關聯的對象形式.緩存的生命週期依賴於事務的生命週期,只有當事務結束時,緩存的生命週期纔會結束.事務範圍的緩存使用內存做爲存儲介質,一級緩存就屬於事務範圍.
2.應用範圍(單SessionFactory即二級緩存)
應用程序的緩存能夠被應用範圍內的全部事務共享訪問.緩存的生命週期依賴於應用的生命週期,只有當應用結束時,緩存的生命週期纔會結束.應用範圍的緩存可使用內存或硬盤做爲存儲介質,二級緩存就屬於應用範圍.
3.集羣範圍(多SessionFactory)
在集羣環境中,緩存被一個機器或多個機器的進程共享,緩存中的數據被複制到集羣環境中的每一個進程節點,進程間經過遠程通訊來保證緩存中的數據的一致,緩存中的數據一般採用對象的鬆散數據形式.算法
一級緩存(session):內部緩存sql
事務範圍:緩存只能被當前事務訪問。緩存的生命週期依賴於事務的生命週期,當事務結束時,緩存也就結束生命週期。數據庫
二級緩存(sessionFactory):緩存
緩存被應用範圍內的全部事務共享。 這些事務有多是併發訪問緩存,所以必須對緩存進行更新。緩存的生命週期依賴於應用的生命週期,應用結束時, 緩存也就結束了生命週期,二級緩存存在於應用範圍。集羣範圍:在集羣環境中,緩存被一個機器或者多個機器的進程共享。緩存中的數據被複制到集羣環境中的每一個進程節點,進程間經過遠程通訊來保證緩存中的數據的一致性, 緩存中的數據一般採用對象的鬆散數據形式,二級緩存也存在與應用範圍。session
注意:對大多數應用來講,應該慎重地考慮是否須要使用集羣範圍的緩存,再加上集羣範圍還有數據同步的問題,因此應當慎用。多種範圍的緩存處理過程持久化層能夠提供多種範圍的緩存。若是在事務範圍的緩存中沒有查到相應的數據,還能夠到應用範圍或集羣範圍的緩存內查詢,若是仍是沒有查到,那麼只有到數據庫中查詢了。併發
在一般狀況下會將具備如下特徵的數據放入到二級緩存中:
● 不多被修改的數據。
● 不是很重要的數據,容許出現偶爾併發的數據。
● 不會被併發訪問的數據。
● 常量數據。
● 不會被第三方修改的數據
而對於具備如下特徵的數據則不適合放在二級緩存中:
● 常常被修改的數據。
● 財務數據,絕對不容許出現併發。
● 與其餘應用共享的數據。
在這裏特別要注意的是對放入緩存中的數據不能有第三方的應用對數據進行更改(其中也包括在本身程序中使用其餘方式進行數據的修改,例如,JDBC),由於那樣Hibernate將不會知道數據已經被修改,也就沒法保證緩存中的數據與數據庫中數據的一致性app
常見的緩存組件
在默認狀況下,Hibernate會使用EHCache做爲二級緩存組件。可是,能夠經過設置hibernate.cache.provider_class屬性,指定其餘的緩存策略,該緩存策略必須實現org.hibernate.cache.CacheProvider接口。
經過實現org.hibernate.cache.CacheProvider接口能夠提供對不一樣二級緩存組件的支持,此接口充當緩存插件與Hibernate之間的適配器。 ide
組件 | Provider類 | 類型 | 集羣 | 查詢緩存 |
Hashtable | org.hibernate.cache.HashtableCacheProvider | 內存 | 不支持 | 支持 |
EHCache | org.hibernate.cache.EhCacheProvider | 內存,硬盤 | 不支持 | 支持 |
OSCache | org.hibernate.cache.OSCacheProvider | 內存,硬盤 | 支持 | 支持 |
SwarmCache | org.hibernate.cache.SwarmCacheProvider | 集羣 | 支持 | 不支持 |
JBoss TreeCache | org.hibernate.cache.TreeCacheProvider | 集羣 | 支持 | 支持 |
Hibernate已經再也不提供對JCS(Java Caching System)組件的支持了。性能
如何在程序裏使用二級緩存:
首先在hibernate.cfg.xml開啓二級緩存
<hibernate-configuration> <session-factory> ...... <!-- 開啓二級緩存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 啓動"查詢緩存"若是想緩存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法得到的數據結果集,必須配置此項--> <property name="hibernate.cache.use_query_cache">true</property> <!-- 設置二級緩存插件EHCache的Provider類--> <!-- <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> --> <!-- 二級緩存區域名的前綴 --> <!--<property name="hibernate.cache.region_prefix">test</property>--> <!-- 高速緩存提供程序 --> <property name="hibernate.cache.region.factory_class"> net.sf.ehcache.hibernate.EhCacheRegionFactory </property> <!-- Hibernate4之後都封裝到org.hibernate.cache.ehcache.EhCacheRegionFactory --> <!-- 指定緩存配置文件位置 --> <!-- <property name="hibernate.cache.provider_configuration_file_resource_path"> ehcache.xml </property> --> <!-- 強制Hibernate以更人性化的格式將數據存入二級緩存 --> <property name="hibernate.cache.use_structured_entries">true</property> <!-- Hibernate將收集有助於性能調節的統計數據 --> <property name="hibernate.generate_statistics">true</property> ...... </session-factory> </hibernate-configuration>
而後是ehcache配置(ehcache.xml)
cache參數詳解:
● name:指定區域名
● maxElementsInMemory :緩存在內存中的最大數目
● maxElementsOnDisk:緩存在磁盤上的最大數目
● eternal :設置是否永遠不過時
● overflowToDisk : 硬盤溢出數目
● timeToIdleSeconds :對象處於空閒狀態的最多秒數後銷燬
● timeToLiveSeconds :對象處於緩存狀態的最多秒數後銷燬
● memoryStoreEvictionPolicy:緩存算法,有LRU(默認)、LFU、LFU
關於緩存算法,常見有三種:
● LRU:(Least Rencently Used)新來的對象替換掉使用時間算最近不多使用的對象
● LFU:(Least Frequently Used)替換掉按命中率高低算比較低的對象
● LFU:(First In First Out)把最先進入二級緩存的對象替換掉
ehcache.xml配置實例
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!--若是緩存中的對象存儲超過指定的緩存數量的對象存儲的磁盤地址--> <diskStore path="D:/ehcache"/> <!-- 默認cache:若是沒有對應的特定區域的緩存,就使用默認緩存 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="false"/> <!-- 指定區域cache:經過name指定,name對應到Hibernate中的區域名便可--> <cache name="cn.javass.h3test.model.UserModel" eternal="false" maxElementsInMemory="100" timeToIdleSeconds="1200" timeToLiveSeconds="1200" overflowToDisk="false"> </cache> </ehcache>
在每一個實體的hbm文件中配置cache元素,usage能夠是read-only或者是read-write等4種。
Xml代碼
<?xml version="1.0" encoding='UTF-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping> <class> <!-- 設置該持久化類的二級緩存併發訪問策略 read-only read-write nonstrict-read-write transactional--> <class name="cn.java.test.model.User" table="TBL_USER"> <cache usage="read-write"/> ...... </class> </hibernate-mapping>
也能夠用Hibernate註解配置緩存實體類
Java代碼
@Entity @Table @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class User implements Serializable { private static final long serialVersionUID = -5121812640999313420L; private Integer id; private String name; ...... }
Query或Criteria接口查詢時設置其setCacheable(true):
默認的若是不在程序中顯示的執行查詢緩存聲明操做,Hibernate是不會對查詢的list進行緩存的。
Java代碼
Session s1= HibernateSessionFactory.getCurrentSession(); s1.beginTransaction(); System.out.println("第一次查詢User"); Query q = s1.createQuery("from User"); q.setCacheable(true); q.list(); System.out.println("放進二級緩存"); s1.getTransaction().commit(); Session s2= HibernateSessionFactory.getCurrentSession(); s2.beginTransaction(); System.out.println("第二次查詢User,將不會發出sql"); Query q = s2.createQuery("from User"); q.setCacheable(true); q.list(); s2.getTransaction().commit(); //若是配置文件打開了generate_statistics性能調解,能夠獲得二級緩存命中次數等數據 Statistics s = HibernateSessionFactoryUtil.getSessionFactory().getStatistics(); System.out.println(s); System.out.println("put:"+s.getSecondLevelCachePutCount()); System.out.println("hit:"+s.getSecondLevelCacheHitCount()); System.out.println("miss:"+s.getSecondLevelCacheMissCount());
若是開啓了二級緩存,因爲session是共享二級緩存的,只要緩存裏面有要查詢的對象,就不會向數據庫發出sql,若是在二級緩存裏沒有找到須要的數據就會發出sql語句去數據庫拿。
一些對二級緩存的理解
當hibernate更新數據庫的時候,它怎麼知道更新哪些查詢緩存呢?
hibernate在一個地方維護每一個表的最後更新時間,其實也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的緩存配置裏面。
當經過hibernate更新的時候,hibernate會知道此次更新影響了哪些表。而後它更新這些表的最後更新時間。每一個緩存都有一個生成時間和這個緩存所查詢的表,當hibernate查詢一個緩存是否存在的時候,若是緩存存在,它還要取出緩存的生成時間和這個緩存所查詢的表,而後去查找這些表的最後更新時間,若是有一個表在生成時間後更新過了,那麼這個緩存是無效的。
若是找到的時間戳晚於高速緩存查詢結果的時間戳,那麼緩存結果將被丟棄,從新執行一次查詢。
能夠看出,只要更新過一個表,那麼凡是涉及到這個表的查詢緩存就失效了,所以查詢緩存的命中率可能會比較低。
使用二級緩存的前置條件
對於那些查詢很是多但插入、刪除、更新很是少的應用程序來講,查詢緩存可提高性能。但寫入多查詢少的沒有用,總失效。
hibernate程序對數據庫有獨佔的寫訪問權,其餘的進程更新了數據庫,hibernate是不可能知道的。
你操做數據庫必需直接經過hibernate,若是你調用存儲過程,或者本身使用jdbc更新數據庫,hibernate也是不知道的。
這個限制至關的棘手,有時候hibernate作批量更新、刪除很慢,可是你卻不能本身寫jdbc來優化。
固然能夠用SessionFactory提供的移除緩存的方法(上面的二級緩存的管理裏面有介紹)
總結 不要想固然的覺得緩存必定能提升性能,僅僅在你可以駕馭它而且條件合適的狀況下才是這樣的。hibernate的二級緩存限制仍是比較多的,不方便用jdbc可能會大大的下降更新性能。在不瞭解原理的狀況下亂用,可能會有1+N的問題。不當的使用還可能致使讀出髒數據。 若是受不了Hibernate的諸多限制,那麼仍是本身在應用程序的層面上作緩存吧! 在越高的層面上作緩存,效果就會越好。就好像儘管磁盤有緩存,數據庫仍是要實現本身的緩存,儘管數據庫有緩存,我們的應用程序仍是要作緩存。由於底層的緩存它並不知道高層要用這些數據幹什麼,只能作的比較通用,而高層能夠有針對性的實現緩存,因此在更高的級別上作緩存,效果也要好些吧!