下面这几种情况就不适合加载到二级缓存中:
1.经常被修改的数据
2.绝对不允许出现并发访问的数据
3.与其他应用共享的数据
下面这己种情况合适加载到二级缓存中:
1.数据更新频率低
2.允许偶尔出现并发问题的非重要数据
3.不会被并发访问的数据
4.常量数据
5.不会被第三方修改的数据
Hibernate有3种缓存:一级缓存,二级缓存,查询缓存
打开二级缓存
i. hibernate.cfg.xml设定:
<property name="cache.use_second_level_cache">true</property>
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
ii. @Cache注解
启用查询缓存:
1.hibernate.cfg.xml中配置:<property name="cache.use_query_cache">true</property>
2.代码中用setCacheable(true)手动启用
只对list()方法起作用
默认情况下list()每次都会发SQL语句,只有配置了查询缓存它才会利用缓存。
二级缓存是SessionFactory级别的全局缓存,它为每个类(或集合)提供缓存。凡是调用二级缓存的查询方法都会从中受益,比如load,list,iterate等方法(注意,get和find不使用缓存,直接访问数据库)。load默认使用二级缓存,iterate默认使用二级缓存,list默认往二级缓存加数据,但是查询时不使用,如果查询时要用二级缓存,需打开查询缓存
查询缓存的实现机制与二级缓存基本一致,最大的差异在于放入缓存中的key是查询的语句,value是查询之后得到的结果集的id列表。表面看来这 样的方案似乎能解决hql利用缓存的问题,但是需要注意的是,构成key的是:hql生成的sql、sql的参数、排序、分页信息等。 也就是说如果你的 hql有小小的差异,比如第一条hql取1-50条数据,第二条hql取20-60条数据,那么hibernate会认为这是两个完全不同的key,无法重复利用缓存。因此利用率也不高。
Hibernate中的Cache大致分为两层,第一层Cache在Session实现,属于事务级数据缓冲,一旦事务结束,这个Cache 也就失效。 此层Cache 为内置实现,无需我们进行干涉。第二层Cache,是Hibernate 中对其实例范围内的数据进行缓存的管理容器。
Hibernate早期版本中采用了JCS(Java Caching System -Apache Turbine项目中的一个子项目)作为默认的第二层Cache实现。由于JCS的发展停顿,以及其内在的一些问题(在某些情况下,可能导致内存泄漏以及 死锁),新版本的Hibernate已经将JCS去除,并用 EHCache作为其默认的第二级Cache实现。相对JCS,EHCache更加稳定,并具备更好的缓存调度性能,缺陷是目前还无法做到分布式缓存,如果我们的系统需要在多台设备上部署,并共享同一个数据库,必须使用支持分布式缓存的Cache 实现(如JCS、JBossCache)以避免出现不同系统实例之间缓存不一致而导致脏数据的情况。Hibernate对Cache进行了良好封装,透明化的Cache机制使得我们在上层结构的实现中无需面对繁琐的Cache维护细节。
目前Hibernate支持的Cache实现有:
HashTable:net.sf.hibernate.cache.HashtableCacheProvider 支持查询缓冲。EHCache:net.sf.ehcache.hibernate.Provider 支持查询缓冲。
OSCache:net.sf.hibernate.cache.OSCacheProvider 支持查询缓冲。
SwarmCache:net.sf.hibernate.cache.SwarmCacheProvider 支持集群。
JBossCache:net.sf.hibernate.cache.TreeCacheProvider 支持集群。
其中SwarmCache 提供的是invalidation 方式的分布式缓存,即当集群中的某个节点更新了缓存中的数据,即通知集群中的其他节点将此数据废除,之后各个节点需要用到这个数据的时候,会重新从数据库 中读入并填充到缓存中。而JBossCache提供的是Reapplication式的缓冲,即如果集群中某个节点的数据发生改变,此节点会将发生改变的 数据的最新版本复制到集群中的每个节点中以保持所有节点状态一致。
cache策略可选值有以下几种:
1. read-only 只读。2. read-write 可读可写。
3. nonstrict-read-write 如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据,可以采用本选项,以减少无谓的检查,获得较好的性能。
4. transactional 事务性cache。在事务性Cache 中,Cache 的相关操作也被添加到事务之中,如果由于某种原因导致事务失败,我们可以连同缓冲池中的数据一同回滚到事务开始之前的状态。目前Hibernate 内置的Cache 中,只有JBossCache 支持事务性的Cache实现。
其他参数简介:
maxElementsInMemory="10000" //Cache中最大允许保存的数据数量eternal="false" //Cache中数据是否为常量
timeToIdleSeconds="120" //缓存数据钝化时间
timeToLiveSeconds="120" //缓存数据的生存时间
overflowToDisk="true" //内存不足时,是否启用磁盘缓存
配置文件举例
PO类的****.hbm.xml文件中要加载的配置
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class>
<!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional-->
<cache usage="read-write"/>
</class>
</hibernate-mapping>
在hibernate.cfg.xml配置文件中要加载的配置:
<hibernate-configuration>
<session-factory>
<!-- 设置二级缓存插件EHCache的Provider类-->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 启动"查询缓存" -->
<property name="hibernate.cache.use_query_cache">true</property>
</session-factory>
</hibernate-configuration>
在最后我们还要给ehcache.xml配置一个单独的配置文件,不要把他们写到CFG中会马上报错的哦:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
<cache name="person.Person" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false" />
</ehcache>
需要注意的是Hibernate 的数据库查询机制。我们从查询结果中取出数据的时候,用的最多的是两个方法: Query.list();Query.iterate();
iterate()使用到二级缓存,list()需要结合查询缓存使用.
对于list方法而言,实际上Hibernate是通过一条Select SQL获取所有的记录。并将其读出,填入到POJO中返回。而iterate 方法,则是 首先通过一条Select SQL 获取所有符合查询条件的记录的id,再对这个id 集合进行循环操作,通过单独的Select SQL 取出每个id 所对应的记录,之后填入POJO中返回。也就是说,对于list 操作,需要一条SQL 完成。而对于iterate 操作,需要n+1条SQL。
看上去iterate方法似乎有些多余,但在不同的情况下确依然有其独特的功效,如对海量数据的查询,如果用list方法将结果集一次取出,内存 的开销可能无法承受。另一方面,对于我们现在的Cache机制而言,list方法将不会从Cache中读取数据,它总是一次性从数据库中直接读出所有符合条件的记录。而iterate方法因为每次根据id获取数据,这样的实现机制也就为从Cache读取数据提供了可能,hibernate首先会根据这个id 在本地Cache内寻找对应的数据,如果没找到,再去数据库中检索。
这样大家可能会想,同样的查询条件,第一次先list,第二次再iterate,就可以使用到缓存了。实际上这是很难的,因为你无法判断什么时候是第一 次,而且每次查询的条件通常是不一样的,假如数据库里面有100条记录,id从1到100,第一次list的时候出了前50个id,第二次iterate 的时候却查询到30至70号id,那么30-50是从缓存里面取的,51到70是从数据库取的,共发送1+20条sql。所以我一直认为iterate没有什么用,总是会有1+N的问题。
二级缓存和查询缓存都相当于一个map。
二级缓存缓存的key为id,value为实体对象。一般load(),iterate()使用到二级缓存,list()需要结合查询缓存使用。iterate()和list() 区别如下:
iterate()不需要开启查询缓存,它首先发出一个sql如”select s.id from Student s”去数据库把id属性列表取出来, 然后再根据id列表一个一个load(),如果缓存有从缓存取,如果缓存没有就从数据库取:select s.id,s.name,s.classid from Student s where s.id=?,取出后再存入二级缓存。 Iterate总会发出取id列表的语句。
List()需要开启查询缓存,它首先发出一个sql如”select s.id,s.name,s.classid from Student s…”去数据库取出所有相关实体,并将这些实体存入二级缓存, 将此sql语句及一些相关信息作为key,id列表作为值,第二次查询这条语句时就会去根据 sql语句及相关信息去key里找,如果有就会把id列表取出一个一个load(),接下来就和iterate一样了。List一般只有第一次发发出取实 体列表的语句,以后的id列表就会去查询缓存取id列表, 不会再发出sql语句。
前提:执行同一hql语句,如:select s from Student s
1.关闭查询缓存,开启二级缓存时:
第二次查询属性时iterate只会发出获取id列表的sql,list会发出和第一次一样的请求实体的sql。
2.开启查询缓存,开启二级缓存
第二次查询属性时iterate只会发出获取id列表的sql,list不发sql。
以上说明iterate只和二级缓存有关,list和二级缓存和查询缓存都有关。
(题外话:有说法说大型查询用list会把整个结果集装入内存,很慢,而iterate只select id比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页20条的话,iterate共需要执行21条语句,list虽然选择若干字段,比iterate第一条select id语句慢一些,但只有一条语句,不装入整个结果集hibernate还会根据数据库方言做优化,比如使用mysql的limit,整体看来应该还是list快.)
Hibernate的缓存管理
clear() 将一级缓存中的所有持久化对象清除,释放其占用的内存资源
contains(Object obj) 判断指定的对象是否存在于一级缓存中.
flush() 刷新一级缓存区的内容,使之与数据库数据保持同步.
二级缓存的管理:
evict(Class arg0) 将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源.
sessionFactory.evict(Customer. class);
evictCollection(String arg0) 将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源.
比较好的做法是在高层次中(业务逻辑层面),针对具体的业务逻辑状况手动使用数据缓存,不仅可以完全控制缓存的生命周期,还可以针对业务具体调整缓存方案提交命中率。