Hibernate 缓存
我這里使用的是Hibernate5.2.0版本
Hibernate緩存分為一級緩存(有的也叫Session緩存)和二級緩存。
?
一級緩存(Session)
一級緩存的生命周期和session的生命周期一致,當(dāng)前sessioin一旦關(guān)閉,一級緩存就消失,因此一級緩存也叫session級的緩存或事務(wù)級緩存。一級緩存只存實(shí)體對象的 ,它不會(huì)緩存一般的對象屬性(查詢緩存可以),即當(dāng)獲得對象后,就將該對象的緩存起來,如果在同一session中如果再去獲取這個(gè)對象 時(shí),它會(huì)先判斷緩存中有沒有該對象的ID,如果有就直接從緩存中取出,反之則去數(shù)據(jù)庫中取,取的同時(shí)將該對象的緩存起來,有以下方法可以 支持一級緩存:
? ????get()?
? ? ? load()?
? ? ? iterate(查詢實(shí)體對象)?
其 中 Query 和Criteria的list() 只會(huì)緩存,但不會(huì)使用緩存(除非結(jié)合查詢緩存)。?
下面我們進(jìn)行代碼測試:
?
測試1:
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 User user=session.get(User.class, 2); 5 System.out.println(user); 6 7 User user2=session.get(User.class, 2); 8 System.out.println(user2); 9 10 tran.commit(); 11 session.close();輸出結(jié)果:
1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 2 User [id=2, name=姓名2] 3 User [id=2, name=姓名2]可以很清楚的看到,這里只執(zhí)行了一條SQL語句,當(dāng)使用get方法時(shí),獲取到一個(gè)對象,并將這個(gè)對象緩存到session中,當(dāng)下次再從這個(gè)session中獲取Id=2的User對象,那么session首先從緩存中查詢是否有一個(gè)id=2的User對象,如果就返回,這里剛好有一個(gè),所有就不會(huì)執(zhí)行第二條SQL語句。
?
測試2:
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 User user=session.get(User.class, 200); 5 System.out.println(user); 6 7 User user2=session.get(User.class, 200); 8 System.out.println(user2); 9 10 tran.commit(); 11 session.close();
輸出結(jié)果
1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 2 null 3 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 4 null因?yàn)镮D=200的User對象在數(shù)據(jù)庫中并不存在,所有第一條語句執(zhí)行后,輸出該對象為null,當(dāng)再次從使用get方法時(shí),Hibernate先判斷session是否有id=200的User對象,這里很明顯并沒有。所有再次從數(shù)據(jù)庫中查找,執(zhí)行了第二條語句
?
測試3
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 User user=session.get(User.class, 2); 5 System.out.println(user); 6 7 tran.commit(); 8 session.close(); 9 10 System.out.println("---------------------"); 11 12 session=factory.openSession(); 13 tran=session.beginTransaction(); 14 15 user=session.get(User.class, 2); 16 System.out.println(user); 17 18 tran.commit(); 19 session.close();
輸出結(jié)果:
1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 2 User [id=2, name=姓名2] 3 --------------------- 4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 5 User [id=2, name=姓名2]這里我們可以看出,執(zhí)行了兩條語句。我們知道一級緩存是基于Session,每個(gè)Session緩存的數(shù)據(jù)并不能共享,當(dāng)關(guān)閉Session時(shí),這個(gè)Session中的緩存也會(huì)被清楚。上面我們可以看到兩個(gè)Session并不是同一個(gè)Session對象,所有會(huì)讀取兩條操作。那么怎么才可以夸Session讀取緩存信息了。這里就要用到我們下面要講解的二級緩存。
?
二級緩存
二級緩存也稱進(jìn)程級的緩存或SessionFactory級的緩存,二級緩存可以被所有的session共享,二級緩存的生命周二級緩存的生命周期和 SessionFactory的生命周期一致。?
二級緩存的工具包也很多,我們這里講解EhCache
hibernate.cfg.xml配置
<!-- 啟用二級緩存 --> <property name="cache.use_second_level_cache">true</property> <!-- 選擇二級緩存的工具類 --> <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>第一句代碼,是啟用二級緩存,要想使用二級緩存,我們必須先啟用二級緩存。第二句代碼是選擇第三方緩存提供類,我們這里使用EhCache。以前版本的Hibernate可能會(huì)用cache.provider_class。
并不是所有的類都需要使用緩存機(jī)制,比如財(cái)務(wù)上的數(shù)據(jù),變更比較大,就不能使用緩存。而基本上沒有變更或變更比較少的話就可以使用緩存。要想使某個(gè)類型的對象使用使用緩存,我們也必須給這個(gè)類定義緩存聲明
1 <class-cache usage="read-write" class="com.myproc.domain.User"/>屬性:class用來指定對那個(gè)類使用緩存,這里必須是全限命名(包括包名)
usage:指定緩存策略
☆實(shí)體緩存
測試代碼:
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 User user=session.get(User.class, 2); 5 System.out.println(user); 6 7 tran.commit(); 8 session.close(); 9 10 System.out.println("---------------------"); 11 12 session=factory.openSession(); 13 tran=session.beginTransaction(); 14 15 user=session.get(User.class, 2); 16 System.out.println(user); 17 18 tran.commit(); 19 session.close();?
輸出結(jié)果
1 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 2 User [id=2, name=姓名2] 3 --------------------- 4 User [id=2, name=姓名2]可以看到,雖然是兩個(gè)不同的Session,但是還是只執(zhí)行了一條SQL語句,說明二級緩存起了作用。
? 【總結(jié)】:通過Session.get()方法和Session.load()方法都可以將一個(gè)實(shí)體對象緩存到一級緩存中。如果開啟了二級緩存,那么數(shù)據(jù)也會(huì)緩存到二級緩存中
?
☆集合屬性的緩存
代碼測試:
1 Dept dept=session.get(Dept.class, 5); 2 System.out.println(dept); 3 System.out.println(dept.getUsers()) 4 tran.commit(); 5 session.close(); 6 7 System.out.println("---------------------"); 8 9 session=factory.openSession(); 10 tran=session.beginTransaction(); 11 12 dept=session.get(Dept.class, 2); 13 System.out.println(dept); 14 System.out.println(dept.getUsers());15 tran.commit();
16 session.close();
輸出結(jié)果:
1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=? 2 Dept [id=5, name=部門5] 3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=? 4 [User [id=49, name=姓名49], User [id=26, name=姓名26], User [id=35, name=姓名35], User [id=14, name=姓名14]] 5 --------------------- 6 Dept [id=5, name=部門5] 7 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=? 8 [User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14], User [id=35, name=姓名35]]這里我們可以看到select .... from dept 語句只有一句,說明對應(yīng)Dept對象的二級緩存是成功了的。但是當(dāng)我們查看該對象下users集合中的值時(shí),在第一次查詢的時(shí)候從數(shù)據(jù)庫中讀取了一次,第二次查詢的時(shí)候又讀取了一次,顯然,對應(yīng)對象的集合屬性并沒有緩存到二級緩存中,那么怎么才能夠緩存集合屬性了?
方法:在hibernate中添加collection-cache
?
<collection-cache usage="read-only" collection="com.myproc.domain.Dept.users"/>?
collection屬性為類的全限命名+集合屬性名稱
輸出結(jié)果
1 Hibernate: select dept0_.Id as Id1_0_0_, dept0_.name as name2_0_0_ from dept dept0_ where dept0_.Id=? 2 Dept [id=5, name=部門5] 3 Hibernate: select users0_.deptid as deptid3_1_0_, users0_.Id as Id1_1_0_, users0_.Id as Id1_1_1_, users0_.name as name2_1_1_, users0_.deptid as deptid3_1_1_ from user users0_ where users0_.deptid=? 4 [User [id=35, name=姓名35], User [id=26, name=姓名26], User [id=49, name=姓名49], User [id=14, name=姓名14]] 5 --------------------- 6 Dept [id=5, name=部門5] 7 [User [id=35, name=姓名35], User [id=14, name=姓名14], User [id=26, name=姓名26], User [id=49, name=姓名49]]?
?☆查詢緩存
首先看一下下面的代碼:
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 List list= session.createQuery("FROM User WHERE id<5").list(); 5 System.out.println(list); 6 7 tran.commit(); 8 session.close(); 9 10 System.out.println("---------------------"); 11 12 session=factory.openSession(); 13 tran=session.beginTransaction(); 14 15 User user=session.get(User.class, 1); 16 System.out.println(user); 17 18 List list2= session.createQuery("FROM User WHERE id<5").list(); 19 System.out.println(list2); 20 21 tran.commit(); 22 session.close(); 23 24 tran.commit(); 25 session.close();輸出結(jié)果
1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5 2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]] 3 --------------------- 4 User [id=1, name=姓名1] 5 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5 6 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]當(dāng)我們第一次使用HQL查詢出一個(gè)id<5的集合。在第一次使用HQL同樣查詢id<5的集合,還是在數(shù)據(jù)庫中再查詢了異常,結(jié)論查詢語句不會(huì)使用緩存。
當(dāng)在第二次中我們使用session.get()方式,我們查詢id=1的user時(shí),結(jié)果沒有想數(shù)據(jù)庫中查詢,說明緩存中存在對應(yīng)的數(shù)據(jù)。
【綜上所訴】:查詢語句不會(huì)使用緩存,但是會(huì)將查詢的結(jié)果放在緩存中
?
Question:對于查詢語句難道我們就沒有辦法進(jìn)行緩存了嗎?
Answer:答案是我們是有辦法的。看看下面的代碼
1 Session session=factory.openSession(); 2 Transaction tran=session.beginTransaction(); 3 4 Iterator iterator1= session.createQuery("FROM User WHERE id<5").iterate(); 5 while(iterator1.hasNext()){ 6 System.out.println(iterator1.next()); 7 } 8 9 tran.commit(); 10 session.close(); 11 12 System.out.println("---------------------"); 13 14 session=factory.openSession(); 15 tran=session.beginTransaction(); 16 17 User user=session.get(User.class, 1); 18 System.out.println(user); 19 20 Iterator iterator2= session.createQuery("FROM User WHERE id<5").iterate(); 21 while(iterator2.hasNext()){ 22 System.out.println(iterator2.next()); 23 } 24 25 tran.commit(); 26 session.close();輸出結(jié)果
1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5 2 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 3 User [id=1, name=姓名1] 4 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 5 User [id=2, name=姓名2] 6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 7 User [id=3, name=姓名3] 8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 9 User [id=4, name=姓名4] 10 --------------------- 11 User [id=1, name=姓名1] 12 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<5 13 User [id=1, name=姓名1] 14 User [id=2, name=姓名2] 15 User [id=3, name=姓名3] 16 User [id=4, name=姓名4]結(jié)果說明:在第一次查詢的時(shí)候,首先查詢了滿足條件對象的所有id值,然后在通過迭代方式,沒迭代一次就查詢一次對應(yīng)id的對象,并將該對象放入緩存中。這種就是N+1的查詢。N表示有多少個(gè)對象
在第二次中,使用session.get能夠讀取到之前緩存的數(shù)據(jù),如果在使用HQL語句查詢,那么還是先從數(shù)據(jù)庫中查詢出滿足條件的所有ID,如果對應(yīng)ID在緩存中存在,則直接從緩存中讀取,否則需要再次從數(shù)據(jù)庫中查詢出來
上面的例子,剛好第二次查詢所有數(shù)據(jù)都在緩存中存在,如果我們第二次查詢id<8,那么我們看到結(jié)果將會(huì)是怎樣
1 Hibernate: select user0_.Id as col_0_0_ from user user0_ where user0_.Id<8 2 User [id=1, name=姓名1] 3 User [id=2, name=姓名2] 4 User [id=3, name=姓名3] 5 User [id=4, name=姓名4] 6 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 7 User [id=5, name=姓名5] 8 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 9 User [id=6, name=姓名6] 10 Hibernate: select user0_.Id as Id1_1_0_, user0_.name as name2_1_0_, user0_.deptid as deptid3_1_0_ from user user0_ where user0_.Id=? 11 User [id=7, name=姓名7]
雖然使用迭代的方式可以HQL語句緩存問題,但是對于N+1條查詢,的確非常影響性能,如果數(shù)據(jù)非常多,反而結(jié)果并不理想,所有我們推薦使用下面一種方式:
1 List list1=session.createQuery("FROM User WHERE id<5") 2 .setCacheable(true) 3 .list(); 4 System.out.println(list1); 5 tran.commit(); 6 session.close(); 7 8 System.out.println("---------------------"); 9 10 session=factory.openSession(); 11 tran=session.beginTransaction(); 12 13 List list2=session.createQuery("FROM User WHERE id<5") 14 .setCacheable(true) 15 .list(); 16 System.out.println(list2);對Query設(shè)置能夠使用緩存setCacheable(true),同時(shí)還需要啟用查詢緩存
<!-- 啟用查詢緩存 --> <property name="cache.use_query_cache">true</property>輸出結(jié)果
1 Hibernate: select user0_.Id as Id1_1_, user0_.name as name2_1_, user0_.deptid as deptid3_1_ from user user0_ where user0_.Id<5 2 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]] 3 --------------------- 4 [User [id=1, name=姓名1], User [id=2, name=姓名2], User [id=3, name=姓名3], User [id=4, name=姓名4]]【注意】:使用這種方式,要求條件不行完全一致,不存在包含關(guān)系
☆緩存策略
a、只讀緩存(read-only):只讀策略,表示數(shù)據(jù)不能夠被更改
b、不嚴(yán)格的讀/寫緩存(nonstrict-read-write):需要更新數(shù)據(jù),但是兩個(gè)事務(wù)更新同一條記錄的可能性很小,性能比讀寫緩存好?
c、讀/寫緩存(read-write):允許少量的修改數(shù)據(jù)
d、事務(wù)緩存(transactional):緩存支持事務(wù),發(fā)生異常的時(shí)候,緩存也能夠回滾,只支持jta環(huán)境
讀寫緩存和不嚴(yán)格讀寫緩存在實(shí)現(xiàn)上的區(qū)別在于,讀寫緩存更新緩存的時(shí)候會(huì)把緩存里面的數(shù)據(jù)換成一個(gè)鎖,其他事務(wù)如果去取相應(yīng)的緩存數(shù)據(jù),發(fā)現(xiàn)被鎖住了,然后就直接取數(shù)據(jù)庫查詢。?
各策略的性能從下往上越來越好
?
☆使用ehcache.xml配置文件
1 <!-- 2 ~ Hibernate, Relational Persistence for Idiomatic Java 3 ~ 4 ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later. 5 ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. 6 --> 7 <ehcache> 8 9 <!-- Sets the path to the directory where cache .data files are created. 10 11 If the path is a Java System Property it is replaced by 12 its value in the running VM. 13 14 The following properties are translated: 15 user.home - User's home directory 16 user.dir - User's current working directory 17 java.io.tmpdir - Default temp file path --> 18 <diskStore path="D:/cache/"/> 19 20 21 <!--Default Cache configuration. These will applied to caches programmatically created through 22 the CacheManager. 23 24 The following attributes are required for defaultCache: 25 26 maxInMemory - Sets the maximum number of objects that will be created in memory 27 eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element 28 is never expired. 29 timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used 30 if the element is not eternal. Idle time is now - last accessed time 31 timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used 32 if the element is not eternal. TTL is now - creation time 33 overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache 34 has reached the maxInMemory limit. 35 36 --> 37 <defaultCache 38 maxElementsInMemory="10" 39 eternal="false" 40 timeToIdleSeconds="120" 41 timeToLiveSeconds="120" 42 overflowToDisk="true" 43 /> 44 </ehcache>
?
<diskStore path="D:/cache/"/>:表示當(dāng)緩存超出了maxElementsInMemory是,存到到硬盤的什么位置
?
在hibernate.cfg.xml文件中我們還需要指定ehcache的位置
1 <!-- 設(shè)置ehcache配置文件 --> 2 <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property>
下面我看一看在硬盤中緩存的文件
?
【最后提醒】
如果對于指定了可讀性的策略是,通過HQL語句update或delete操作修改了對象后,hibernate會(huì)通知二級緩存,刪除對于該對象的緩存信息。當(dāng)下一次訪問該對象時(shí),首先訪問二級緩存,此時(shí)二級緩存中該對象根本就不存在了,所有會(huì)查詢數(shù)據(jù)庫,然后將查詢來的對象再次保存到二級緩存中。所有我們直觀的看到,二級緩存中的信息是被更改了的。
而對于一級緩存,對于update的操作,不會(huì)通知session,所有對象屬性還是在沒有保存之前的數(shù)據(jù),如果需要查看對象的新狀態(tài),則使用session.refresh(object);
總結(jié)
以上是生活随笔為你收集整理的Hibernate 缓存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery手机菜单
- 下一篇: 解决slf4j 冲突