复习 - Hibernate的缓存
复习 - Hibernate的缓存(主要是二级缓存)
对缓存做一下简单的总结和复习.
首先我们知道Hibernate的缓存有Session级别和SessionFactory级别的,也就是一级缓存和二级缓存. 顾名思义,一级缓存是不能跨session的,这样,不同的session里面,就不能共享信息,这样我们就需要跨session的二级缓存, 即可以共享不同session中的信息。 关于这个就不写例子了,记着就行了..
首先从较高的层次来总结一下缓存的作用范围.
1. 事务级别(Transaction level)
事务级别的缓存其作用范围仅限于单个Hibenate Session中,一级缓存属于事务级别的缓存
2. 进程级别(Process level)
进程级别的缓存其作用范围并不限于单个的Hibernate Session了,而是扩展至整个JVM进程. 二级缓存属于进程级别的缓存
3. 集群级别(Cluster level)
集群级别的缓存作用范围则不限于JVM,而是跨JVM,甚至跨实际机器,即分布式的缓存.
ok,事务的类型做了一下介绍,接着来看看同步策略.
什么是同步策略呢?
我们可以考虑一个问题:在进行开发时,我们指定了相应的事务隔离级别(即使没有显式的指定,也有默认的啦., P.S. 我自己觉得事务隔离级别事实上就是对数据库中的数据进行读写上的控制(实现其实就是加锁),已保证各种事务问题(如: 丢失更新,不可重复读等等)不发生). 但是现在的问题是我们开启了缓存,这个时候应用程序可以直接从缓存里面读取数据,而不用去数据库中读取,并且可以将修改后的数据写入缓存,这个时候原先的事务隔离级别肯定就有问题了,因为它控制的仅仅是数据库中的记录,而管不了缓存中的数据啊. ------ 我的理解是这样,不知道是否正确.
问题出来了,那么同步策略的作用就很明了了: 同步缓存和数据库中的数据,以保障应用程序原先设定的隔离级别.
有如下几种同步策略:
1. read-only : 仅适用于只读数据
2. Nonstrict-read-write : 不是很严格的读写, 不能保证缓存和数据库中的数据保持一致, 缓存里面的数据可能是过期的.
3. Read-write : 较为严格的读写控制, 保证read-commited的隔离级别.
4. Transactional: 可以保证repeatable read的隔离级别.
二级缓存适用的对象一般具有如下特性:
1. 很少修改
2. 非关键性数据
3. 不和其他的应用系统共享数据
4. 读取很频繁
5. 数据量不大.
一些常用的二级缓存提高者:
EHCache, Jboss Cache, OsCache, SwarmCache.
好了,需要的基础知识基本完了,下面该动手实践啦.......这里使用EHCache,步骤如下:
1. 首先是hibernate.cfg.xml里面需要加入cache提供者的相关信息:
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property>
2. 然后就是要配置ehcache,文件名为ehcache.xml,你可以把它放在classpath下面.
<ehcache> <diskStore path="c:\data\ehcache" /> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" /> </ehcache>
3. 接着就配置要使用的同步策略,之前说过的那几种,这里我使用read-write.
配置的方法有两种.
<1>. 在hibernate.cfg.xml里面配置,必须放在<mapping>后面
<class-cache usage="read-write" class="com.yxy.bean.Student"/>
<2>. 在对应实体的hbm文件里面配置,必须放在<id>之前
<class name="Student" table="student" select-before-update="true"> <cache usage="read-only"/> <id name="id" type="int"> <generator class="native" /> </id> <property name="name" type="string" column="name"/> <property name="sex" type="string" column="sex"/> <property name="registerDate" type="date" column="register_date"/> </class>
两种选一种就行了..
4. 决定使用何种CacheMode.(即决定如何去对待缓存)
总共有5种模式:
<1> CacheMode.PUT Hibernate会将从db读取出来的数据放入二级缓存,但是不会从二级缓存中拿数据,也就是只放不拿.
<2> CacheMode.NORMAL 这个是默认的行为,即需要数据时,首先去二级缓存里面查看有没有,没有的话就去db里面查找,找到后顺便把数据放入二级缓存中.
<3> CacheMode.IGNORE 无视Hiberante的二级缓存.相当于没有开启二级缓存,不会往里面放,也不会从里面拿
<4> CacheMode.GET 这个和PUT相对,这个只拿不放
<5> CacheMode.REFRESH 这个和只放不拿(PUT)类似.
5. 测试:
首先看看CacheMode为PUT时的效果..如果不出意外,应该是只会往缓存中放,而不会从缓存中取.
package com.yxy.test; import org.hibernate.CacheMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.stat.Statistics; import com.yxy.bean.Student; public class HibernateCacheTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * 保证之前二级缓存中的student数据都被清理掉 */ sessionFactory.evict(Student.class); Session session = sessionFactory.openSession(); /* * 设置CacheMode */ session.setCacheMode(CacheMode.PUT); Statistics stat = sessionFactory.getStatistics(); session.getTransaction().begin(); stat.setStatisticsEnabled(true); Student s1 = (Student)session.get(Student.class, 1); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); Student s2 = (Student)session.get(Student.class, 2); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); stat.setStatisticsEnabled(false); session.getTransaction().commit(); session.close(); } }
输出的结果:
Hibernate: /* load com.yxy.bean.Student */ select student0_.id as id0_0_, student0_.name as name0_0_, student0_.sex as sex0_0_, student0_.register_date as register4_0_0_ from student student0_ where student0_.id=? 0 0 1 Hibernate: /* load com.yxy.bean.Student */ select student0_.id as id0_0_, student0_.name as name0_0_, student0_.sex as sex0_0_, student0_.register_date as register4_0_0_ from student student0_ where student0_.id=? 0 0 2
从输出我们可以看到,Hiberante并不会尝试去二级缓存里面查找: System.out.println(stat.getSecondLevelCacheMissCount())为0, 但是一旦从db里面找出数据,是会把数据放入二级缓存的.
接下来可以测试一下默认情况,也就是CacheMode.NORMAL.
例子如下,会有两个session:
package com.yxy.test; import org.hibernate.CacheMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.stat.Statistics; import com.yxy.bean.Student; public class HibernateCacheTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * 保证之前二级缓存中的student数据都被清理掉 */ // sessionFactory.evict(Student.class); Session session = sessionFactory.openSession(); /* * 设置CacheMode */ session.setCacheMode(CacheMode.NORMAL); Statistics stat = sessionFactory.getStatistics(); session.getTransaction().begin(); stat.setStatisticsEnabled(true); Student s1 = (Student)session.get(Student.class, 1); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); Student s2 = (Student)session.get(Student.class, 2); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); stat.setStatisticsEnabled(false); session.getTransaction().commit(); session.close(); /* * 开启另外一个session */ System.out.println("------------------ another session -----------------"); session = sessionFactory.openSession(); /* * 设置CacheMode */ session.setCacheMode(CacheMode.NORMAL); stat = sessionFactory.getStatistics(); stat.clear(); session.getTransaction().begin(); stat.setStatisticsEnabled(true); s1 = (Student)session.get(Student.class, 1); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); s2 = (Student)session.get(Student.class, 2); System.out.println(stat.getSecondLevelCacheHitCount()); System.out.println(stat.getSecondLevelCacheMissCount()); System.out.println(stat.getSecondLevelCachePutCount()); stat.setStatisticsEnabled(false); session.getTransaction().commit(); session.close(); } }
输出的情况如下:
Hibernate: /* load com.yxy.bean.Student */ select student0_.id as id0_0_, student0_.name as name0_0_, student0_.sex as sex0_0_, student0_.register_date as register4_0_0_ from student student0_ where student0_.id=? 0 1 1 Hibernate: /* load com.yxy.bean.Student */ select student0_.id as id0_0_, student0_.name as name0_0_, student0_.sex as sex0_0_, student0_.register_date as register4_0_0_ from student student0_ where student0_.id=? 0 2 2 ------------------ another session ----------------- 1 0 0 2 0 0
在第一个session中,由于默认的会首先去二级缓存中找数据,结果是: 命中0次,miss两次,放入两次. 这时二级缓存中已经存在id为1,2的Student的数据了,
在第二个session中,由于之前缓存的数据,所以两次都命中了,而且不会发出sql从db里面取数据. 这就是二级缓存的效果!
其他的几种CacheMode也可以做类似的测试,就不在这里弄了.
最后有一个值得注意的知识点: 查询缓存
上面的这些查询,基本上都是通过主键(ID)来获得一个实体,然后存入缓存,其实对于一堆数据,也是可以存入二级缓存的,即查询缓存.
对于查询缓存,当结果集很大的时候,也需要谨慎使用,因为毕竟它使用的是内存.
开启查询缓存的设置(hibernate.cfg.xml)
<property name="hibernate.cache.use_query_cache">true</property>
具体的示例就不在这里写了. Go for lunch first....