Hibernate 缓存
我這里使用的是Hibernate5.2.0版本
Hibernate緩存分為一級緩存(有的也叫Session緩存)和二級緩存。
?
一級緩存(Session)
一級緩存的生命周期和session的生命周期一致,當前sessioin一旦關閉,一級緩存就消失,因此一級緩存也叫session級的緩存或事務級緩存。一級緩存只存實體對象的 ,它不會緩存一般的對象屬性(查詢緩存可以),即當獲得對象后,就將該對象的緩存起來,如果在同一session中如果再去獲取這個對象 時,它會先判斷緩存中有沒有該對象的ID,如果有就直接從緩存中取出,反之則去數據庫中取,取的同時將該對象的緩存起來,有以下方法可以 支持一級緩存:
? ????get()?
? ? ? load()?
? ? ? iterate(查詢實體對象)?
其 中 Query 和Criteria的list() 只會緩存,但不會使用緩存(除非結合查詢緩存)。?
下面我們進行代碼測試:
?
測試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();輸出結果:
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]可以很清楚的看到,這里只執行了一條SQL語句,當使用get方法時,獲取到一個對象,并將這個對象緩存到session中,當下次再從這個session中獲取Id=2的User對象,那么session首先從緩存中查詢是否有一個id=2的User對象,如果就返回,這里剛好有一個,所有就不會執行第二條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();
輸出結果
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因為ID=200的User對象在數據庫中并不存在,所有第一條語句執行后,輸出該對象為null,當再次從使用get方法時,Hibernate先判斷session是否有id=200的User對象,這里很明顯并沒有。所有再次從數據庫中查找,執行了第二條語句
?
測試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();
輸出結果:
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]這里我們可以看出,執行了兩條語句。我們知道一級緩存是基于Session,每個Session緩存的數據并不能共享,當關閉Session時,這個Session中的緩存也會被清楚。上面我們可以看到兩個Session并不是同一個Session對象,所有會讀取兩條操作。那么怎么才可以夸Session讀取緩存信息了。這里就要用到我們下面要講解的二級緩存。
?
二級緩存
二級緩存也稱進程級的緩存或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可能會用cache.provider_class。
并不是所有的類都需要使用緩存機制,比如財務上的數據,變更比較大,就不能使用緩存。而基本上沒有變更或變更比較少的話就可以使用緩存。要想使某個類型的對象使用使用緩存,我們也必須給這個類定義緩存聲明
1 <class-cache usage="read-write" class="com.myproc.domain.User"/>屬性:class用來指定對那個類使用緩存,這里必須是全限命名(包括包名)
usage:指定緩存策略
☆實體緩存
測試代碼:
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();?
輸出結果
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]可以看到,雖然是兩個不同的Session,但是還是只執行了一條SQL語句,說明二級緩存起了作用。
? 【總結】:通過Session.get()方法和Session.load()方法都可以將一個實體對象緩存到一級緩存中。如果開啟了二級緩存,那么數據也會緩存到二級緩存中
?
☆集合屬性的緩存
代碼測試:
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();
輸出結果:
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 語句只有一句,說明對應Dept對象的二級緩存是成功了的。但是當我們查看該對象下users集合中的值時,在第一次查詢的時候從數據庫中讀取了一次,第二次查詢的時候又讀取了一次,顯然,對應對象的集合屬性并沒有緩存到二級緩存中,那么怎么才能夠緩存集合屬性了?
方法:在hibernate中添加collection-cache
?
<collection-cache usage="read-only" collection="com.myproc.domain.Dept.users"/>?
collection屬性為類的全限命名+集合屬性名稱
輸出結果
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();輸出結果
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]]當我們第一次使用HQL查詢出一個id<5的集合。在第一次使用HQL同樣查詢id<5的集合,還是在數據庫中再查詢了異常,結論查詢語句不會使用緩存。
當在第二次中我們使用session.get()方式,我們查詢id=1的user時,結果沒有想數據庫中查詢,說明緩存中存在對應的數據。
【綜上所訴】:查詢語句不會使用緩存,但是會將查詢的結果放在緩存中
?
Question:對于查詢語句難道我們就沒有辦法進行緩存了嗎?
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();輸出結果
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]結果說明:在第一次查詢的時候,首先查詢了滿足條件對象的所有id值,然后在通過迭代方式,沒迭代一次就查詢一次對應id的對象,并將該對象放入緩存中。這種就是N+1的查詢。N表示有多少個對象
在第二次中,使用session.get能夠讀取到之前緩存的數據,如果在使用HQL語句查詢,那么還是先從數據庫中查詢出滿足條件的所有ID,如果對應ID在緩存中存在,則直接從緩存中讀取,否則需要再次從數據庫中查詢出來
上面的例子,剛好第二次查詢所有數據都在緩存中存在,如果我們第二次查詢id<8,那么我們看到結果將會是怎樣
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條查詢,的確非常影響性能,如果數據非常多,反而結果并不理想,所有我們推薦使用下面一種方式:
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設置能夠使用緩存setCacheable(true),同時還需要啟用查詢緩存
<!-- 啟用查詢緩存 --> <property name="cache.use_query_cache">true</property>輸出結果
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]]【注意】:使用這種方式,要求條件不行完全一致,不存在包含關系
☆緩存策略
a、只讀緩存(read-only):只讀策略,表示數據不能夠被更改
b、不嚴格的讀/寫緩存(nonstrict-read-write):需要更新數據,但是兩個事務更新同一條記錄的可能性很小,性能比讀寫緩存好?
c、讀/寫緩存(read-write):允許少量的修改數據
d、事務緩存(transactional):緩存支持事務,發生異常的時候,緩存也能夠回滾,只支持jta環境
讀寫緩存和不嚴格讀寫緩存在實現上的區別在于,讀寫緩存更新緩存的時候會把緩存里面的數據換成一個鎖,其他事務如果去取相應的緩存數據,發現被鎖住了,然后就直接取數據庫查詢。?
各策略的性能從下往上越來越好
?
☆使用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/"/>:表示當緩存超出了maxElementsInMemory是,存到到硬盤的什么位置
?
在hibernate.cfg.xml文件中我們還需要指定ehcache的位置
1 <!-- 設置ehcache配置文件 --> 2 <property name="net.sf.ehcache.configurationResourceName">ehcache.xml</property>
下面我看一看在硬盤中緩存的文件
?
【最后提醒】
如果對于指定了可讀性的策略是,通過HQL語句update或delete操作修改了對象后,hibernate會通知二級緩存,刪除對于該對象的緩存信息。當下一次訪問該對象時,首先訪問二級緩存,此時二級緩存中該對象根本就不存在了,所有會查詢數據庫,然后將查詢來的對象再次保存到二級緩存中。所有我們直觀的看到,二級緩存中的信息是被更改了的。
而對于一級緩存,對于update的操作,不會通知session,所有對象屬性還是在沒有保存之前的數據,如果需要查看對象的新狀態,則使用session.refresh(object);
總結
以上是生活随笔為你收集整理的Hibernate 缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery手机菜单
- 下一篇: 解决slf4j 冲突