Java实战之02Hibernate-08二级缓存
十四、Hibernate的二级缓存
1、Hibernate的缓存结构
2、由于二级缓存被多线程共享,就必须有一定的事务访问策略
非严格读写:READ UNCOMMITTED
读写型:READ COMMITTED
事务型:REPEATABLED READ
只读型:SERIALIZABLE
适合放入二级缓存中的数据:
很少被修改
不是很重要的数据, 允许出现偶尔的并发问题
不适合放入二级缓存中的数据:
经常被修改
财务数据, 绝对不允许出现并发问题
与其他应用数据共享的数据
3、缓存提供的供应商
3.1、各个提供商介绍
Hibernate 的二级缓存是进程或集群范围内的缓存, 缓存中存放的是对象的散装数据,二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
a) EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
b) OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
c) SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
d) JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
3.2、采用EHCache第三方组件
3.2.1、把所需jar包加入到构建路径中:
1 <!-- 开启hibernate的二级缓存 --> 2 <property name="hibernate.cache.use_second_level_cache">true</property> 3 <!-- 配置二级缓存的提供商 --> 4 <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
3.2.3、在应用中加入EHCache的配置文件
把ehcache-1.5.0.jar包打开,把ehcache-failsafe.xml拷贝出来,去掉里面的注释。并把文件名改为ehcache.xml
1 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> 2 3 <diskStore path="java.io.tmpdir"/> 4 5 <defaultCache 6 maxElementsInMemory="10000" 7 eternal="false" 8 timeToIdleSeconds="120" 9 timeToLiveSeconds="120" 10 overflowToDisk="true" 11 maxElementsOnDisk="10000000" 12 diskPersistent="false" 13 diskExpiryThreadIntervalSeconds="120" 14 memoryStoreEvictionPolicy="LRU" 15 /> 16 </ehcache>
4、使用二级缓存
4.1、配置使用二级缓存实体
1 <!-- 配置哪些类使用二级缓存 --> 2 <class-cache usage="read-write" class="cn.itcast.domain.Customer"/> 3 <class-cache usage="read-write" class="cn.itcast.domain.Order"/> 4 <!-- 配置哪些集合使用二级缓存 --> 5 <collection-cache usage="read-write" collection="cn.itcast.domain.Customer.orders"/>
4.2、验证二级缓存是有效的
注意:为了验证二级缓存的存在及有效,先不要使用把session绑定到当前线程上。同时也不要使用getCurrentSession()方法。
1 //验证二级缓存确实可用 2 @Test 3 public void test1(){ 4 Session s1 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 5 Transaction tx1 = s1.beginTransaction(); 6 //使用get方法获取一个客户 7 Customer c1 = s1.get(Customer.class,1);//会去数据库中查询,把结果放入一级缓存之中。 如果配置了二级缓存,还会把查询结果放入二级缓存之中。 8 System.out.println(c1); 9 tx1.commit(); 10 s1.close();//session一关闭,一级缓存就消失了 11 12 Session s2 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 13 Transaction tx2 = s2.beginTransaction(); 14 //使用get方法获取一个客户 15 Customer c2 = s2.get(Customer.class,1);//不查。二级缓存中有 16 System.out.println(c2); 17 tx2.commit(); 18 s2.close(); 19 }
5、类缓存区(Class Cache Region)
明确存的是什么:是对象中的数据,而不是一个对象。
注意:
get和load都可以存和取二级缓存的数据。
Query.list("from Customer")只能存不能取。原因:动态查询。HQL是不一定的。
1 /* 2 * 类缓存区 3 * 都是针对实体类说的,不涉及类中的关联对象。 4 * 举例: 5 * 如果配置了客户,只涉及客户的信息,不会涉及客户关联的订单! 6 * 哪些方法可以操作类缓存区: 7 * get和load: 8 * 他们都是可以存和取二级缓存中类缓存区的数据。 9 * query.list() 10 * 它只能存,不能取二级缓存的类缓存区数据 11 * 12 * 类缓存区,存的是什么? 13 * 一级缓存:存的是对象 14 * 二级缓存:存的是散装数据。 15 * 例如:Cusomert[ 16 * {id:1,name:'testA',age:20}, 17 * {id:2,name:'testB',age:28} 18 * ] 19 */ 20 @Test 21 public void test2(){ 22 Session s1 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 23 Transaction tx1 = s1.beginTransaction(); 24 //使用query的list方法,查询所有客户 25 Query query1 = s1.createQuery("from Customer"); 26 List list1 = query1.list();//会去查询,同时把查询结果放入一级缓存。如果配置了二级缓存,也会放入二级缓存。 27 System.out.println(list1); 28 //使用get方法获取一个客户 29 Customer c1 = s1.load(Customer.class,1);//不查,因为一级缓存之中一级有了 30 System.out.println(c1); 31 tx1.commit(); 32 s1.close();//session一关闭,一级缓存就消失了 33 34 Session s2 = HibernateUtil.getSession(); 35 Transaction tx2 = s2.beginTransaction(); 36 //使用get方法获取一个客户 37 Customer c2 = s2.load(Customer.class,1);//不查,因为二级缓存中有 38 System.out.println(c2); 39 Query query2 = s2.createQuery("from Customer"); 40 List list2 = query2.list();//会去查询,不会从二级缓存中取数据 41 System.out.println(list2); 42 tx2.commit(); 43 s2.close(); 44 }
6、集合缓存区(Collection Cache Region)
一对多关系映射:操作多的一方就是集合。在配置集合映射时,需注意:
1 /* 2 * 集合缓存区 3 * 要想使用集合缓存区: 4 * 1、必须在主配置文件中配置开启集合缓存区。 5 * 2、必须同时配置上集合元素的类缓存区。 6 * 要想使用orders集合缓存区,以下两行缺一不可 7 * <class-cache usage="read-write" class="cn.itcast.domain.Order"/> 8 <collection-cache usage="read-write" collection="cn.itcast.domain.Customer.orders"/> 9 集合缓存区,存入的是什么? 10 是只有OID的一个集合。 11 举例: 12 {id:1,id:2,id:3,id:4....} 13 执行方式: 14 在使用集合缓存区时,会先从集合缓存区去匹配OID,把匹配上的OID全部取出,到对应的类缓存区去取数据,再生成对象 15 */ 16 @Test 17 public void test3(){ 18 Session s1 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 19 Transaction tx1 = s1.beginTransaction(); 20 //使用get方法获取一个客户 21 Customer c1 = s1.load(Customer.class,1); 22 //输出客户的订单 23 System.out.println(c1.getOrders());//由于有延迟加载的存在,此时才会去查询。并且把查询结果存入一级缓存之中。同时也会存入二级缓存。 24 tx1.commit(); 25 s1.close();//session一关闭,一级缓存就消失了 26 27 Session s2 = HibernateUtil.getSession(); 28 Transaction tx2 = s2.beginTransaction(); 29 //使用get方法获取一个客户 30 Customer c2 = s2.load(Customer.class,1); 31 //获取该客户的订单 32 System.out.println(c2.getOrders()); 33 tx2.commit(); 34 s2.close(); 35 }
7、更新时间戳
当我们修改一级缓存的数据时,会自动同步二级缓存的数据。用的是时间戳原理。
1 /* 2 * 更新时间戳 3 * 4 * 时间戳原理: 5 * 当一级缓存和二级缓存在创建时,都会有两个时间点。 6 * 其一:创建时间 7 * 其二:最后修改时间 8 * 当执行update后,由于一级缓存已经发生变化了,这时hibernate会用一级缓存的最后修改时就 9 * 和二级缓存的最后修改时间进行比较,用离当前时间近的去修改离当前时间远的。 10 */ 11 @Test 12 public void test4(){ 13 Session s1 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 14 Transaction tx1 = s1.beginTransaction(); 15 //使用get方法获取一个客户 16 Customer c1 = s1.get(Customer.class,1);//会查询,同时存入一级缓存和二级缓存 17 c1.setName("泰斯特"); 18 tx1.commit();//由于快照机制,此行会执行更新,同时更新一级缓存。也会更新二级缓存。 19 s1.close();//session一关闭,一级缓存就消失了 20 21 Session s2 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 22 Transaction tx2 = s2.beginTransaction(); 23 //使用get方法获取一个客户 24 Customer c2 = s2.get(Customer.class,1);//不查。二级缓存中有 25 System.out.println(c2);//输出的【泰斯特】还是【testA】? 26 tx2.commit(); 27 s2.close(); 28 }
8、EHCache的配置文件
diskStore :指定数据存储位置,可指定磁盘中的文件夹位置
defaultCache : 默认的管理策略
maxElementsOnDisk: 在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
eternal: 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。
timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
diskExpiryThreadIntervalSeconds: 对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。
diskSpoolBufferSizeMB: DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
FIFO ,first in first out (先进先出).
LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。
LRU ,Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
9、查询缓存区(Query cache)
问题:
Query查询只能存不能取,因为语句是动态的。
解决办法:
按照语句进行存储。使用Map。Map<String,Object>。 key是SQL语句。value是查询的结果集。
这个Map就是查询缓存区。它默认是关闭的。
注意事项:
放到查询缓冲区中的数据,一定要不怎么变化的数据。(并且是非敏感数据,其实放到任何缓存中的数据都应该是非敏感数据)
使用:
a、开启查询缓存区(在hibernate.cfg.xml中配置)
1 <!-- 开启hibernate的查询缓存区:有些地方(书籍或者公司)可能会把查询缓存区叫成hibernate的三级缓存 --> 2 <property name="hibernate.cache.use_query_cache">true</property>
b、实验是否可用
1 /* 2 * 为什么query的list方法对二级缓存是能存不能取? 3 * 原因: 4 * 因为HQL语句是不定的,hibernate没法确定每次查询的HQL语句都是一样。 5 * 6 * 解决不能取的思路: 7 * Map<String hql,Object result> queryCache; 8 * 思路: 9 * 创建一个新的区域,区域可能是一个map。 10 * map的key是查询的HQL语句。 11 * map的value是查询的结果集。 12 * 13 * hibernate的查询缓存区: 14 * 1、即是开启了二级缓存,hibernate也不会开启查询缓存区。查询缓存区必须独立开启,且必须是在二级缓存已经开启的基础之上。 15 * <!-- 开启hibernate的查询缓存区:有些地方(书籍或者公司)可能会把查询缓存区叫成hibernate的三级缓存 --> 16 <property name="hibernate.cache.use_query_cache">true</property> 17 2、在执行查询的时候,需要设置使用查询缓存区 18 query1.setCacheable(true);//明确使用查询缓存区 19 * 20 */ 21 @Test 22 public void test5(){ 23 Session s1 = HibernateUtil.getSession();//每次都是获取一个新的Session,不能绑到当前线程上。 24 Transaction tx1 = s1.beginTransaction(); 25 Query query1 = s1.createQuery("from Customer"); 26 query1.setCacheable(true);//明确使用查询缓存区 27 List list1 = query1.list(); 28 System.out.println(list1); 29 tx1.commit(); 30 s1.close();//session一关闭,一级缓存就消失了 31 32 Session s2 = HibernateUtil.getSession(); 33 Transaction tx2 = s2.beginTransaction(); 34 Query query2 = s2.createQuery("from Customer"); 35 query2.setCacheable(true); 36 List list2 = query2.list(); 37 System.out.println(list2); 38 tx2.commit(); 39 s2.close(); 40 }