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
可见,当数据库有更新的时候,缓存就失效了。 
相关文章
相关标签/搜索