把常用数据库持久化对象,作为缓存保存在内存中,减少与数据库交互次数,可以提高性能。
一级缓存:session级别的缓存,属于事务范围。hibernate框架内置。
二级缓存:sessionFactory级别的缓存,属于进程范围的缓存。需引入外部缓存插件并配置。
二级缓存提供商:
1》EHCache
2》OSCache
3》Swarm Cache
4》JBOSS Cache
EHCache、OSCache主要用于单机数据库软件,SwarmCache和JBossCache主要用于集群环境。
二级缓存四个部分组成:
1》类级别缓存区
2》集合级别的缓存区
3》更新时间戳
4》查询缓存(三级缓存)
二级缓存并发策略,与事务隔离级别相对应:
1》非严格读写(nonstrict-read-write) -------- read uncommitted
2》读写型(read-write) ----------------------- read committed
3》事务型(transactional) -------------------- repeatable read
4》只读型(read-only) ------------------------ serializable
数据库中极少被改动的数据,比较适用于放入二级缓存。
本文使用EHCache进行Hibernate框架二级缓存。
解压hibernate后从lib/optional/ehcache得到
(1)开启二级缓存:
<hibernate-configuration> <session-factory> <!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property>
(2)配置hibernate.cfg.xml 缓存的供应商:
<hibernate-configuration> <session-factory> <!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- hibernate3配置:指明缓存供应商 --> <!-- <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider </property> --> <!-- hibernate4配置:指明缓存供应商 --> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory </property>
(3)指定对哪些数据应用二级缓存:
有两种配置方式POJO.hbm.xml配置,和hibernate.cfg.xml配置。
例:在hibernate.cfg.xml配置,<class-cache>类级别缓存;<collection-cache>集合级别缓存
<hibernate-configuration> <session-factory> <!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 指明缓存供应商 --> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory </property> <!-- property、mapping等配置在中间 --> <!-- 类级别配置:指明使用二级缓存的类: usage=二级缓存并非策略 class=对应的POJO --> <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoUser"/> <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoOrder"/> <!-- 集合级别配置:指明使用二级缓存的集合: usage=二级缓存并非策略 collection=对应POJO中的集合变量 --> <collection-cache usage="read-write" collection="cn.cvu.hibernate.domain.PojoUser.orders"/> </session-factory> </hibernate-configuration>
在src下创建,主要用来配置EHCache插件的属性。可以将hibernate解压的/project/etc/ehcache.xml复制到src下进行修改。
(1)开启状态:
public void testCache(){ //二级缓存是SessionFactory级别的,可以被多个Session共享。 /*** 第一个Session **/ Session session1 = UtilGetSession.openSession(); Transaction transaction1 = session1.beginTransaction(); //存入一级缓存和二级缓存 PojoUser user1 = (PojoUser) session1.get(PojoUser.class, 1); System.out.println(user1.hashCode()); //直接使用一级缓存中存在的数据 PojoUser user2 = (PojoUser) session1.get(PojoUser.class, 1); System.out.println(user2.hashCode()); transaction1.commit(); session1.close(); /*** 第二个Session,不产生新的sql语句 **/ Session session2 = UtilGetSession.openSession(); Transaction transaction2 = session2.beginTransaction(); //使用二级缓存中的数据在新的一级缓存封装数据 PojoUser user3 = (PojoUser) session2.get(PojoUser.class, 1); System.out.println(user3.hashCode()); //使用新一级缓存中的数据 PojoUser user4 = (PojoUser) session2.get(PojoUser.class, 1); System.out.println(user4.hashCode()); transaction2.commit(); session2.close(); /** 以上操作仅执行一条sql **/ }
(2)关闭状态:
(1)散装数据的存储:每次使用二级缓存,将获得一个新的对象:
(2)Query接口可以将数据放置到类级别的二级缓存中,但是不能使用query接口的list方法从缓存中获取数据,能存不能取:
@Test public void testCache(){ Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); //Query接口可以将数据放置到类级别的二级缓存中,但是不能使用list方法从缓存中获取数据 Query query1 = session.createQuery("from PojoUser"); //Query可以向二级缓存存入数据 List<PojoUser> list1 = query1.list(); //生成第一条sql System.out.println(list1); transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); //直接从二级缓存获取,无sql生成 PojoUser user = (PojoUser) session.get(PojoUser.class, 2); System.out.println("直接读取二级缓存:" + user); transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); Query query2 = session.createQuery("from PojoUser"); //Query不能从二级缓存读取数据 List<PojoUser> list2 = query2.list(); //因为不能读,生成第2条sql System.out.println(list2); transaction.commit(); session.close(); }
(1)测试集合缓存:
public void testCacheList() { Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); PojoUser user1 = (PojoUser) session.get(PojoUser.class, 1); System.out.println("第1次查询集合:" + user1.getOrders().size()); // 存入二级缓存 transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); PojoUser user2 = (PojoUser) session.get(PojoUser.class, 1); System.out.println("第2次查询集合:" + user2.getOrders().size()); // 从二级缓存取数据 for (PojoOrder order : user2.getOrders()) { // 从二级缓存取数据 System.out.println("订单:" + order.getName()); } transaction.commit(); session.close(); }
(2)注释 <collection-cache/> 测试:
<hibernate-configuration> <session-factory> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- property、mapping等配置在中间 --> <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoUser"/> <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoOrder"/> <!-- 注释掉集合配置 --> <!-- <collection-cache usage="read-write" collection="cn.cvu.hibernate.domain.PojoUser.orders"/> --> </session-factory> </hibernate-configuration>
(3)注释<class-cache class="PojoOrder"/>测试:
<hibernate-configuration> <session-factory> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- property、mapping等配置在中间 --> <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoUser"/> <!-- 注释集合所保存的数据类 <class-cache usage="read-write" class="cn.cvu.hibernate.domain.PojoOrder"/> --> <collection-cache usage="read-write" collection="cn.cvu.hibernate.domain.PojoUser.orders"/> </session-factory> </hibernate-configuration>
(4)集合级别缓存存放的是保存在类级别缓冲区中的数据对象的OID:
当一级缓存中的数据发生更新,会自动同步到二级缓存。
public void testCacheList() { Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); PojoUser user1 = (PojoUser) session.get(PojoUser.class, 1); System.out.println("第1次:" + user1.getName()); // 存入一级缓存和二级缓存 user1.setName("二级缓存!");//从一级缓存更新到二级缓存,也更新到数据库 transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); PojoUser user2 = (PojoUser) session.get(PojoUser.class, 1); System.out.println("第2次:" + user2.getName()); // 从二级缓存取数据 transaction.commit(); session.close(); }
配置ehcache.xml文件:
默认配置对全局有效。如需要可单独配置对应POJO的<cache>,<cache name="cn.itcast.domain.POJO" name="对应POJO" 其它属性...> 。
在hibernate.cfg.xml中缓存全部POJO,查询大量数据(超过defaultCache的maxElementsInMemory,并且overflowToDisk为true),在diskStore配置的目录查看是否有缓存文件。
相关:org.hibernate.cache.spi.UpdateTimestampsCache
作用:保证二级缓存数据有效性。
情景:事务1查询了数据,保存到二级缓存,当事务1再次使用本线程的二级缓存前,有事务2更新了数据库,导致事务1的二级缓存数据是无效的。更新时间戳在二级缓存开辟一个区域,保存每次读写的时间,当执行查询时先对比时间戳区内的版本,如果大于类缓存区中的版本,就重新查询数据库。
public void testTimer(){ Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); //存入一级缓存和二级缓存 PojoUser user1 = (PojoUser) session.get(PojoUser.class, 2); //创建时间戳t1 System.out.println(user1); //直接修改数据库,模拟多用户访问 session.createQuery("update PojoUser set name='猫王' where id = 2").executeUpdate(); //创建时间戳t2 transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); //读取二级缓存,首先检查时间戳,如果有异,重新查询数据库 PojoUser user2 = (PojoUser) session.get(PojoUser.class, 2); //判断t2大于t1 System.out.println(user2); transaction.commit(); session.close(); }
注意:hibernate能感知的操作才会创建时间戳。
list方法只能写,不能读二级缓存。
Query的iterate方法返回List集合的迭代器对象Iterator。使用Query的iterate方法遍历时,集合中保存的是只有OID的代理对象,当访问对象的其它属性时,才进行初始化。初始化时,优先查找二级缓存;如果缓存中不存在,则生成SQL语句查询数据库。
当使用二级缓存时,iterate比list 效率要好。
public void testTimer(){ Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); //3条记录存入二级缓存 session.createQuery("from PojoOrder where id <= 3").list(); transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); //读取二级缓存,获得3条,剩余2条执行sql Iterator iterate = session.createQuery("from PojoOrder where id <= 5").iterate(); while (iterate.hasNext()) { PojoOrder order = (PojoOrder) iterate.next(); System.out.println(order.getName()); } transaction.commit(); session.close(); }
之前类级别缓存、集合级别缓存,都是将数据存入类级别缓冲区,通过id查询对象。key是对象的id,value是缓存对象;这里的查询缓存key是HQL语句或SQL语句,value是查询结果数据。查询缓存依赖二级缓存。
在hibernate.cfg.xml中添加:
<hibernate-configuration> <session-factory> <!-- 启用查询缓存 --> <property name= "hibernate.cache.use_query_cache">true</property>
public void testTimer(){ Session session = UtilGetSession.openSession(); Transaction transaction = session.beginTransaction(); //表中的一列,数据对象的部分属性 无法缓存,之后的操作无法使用。设置cacheAble为true即可 List list1 = session.createQuery("select name from PojoUser").setCacheable(true).list(); System.out.println(list1); transaction.commit(); session.close(); session = UtilGetSession.openSession(); transaction = session.beginTransaction(); //要想从缓存读取,设置cacheAble为true即可 List list2 = session.createQuery("select name from PojoUser").setCacheable(true).list(); System.out.println(list2); transaction.commit(); session.close(); }