Hibernate 緩存機制初探

 

1.緩存簡述

Hibernate緩存分兩級緩存java

一級session緩存,就是常說的一級緩存;二級應用緩存(二級緩存);web

一級緩存,一級緩存依賴於session,在一個session中就是一個緩存,當session失效時,緩存消失。spring

public void loadBookAgain(){ 
Session session = HibernateSessionFactory.getSession(); 
Book book1 = (Book) session.get(Book.class, 6); 
Book book2 = (Book) session.get(Book.class, 6); 
session.close(); 
// Session session1 = HibernateSessionFactory.getSession(); 
// Book book2 = (Book) session1.get(Book.class, 6); 
// session1.close(); 
}

在一個session裏面查詢兩次相同的book,只會執行一次sql。 
但若放在不一樣的session中,將會執行兩次數據庫查詢。 sql

解決不一樣session之間的緩存問題的辦法就是用二級緩存。數據庫

下面這幾種狀況就不適合加載到二級緩存中: 緩存

1.常常被修改的數據 session

2.絕對不容許出現併發訪問的數據 併發

3.與其餘應用共享的數據 ide

 

下面這己種狀況合適加載到二級緩存中: spa

 

1.數據更新頻率低 

2.容許偶爾出現併發問題的非重要數據 

3.不會被併發訪問的數據 

4.常量數據 

5.不會被第三方修改的數據 

2.配置二級緩存ehcahe

配置二級緩存比較簡單,以ehcache爲例: 

添加緩存文件ehcache-hibernate-local.xml 

<?xml version="1.0" encoding="UTF-8"?> 
<ehcache> 
<diskStore path="java.io.tmpdir/hibernate/book" /> 
<defaultCache maxElementsInMemory="10000" overflowToDisk="true" eternal="false" 
memoryStoreEvictionPolicy="LRU" maxElementsOnDisk="10000000" 
diskExpiryThreadIntervalSeconds="600" 
timeToIdleSeconds="3600" timeToLiveSeconds="100000" diskPersistent="false" /> 
<!-- Special objects setting. --> 
<cache name="bean.entity.Book" maxElementsInMemory="500" overflowToDisk="true" 
eternal="true"> 
</cache> 
</ehcache>

maxElementsInMemory爲緩存對象的最大數目, 

eternal設置是否永遠不過時, 

timeToIdleSeconds對象處於空閒狀態的最多秒數, 

timeToLiveSeconds對象處於緩存狀態的最多秒數 。 

在實體bean的hbm.xml文件中加上緩存配置:

<!-- 設置該持久化類的二級緩存併發訪問策略 read-only read-write 
nonstrict-read-write transactional--> 
<cache usage="read-write" />

如今大部分的hibernate應用再也不寫實體映射配置文件,那麼就在實體bean中加上 

//默認的緩存策略. 

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

在hibernate定義sessionFactory中加上查詢緩存配置: 

<!-- 設置二級緩存插件EHCache的Provider類--> 
<property name="hibernate.cache.provider_class"> 
org.hibernate.cache.EhCacheProvider 
</property>  
<!-- 啓動"查詢緩存" --> 
<property name="hibernate.cache.use_query_cache">true</property> 
<property 
name="hibernate.cache.provider_configuration_file_resource_path"> 
/ehcache-hibernate-local.xml
</property>

 若是項目試用了spring,那麼相應配置爲: 

<property name="hibernateProperties"> 
<props> 
<prop key="hibernate.dialect">${hibernate.dialect}</prop> 
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop> 
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop> 
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> 
<prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache-hibernate-local.xml</prop> 
</props> 
</property>

兩種配置基本一致。 

這個時候在按實體的ID來查詢的時候即便不在一個session中,hibernate也只是執行一次sql。 

查詢緩存作到如今,有一點效果,但基本仍是個擺設。

public void listBookTwice(){ 
String hql = "from Book"; 
Session session = HibernateSessionFactory.getSession(); 
Query q = session.createQuery(hql); 
List<Book> list1 = q.list(); 
List<Book> list2 = q.list(); 
session.close();

同一個query。list了兩次,按照以前的效果,應該是執行一個sql。事實是,他要去查兩次, 

在一個query尚且如此,兩個不用說,確定也是沒有用到緩存了。 

難道緩存失效了?呵呵,實際上是由於咱們雖然配置了緩存,可是在query級卻沒有設置緩存,若是須要query緩存, 

則須要手工寫入: 

q.setCacheable(true);來激活查詢緩存。 

修改代碼以下:

public void listBookTwice(){ 
String hql = "from Book"; 
Session session = HibernateSessionFactory.getSession(); 
Query q = session.createQuery(hql); 
q.setCacheable(true); 
List<Book> list1 = q.list(); 
for(Book b : list1){ 
System.out.println(b.getBname()+"--------->list1"); 
} 
// List<Book> list2 = q.list(); 
session.close(); 
Session session2 = HibernateSessionFactory.getSession(); 
Query q2 = session2.createQuery(hql); 
q2.setCacheable(true); 
List<Book> list2 = q2.list(); 
for(Book b : list2){ 
System.out.println(b.getBname()+"--------->list2"); 
} 
session2.close(); 
}

在兩個session立分別list查詢,ok,只輸出一條sql。說明二級緩存在list查詢的時候也起做用了。 

3.hibernate是根據什麼來緩存list

對於查詢緩存來講,緩存的key是根據hql生成的sql,再加上參數,分頁等信息(能夠經過日誌輸出看到,不過它的輸出不是很可讀,最好改一下它的代碼)。 

好比hql: 

from Cat c where c.name like ? 

生成大體以下的sql: 

select * from cat c where c.name like ? 

參數是"tiger%",那麼查詢緩存的key*大約*是這樣的字符串(我是憑記憶寫的,並不精確,不過看了也該明白了): 

select * from cat c where c.name like ? , parameter:tiger% 

這樣,保證了一樣的查詢、一樣的參數等條件下具備同樣的key。 

如今說說緩存的value,若是是list方式的話,value在這裏並非整個結果集,而是查詢出來的這一串ID。 

也就是說,不論是list方法仍是iterate方法,第一次查詢的時候,它們的查詢方式很它們平時的方式是同樣的, 

list執行一條sql,iterate執行1+N條,多出來的行爲是它們填充了緩存。可是到一樣條件第二次查詢的時候,就都和iterate的行爲同樣了, 根據緩存的key去緩存裏面查到了value,value是一串id,而後在到class的緩存裏面去一個一個的load出來。這樣作是爲了節約內存。 

能夠看出來,查詢緩存須要打開相關類的class緩存。list和iterate方法第一次執行的時候,都是既填充查詢緩存又填充class緩存的。 

這裏還有一個很容易被忽視的重要問題,即打開查詢緩存之後,即便是list方法也可能遇到1+N的問題!相同條件第一次list的時候, 由於查詢緩存中找不到,無論class緩存是否存在數據,老是發送一條sql語句到數據庫獲取所有數據,而後填充查詢緩存和class緩存。 

可是第二次執行的時候,問題就來了,若是你的class緩存的超時時間比較短,如今class緩存都超時了,可是查詢緩存還在, 

 那麼list方法在獲取id串之後,將會一個一個去數據庫load!所以,class緩存的超時時間必定不能短於查詢緩存設置的超時時間 

 !若是還設置了發呆時間的話,保證class緩存的發呆時間也大於查詢的緩存的生存時間。這裏還有其餘狀況,好比class緩存被程序強制evict了, 

這種狀況就請本身注意了。 

另外,若是hql查詢包含select字句,那麼查詢緩存裏面的value就是整個結果集了。 

能夠理解爲hibernate緩存了每次查詢的hql語句做爲緩存map的key,將對應對象的id做爲value緩存,每次遇到相同的hql,就將id取出來, 若是在緩存裏面有對象,就從緩存取,沒有的話就去數據庫load 

緩存何時更新呢? 

public void update(){ 
Session session = HibernateSessionFactory.getSession(); 
Transaction tran = session.beginTransaction(); 
tran.begin(); 
Book b = (Book) session.get(Book.class, 7); 
b.setIsbn("567890"); 
session.saveOrUpdate(b); 
tran.commit(); 
session.close(); 
} 
public void listBookTwice(){ 
String hql = "from Book"; 
Session session = HibernateSessionFactory.getSession(); 
Query q = session.createQuery(hql); 
q.setCacheable(true); 
List<Book> list1 = q.list(); 
for(Book b : list1){ 
System.out.println(b.getBname()+"----"+b.getIsbn()+"--------->list1"); 
} 
session.close(); 
} 
public void listBookAgain(){ 
String hql = "from Book"; 
Session session2 = HibernateSessionFactory.getSession(); 
Query q2 = session2.createQuery(hql); 
q2.setCacheable(true); 
List<Book> list2 = q2.list(); 
for(Book b : list2){ 
System.out.println(b.getBname()+"----"+b.getIsbn()+"--------->list3"); 
} 
session2.close(); 
} 
public static void main(String[] args) { 
BookDao dao = new BookDao(); 
dao.listBookTwice(); 
dao.update(); 
dao.listBookAgain(); 
}

先list一次,之間更新一個book,第二次list,輸出: 

Hibernate: select book0_.id as id0_, book0_.bname as bname0_, book0_.isbn as isbn0_, book0_.price as price0_ from hibernate_test.dbo.book book0_ 
book1250672666171----123456--------->list1
book1250672666203----123456--------->list1
book1250672666203----123456--------->list1
book1250672666203----123456--------->list1
Hibernate: update hibernate_test.dbo.book set bname=?, isbn=?, price=? where id=? 
Hibernate: select book0_.id as id0_, book0_.bname as bname0_, book0_.isbn as isbn0_, book0_.price as price0_ from hibernate_test.dbo.book book0_ 
book1250672666171----123456--------->list3 
book1250672666203----567890--------->list3 
book1250672666203----123456--------->list3 
book1250672666203----123456--------->list3 
book1250672666203----123456--------->list3
可見,當數據庫有更新的時候,緩存就失效了。 
相關文章
相關標籤/搜索