第十二章 hibernate緩存
1、一級緩存(Session級緩存)
一級緩存很短和session的生命周期一致,因此也叫session級緩存或事務級緩存
那些方法支持一級緩存:
*get()
*load()
*iterate(查詢實體對象)
如何管理一級緩存:
*session.clear(),session.evict()
如何避免一次性大量的實體數據入庫導致內存溢出
方法1:先flush,再clear
for(int i = 0; i <1000000; i++){ session.save(user); if(i % 20 == 0){ session.flush(); session.clear(); } }
方法2:用StatelessSession接口
Hibernate提供了基於命令的API,可以用detachedobject(分離對象)的形式把數據以流的方法加入到數據庫,或從數據庫輸出。StatelessSession沒有持久化上下文,也不提供多少高層的生命周期語義。特別是,無狀態session不實現第一級cache,也不和第二級緩存,或者查詢緩存交互。用StatelessSession進行的操作甚至不級聯到關聯實例。
無狀態session是低層的抽象,和低層JDBC相當接近。
StatelessSession statelessSession = sessionFactory.openStatelessSession(); statelessSession.insert(user);
方法3:批量更新
session.beginTransaction(); Queryquery = session.createQuery("updateUser as u set u.password=:p"); query.setString("p","000000"); query.executeUpdate(); session.getTransaction().commit();
如果數據量特別大,考慮采用jdbc實現,如果jdbc也不能滿足要求可以考慮采用數據本身的特定導入工具
2、二級緩存
Hibernate默認的二級緩存是開啟的。
二級緩存也稱為進程級的緩存,也可稱為SessionFactory級的緩存(因為SessionFactory可以管理二級緩存),它與session級緩存不一樣,一級緩存隻要session關閉緩存就不存在了。而二級緩存則隻要進程在二級緩存就可用。
二級緩存可以被所有的session共享
二級緩存的生命周期和SessionFactory的生命周期一樣,SessionFactory可以管理二級緩存
二級緩存同session級緩存一樣,隻緩存實體對象,普通屬性的查詢不會緩存
二級緩存一般使用第三方的產品,如EHCache
2.1常見緩存提供商
Cache |
Providerclass |
Type |
ClusterSafe |
QueryCache Supported |
Hashtable(not intended for production use) |
org.hibernate.cache.HashtableCacheProvider |
memory |
|
yes |
EHCache |
org.hibernate.cache.EhCacheProvider |
memory,disk |
|
yes |
OSCache |
org.hibernate.cache.OSCacheProvider |
memory,disk |
|
yes |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
clustered(ip multicast) |
yes(clustered invalidation) |
|
JBossTreeCache |
org.hibernate.cache.TreeCacheProvider |
clustered(ip multicast), transactional |
yes(replication) |
yes(clock sync req.) |
2.2二級緩存的用法
這裏以常見的EHCache為例
(1)在hibernate.cfg.xml中開啟二級緩存。
設置啟用二級緩存:
<propertyname="hibernate.cache.use_second_level_cache">true</property>
設置二級緩存的實現類(緩存提供商):
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider </property>
(2)導入所使用的二級緩存JAR包
ehcache-1.2.3.jar、commons-logging.jar、commons-logging-1.0.4.jar
(3)src下寫緩存配置文件:ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="C:\\cache" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskPersistent="false" /> </ehcache>
maxElementsInMemory屬性用於指定緩存中最多可放多少個對象。
overflowToDisk當內存中緩存的記錄達到maxElementsInMemory時是否被持久化到硬盤中。保存路徑由diskStore決定的
eternal屬性指定緩存是否永久有效。
timeToIdleSeconds屬性指定緩存多久未被使用便清理掉。
timeToLiveSeconds屬性指定緩存的生命長度。
diskPersistent屬性指定緩存是否被持久化到硬盤中,保存路徑由diskStore標簽指定。
(4)設置所有緩存的實體類
a、hibernate.cfg.xml中設置
<class-cacheusage="read-only"/>
b、*.hbm.xml中設置
<hibernate-mapping> <class name="cn.framelife.hibernate.entity.User" table="user" catalog="hibernate"> <cache usage="read-only"/> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator /> </id> <property name="username" type="java.lang.String"> <column name="username" length="45" not-null="true" /> </property> <property name="password" type="java.lang.String"> <column name="password" length="45" not-null="true" /> </property> </class> </hibernate-mapping>
c、Annotation方式
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
@Table(name = "user", catalog = "hibernate")
public class User implements java.io.Serializable {}
d、緩存策略
隻讀緩存(read-only):沒有什麼好說的 ,最常用也最簡單的。
讀/寫緩存(read-write):程序可能要的更新數據
不嚴格的讀/寫緩存(nonstrict-read-write):需要更新數據,但是兩個事務更新同一條記錄的可能性很小,性能比讀寫緩存好
事務緩存(transactional):緩存支持事務,發生異常的時候,緩存也能夠回滾,隻支持jta環境。
讀寫緩存和不嚴格讀寫緩存在實現上的區別在於,讀寫緩存更新緩存的時候會把緩存裏麵的數據換成一個鎖,其他事務如果去取相應的緩存數據,發現被鎖住了,然後就直接取數據庫查詢。
在hibernate2.1的ehcache實現中,如果鎖住部分緩存的事務發生了異常,那麼緩存會一直被鎖住,直到60秒後超時。
不嚴格讀寫緩存不鎖定緩存中的數據。
(5)測試
測試代碼:
List<User> uesrs = session.createQuery("from User").list(); System.out.println("--------------"); Session s2 = sessionFactory.openSession(); User user = (User) s2.get(User.class, 2); System.out.println(user.getUsername());
結果:
Hibernate: select user0_.id as id0_, user0_.first_name as first2_0_, user0_.last_name as last3_0_, user0_.password as password0_, user0_.username as username0_ from hibernate.user user0_ -------------- zhangsan
2.3打開二級緩存統計信息
List<User> uesrs = session.createQuery("from User").list(); System.out.println("--------------"); Session s2 = sessionFactory.openSession(); User user = (User) s2.get(User.class, 2); System.out.println(user.getUsername()); Statistics st = sessionFactory.getStatistics(); System.out.println(st); System.out.println(st.getSecondLevelCacheStatistics("cn.framelife.hibernate.entity.User").getEntries());
3、查詢緩存
hibernate的查詢緩存是主要是針對普通屬性結果集的緩存,而對於實體對象的結果集隻緩存id。在一級緩存,二級緩存和查詢緩存都打開的情況下作查詢操作時這樣的:查詢普通屬性,會先到查詢緩存中取,如果沒有,則查詢數據庫;查詢實體(對象),會先到查詢緩存中取id,如果有,則根據id到緩存(一級/二級)中取實體(對象),如果緩存中取不到實體,再查詢數據庫。
查詢緩存的生命周期,是不確定的,當前關聯的表發生改變時,查詢緩存的生命周期結束。
配置和使用:
查詢緩存的配置和使用也是很簡單的:
1>查詢緩存的啟用不但要在配置文件中進行配置
<propertyname="hibernate.cache.use_query_cache">true</property>
2>還要在程序中顯示的進行啟用
query.setCacheable(true);
測試:
Query query = session.createQuery("select u.username from User u where u.id > 210"); //query.setCacheable(true); List<String> names = query.list(); for (String name : names) { System.out.println(name); } System.out.println("================================"); query = session.createQuery("select u.username from User u where u.id > 210"); //query.setCacheable(true); names = query.list(); for (String name : names) { System.out.println(name); }
沒開啟查詢緩存(query.setCacheable(false))時的結果:
Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111 ================================ Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111
一模一樣的查詢,執行了兩次SQL。
開啟查詢緩存(query.setCacheable(true))時的結果:
Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210 abcd 1111 11111 ================================ abcd 1111 11111
開啟查詢緩存後,一模一樣兩次的查詢,隻需要執行一次sql。
注意:
當隻是用Hibernate查詢緩存,而關閉二級緩存的時候:
第一:如果查詢的是部分屬性結果集:
那麼當第二次查詢的時候就不會發出SQL直接從Hibernate查詢緩存中取數據
第二:如果查詢的是實體結果集eg(fromStudent) 這個HQL那麼查詢出來的實體,首先Hibernate查詢緩存存放實體的ID,
第二次查詢的時候就到Hibernate查詢緩存中取出ID一條一條的到數據庫查詢這樣 將發出N條SQL造成了SQL泛濫
在隻打開查詢緩存,關閉二級緩存的情況下,不要去查詢實體對象。這樣會造成很大的資源浪費。
當都開啟Hibernate查詢緩存和二級緩存的時候
第一:如果查詢的是部分屬性結果集:這個和上麵隻是用Hibernate查詢緩存而關閉 二級緩存的時候,一致 因為不涉及實體不會用到二級緩存
第二:如果查詢的是實體結果集eg(fromStudent) 這個HQL那麼查詢出來的實體,首先Hibernate查詢緩存存放實體的ID,第二次查詢,的時候就到Hibernate查詢緩存中取出ID,拿到二級緩存區找數據,如果有數據就不會發出SQL如果都有一條SQL都不會發出直接從二級緩存中取數據
例子:
/** * 開啟查詢緩存,開啟二級緩存, 開啟兩個session,分別調用query.list查詢實體對象 */ // 如果不用查詢緩存的話,那兩個都發出查詢語句,這也是默認的情況. try { session = sessionFactory.openSession(); t = session.beginTransaction(); Query query = session.createQuery("from User as u where u.id < 90"); // 啟用查詢緩存 query.setCacheable(true); List<User> list = query.list(); for (User user : list) { System.out.println(user.getUsername()); } t.commit(); } catch (Exception e) { e.printStackTrace(); t.rollback(); } finally { session.close(); } System.out.println("================================"); try { session = sessionFactory.openSession(); t = session.beginTransaction(); Query query = session.createQuery("from User as u where u.id < 90"); // 啟用查詢緩存 query.setCacheable(true); // 不會發出查詢語句,因為這種情況下,查詢過程是這樣的: // 在第一次執行list時,會把查詢對象的id緩存到查詢緩存裏 // 第二次執行list時, 會遍曆查詢緩存裏的id到緩存裏去找實體對象,由於這裏開啟了二級緩存,可以找到目標實體對象, // 所以就不會再發出n條查詢語句. List<User> list = query.list(); for (User user : list) { System.out.println(user.getUsername()); } t.commit(); } catch (Exception e) { e.printStackTrace(); t.rollback(); } finally { session.close(); }
4集群緩存
集群中的每一台機子上都有緩存。修改的成本高。
5中央緩存
服務中使用一台獨立的機器作為緩存。獲取數據的成本高,修改的成本低。
6使用緩存的條件
1、服務中讀取數據多於修改數據。2、數據量不能大於內存容量。
3、對數據要有獨享的控製。
4、可以容忍無效數據。
7注意事項
不要想當然的以為緩存一定能提高性能,僅僅在你能夠駕馭它並且條件合適的情況下才是這樣的。hibernate的二級緩存限製還是比較多的,不方便用jdbc可能會大大的降低更新性能。在不了解原理的情況下亂用,可能會有1+N的問題。不當的使用還可能導致讀出髒數據。如果受不了hibernate的諸多限製,那麼還是自己在應用程序的層麵上做緩存吧。
在越高的層麵上做緩存,效果就會越好。就好像盡管磁盤有緩存,數據庫還是要實現自己的緩存,盡管數據庫有緩存,咱們的應用程序還是要做緩存。因為底層的緩存它並不知道高層要用這些數據幹什麼,隻能做的比較通用,而高層可以有針對性的實現緩存,所以在更高的級別上做緩存,效果也要好些吧。
最後更新:2017-04-03 18:52:12
上一篇:
第十三章 事務並發處理
下一篇:
第十一章 Hibernate的查詢 本地SQL查詢
Android之ExpandableListView下拉分組的實現
MQ機製
淺談物聯網智能設備數據安全麵臨的挑戰
阿裏雲 oss JavaScript客戶端簽名文件上傳 vue2.0
[LeetCode]19.Remove Nth Node From End of List
純幹貨 | 機器學習中梯度下降法的分類及對比分析(附源碼)
無線技術在遠程醫療中正扮演著重要角色
MySQL使用初步—mysql數據庫的基本命令
Android 在線視頻播放總結
System.BadImageFormatException: 未能加載文件或程序集“Oracle.DataAccess”或它的某一個依賴項。試圖加載格式不正確的程序。