我們繼續來說hibernate的緩存,先來探討一下N+1的問題。
【問題情況】
1)一對多(one-to-many),在1的這方,通過1條sql查找得到了1個對象,由於關聯的存在,那麼又需要將這個對
象關聯的集合取出,所以合集數量是n還要發出n條sql,於是本來的1條sql查詢變成了1 +n條 .
2)多對一<many-to-one> ,在多的這方,通過1條sql查詢得到了n個對象,由於關聯的存在,也會將這n個對象對
應的1方的對象取出,於是本來的1條sql查詢變成了1+n條 .
3)iterator查詢時,通過iterator()方法來獲得我們對象的時候,hibernate首先會發出1條sql去查詢出所有對
象的id值,當我們如果需要查詢到某個對象的具體信息的時候,hibernate此時會根據查詢出來的id值再發sql
語句去從數據庫中查詢對象的信息,這就是典型的N+1的問題。
【解決辦法】
1)lazy=true,hibernate3開始已經默認是lazy=true了;lazy=true時不會立刻查詢關聯對象,只有當需要關聯對
象(訪問其屬性,非id字段)時纔會發生查詢動作。
2)使用二級緩存,二級緩存的應用將不怕1+N 問題,因爲即使第一次查詢很慢(未命中),以後查詢直接緩存命
中也是很快的。剛好又利用了1+N .
3)當然你也可以設定fetch="join",一次關聯表全查出來,但失去了延遲加載的特性。
【二級緩存】
我們上篇文章說了一級緩存是session級別的,當session關閉了,緩存也就不存在了。所以爲了提高我們的
查詢效率,我們需要手動配置二級緩存(sessionFactory級別)的緩存。
1.下載.
hibernate並沒有提供相應的二級緩存的組件,所以需要加入額外的二級緩存包,常用的二級緩存包
EHcache。下載之後將裏面的幾個jar包導入即可。
2.配置屬性
在hibernate.cfg.xml配置文件中配置我們二級緩存的一些屬性:
<span style="font-size:18px;"><!-- 開啓二級緩存 -->
<propertyname="hibernate.cache.use_second_level_cache">true</property>
<propertyname="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 二級緩存配置文件的位置 -->
<propertyname="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
</span>
3.配置緩存信息
配置hibernate的二級緩存是通過使用ehcache的緩存包,所以我們需要創建一個ehcache.xml的配置文件,
來配置我們的緩存信息,將其放到項目根目錄下。
<span style="font-size:18px;"><ehcache>
<!--指定二級緩存存放在磁盤上的位置-->
<diskStorepath="user.dir"/>
<!--我們可以給每個實體類指定一個對應的緩存,如果沒有匹配到該類,則使用這個默認的緩存配置-->
<defaultCache
maxElementsInMemory="10000" //在內存中存放的最大對象數
eternal="false" //是否永久保存緩存,設置成false
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" //如果對象數量超過內存中最大的數,是否將其保存到磁盤中,設置成true
/>
<!--
1、timeToLiveSeconds的定義是:以創建時間爲基準開始計算的超時時長;
2、timeToIdleSeconds的定義是:在創建時間和最近訪問時間中取出離現在最近的時間作爲基準計算的超時時長;
3、如果僅設置了timeToLiveSeconds,則該對象的超時時間=創建時間+timeToLiveSeconds,假設爲A;
4、如果沒設置timeToLiveSeconds,則該對象的超時時間=max(創建時間,最近訪問時間)+timeToIdleSeconds,假設爲B;
5、如果兩者都設置了,則取出A、B最少的值,即min(A,B),表示只要有一個超時成立即算超時。
-->
<!--可以給每個實體類指定一個配置文件,通過name屬性指定,要使用類的全名-->
<cache name="com.wyj.bean.Student"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cachename="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/> -->
</ehcache></span>
4.開啓二級緩存
需要在Student.hbm.xml中加上一下配置:
<hibernate-mappingpackage="com.wyj.bean">
<class name="Student"table="t_student">
<!-- 二級緩存一般設置爲只讀的-->
<cacheusage="read-only"/>
<id name="id"type="int" column="id">
<generator class="native"/>
</id>
<propertyname="name" column="name" type="string"/>
<propertyname="sex" column="sex" type="string"/>
<many-to-one name="room"column="rid" fetch="join"/>
</class>
</hibernate-mapping>
二級緩存的使用策略一般有這幾種:read-only、nonstrict-read-write、read-write、transactional。注
意:我們通常使用二級緩存都是將其配置成read-only ,即我們應當在那些不需要進行修改的實體類上使用二級
緩存,否則如果對緩存進行讀寫的話,性能會變差,這樣設置緩存就失去了意義。
5.測試運行(解決N+1問題)
<span style="font-size:18px;">@Test
public void testCache13()
{
Session session =null;
try
{
session =HibernateUtil.openSession();
/**
*將查詢出來的Student對象緩存到二級緩存中去
*/
List<Student> stus =(List<Student>) session.createQuery(
"select stu fromStudent stu").list();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
*由於學生的對象已經緩存在二級緩存中了,此時再使用iterate來獲取對象的時候,首先會通過一條
*取id的語句,然後在獲取對象時去二級緩存中,如果發現就不會再發SQL,這樣也就解決了N+1問題
* 而且內存佔用也不多
*/
session =HibernateUtil.openSession();
Iterator<Student>iterator = session.createQuery("from Student")
.iterate();
for (;iterator.hasNext();)
{
Student stu = (Student)iterator.next();
System.out.println(stu.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
</span>
注:
1.二級緩存緩存的僅僅是對象,如果查詢出來的是對象的一些屬性,則不會被加到緩存中去
2.二級緩存不會緩存我們的hql查詢語句。需要配置查詢緩存纔會緩存hql語句。
【查詢緩存】
我們如果要配置查詢緩存,只需要在hibernate.cfg.xml中加入一條配置即可:
<span style="font-size:18px;"><!-- 開啓查詢緩存-->
<propertyname="hibernate.cache.use_query_cache">true</property>
然後我們如果在查詢hql語句時要使用查詢緩存,就需要在查詢語句後面設置這樣一個方法:
List<Student>ls = session.createQuery("from Student where name like ?")
.setCacheable(true)//開啓查詢緩存,查詢緩存也是SessionFactory級別的緩存
.setParameter(0,"%王%")
.setFirstResult(0).setMaxResults(50).list();</span>
注:
1.只有當 HQL查詢語句完全相同時,連參數設置都要相同,此時查詢緩存纔有效
2.和一級/二級緩存不同,查詢緩存的生命週期 ,是不確定的,當前關聯的表發生改變時,查詢緩存的生命週期
結束。
【總結】
選擇合適的緩存策略,可以提高系統的性能。我們都知道一級緩存對性能提高沒有太大的意義,因爲生命
周期太短了。查詢緩存意義不是很大,查詢緩存說明白了就是存放由list方法或者iterate方法查詢的數據,我們
在查詢時很少出現完全相同的條件查詢,這就是說命中率低。也就是二級緩存纔是需要我們重點學習的,二級緩
存由SessionFactory對象管理,是應用級別的緩存。它可以緩存整個應用的持久化對象。