二级缓存
1.二级缓存相关介绍
①缓存好处: 将数据库或者硬盘数据,保存在内存中,减少数据库查询次数,减少硬盘交互,提高检索效率
hibernate 共有两个级别的缓存
* 一级缓存,保存Session中, 事务范围的缓存
* 二级缓存,保存SessionFactory ,进程范围的缓存
SessionFacoty 两部分缓存
内置 :Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据的复制, 而预定义 SQL 语句时 Hibernate 根据映射元数据推到出来的. 该内置缓存是只读的.
外置 :一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘,必须引入第三方缓存插件才能使用。
2.二级缓存内部结构学习
* 类缓存区域
* 集合缓存区域
* 更新时间戳区域
* 查询缓存区域
3.二级缓存并发策略
transactional : 提供Repeatable Read事务隔离级别,缓存支持事务,发生异常的时候,缓存也能够回滚
read-write : 提供Read Committed事务隔离级别,更新缓存的时候会锁定缓存中的数据
nonstrict-read-write :导致脏读, 很少使用
read-only : 数据不允许修改,只能查询
* 很少被修改,不是很重要,允许偶尔的并发问题, 适合放入二级缓存。考虑因素(二级缓存的监控【后面学习】,它是是否采用二级缓存主要参考指标)
4.hibernate 支持二级缓存技术
* EHCache (主要学习,支持本地缓存,支持分布式缓存)
可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持。
* OSCache
可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
* SwarmCache
可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
* JBossCache
可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
5.二级缓存的配置
1) 导入第三方缓存技术jar包(有3个)
ehcache-1.5.0.jar
依赖 backport-util-concurrent 和 commons-logging
2) 在hibernate.cfg.xml 开启二级缓存
<property name="hibernate.cache.use_second_level_cache">true</property><property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>使用read-write 并发策略
方式一 在hbm文件配置
<class name="cn.itcast.domain.Customer" table="customers" catalog="hibernate3day4" >
<set name="orders" cascade="all-delete-orphan" inverse="true" > <!-- 关联集合级别缓存 --> <cache usage="read-write"/> </set></class><!-- 类级别缓存 -->
5) 在src中 配置 ehcache.xml
将 ehcache-1.5.0 jar包中 ehcache-failsafe.xml 改名 ehcache.xml 放入 src
编写程序:测试二级缓存的存在
@Test// 证明二级缓存是存在的 (类缓存区域 散装数据 存储)public void demo1() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 放入一级缓存的同时也放入二级缓存
Customer customer1 = (Customer) session.get(Customer.class, 1); System.out.println(customer1); Customer customer2 = (Customer) session.get(Customer.class, 1); // 读取一级缓存 System.out.println(customer2); transaction.commit();// 自动关闭Session // Session 关闭后 一级缓存 就没有了 session = HibernateUtils.getCurrentSession();//开启新的session transaction = session.beginTransaction(); // 查找二级缓存 Customer customer3 = (Customer) session.get(Customer.class, 1); System.out.println(customer3); transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); // 查找二级缓存 Customer customer4 = (Customer) session.get(Customer.class, 1); System.out.println(customer4); transaction.commit();// 自动关闭Session}6.理解类缓存区数据存储特点
* 从二级缓存区返回数据每次地址都是不同的(散装数据)。每次查询二级缓存,都是将散装数据构造为一个新的对象
** get、load 方法都可以读取二级缓存的数据 , Query 的 list方法只能存,不能取(不走二级缓存)
@Test// get/load 读写二级缓存, Query 的 list 只能存不能取public void demo2() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 将数据 保存到二级缓存 List<Customer> customers = session.createQuery("from Customer").list(); transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); // 不能读取二级缓存数据(有2次查询就说明不走缓存) // List<Customer> customers2 = // session.createQuery("from Customer").list(); // 直接读取二级缓存 (没有产生新的查询),检测已经存进去了。 Customer customer = (Customer) session.get(Customer.class, 1); System.out.println(customer); transaction.commit();// 自动关闭Session}7. 理解集合缓存区数据存储特点
@Test// 理解集合缓存区 存储特点// 将 <class-cache usage="read-write" class="cn.itcast.domain.Order"/> 注释public void demo3() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 读取类数据,放入二级缓存 Customer customer = (Customer) session.get(Customer.class, 1); System.out.println(customer.getOrders().size());// 查询order数据,放入二级缓存 transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); Customer customer2 = (Customer) session.get(Customer.class, 1); System.out.println(customer2.getOrders().size()); transaction.commit();// 自动关闭Session}如果注释掉 Order类缓存,orders 集合无法缓存
<!--<class-cache usage="read-write" class="cn.itcast.domain.Order"/> -->** 一级缓存的操作会同步到二级缓存
@Test// 一级缓存操作会同步到二级缓存public void demo4() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 放入 一级缓存 和 二级缓存 Customer customer = (Customer) session.get(Customer.class, 1); customer.setName("tom"); // 修改一级缓存,同步到二级缓存 transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); Customer customer2 = (Customer) session.get(Customer.class, 1); System.out.println(customer2); transaction.commit();// 自动关闭Session}8. 当内存缓存数据过多之后,需要将数据缓存到硬盘上
配置 src/ehcache.xml
* <diskStore path="c:/ehcache"/> 配置二级缓存硬盘临时目录位置* <defaultCache <!-- 缓存属性配置 --> maxElementsInMemory="10000"
<cache name="cn.itcast.domain.Customer" // 自定义配置,该属性对cn.itcast.domain.Customer类缓存有效
@Test// 配置二级缓存数据缓存到硬盘// maxElementsInMemory="5" 内存只能缓存5个对象public void demo5() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 查询10个订单 List<Order> orders = session.createQuery("from Order").list(); transaction.commit();// 自动关闭Session}9.更新时间戳区域
作用:记录数据最后更新时间,确保缓存数据是有效的
Hibernate 提供了和查询相关的缓存区域:
**时间戳缓存区域: org.hibernate.cahce.UpdateTimestampCache时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果。
@Test// 测试更新时间戳区域的使用public void demo6() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 数据被保存一级缓存 和 二级缓存 Customer customer = (Customer) session.get(Customer.class, 1); // 通过hibernate程序,修改1号客户数据 session.createQuery("update Customer set name ='kitty' where id = 1").executeUpdate(); // 不会通知二级缓存 transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); // 先查询二级缓存 Customer customer2 = (Customer) session.get(Customer.class, 1); System.out.println(customer2); transaction.commit();// 自动关闭Session} **更新时间戳区域,记录数据最后更新时间,在使用二级缓存时,比较缓存时间t1 与更新时间 t2 , 如果 t2 > t1 丢弃原来缓存数据,重新查询缓存
10.Query 接口的 iterate() 方法
同 list() 一样也能执行查询操作
list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
Iterate() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID 的对象, 如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select 语句到数据库中加载特定的实体对象
大多数情况下, 应考虑使用 list() 方法执行查询操作. iterate() 方法仅在满足以下条件的场合, 可以稍微提高查询性能:
**要查询的数据表中包含大量字段
**启用了二级缓存, 且二级缓存中可能已经包含了待查询的对象
@Test// 使用Iterate 访问二级缓存public void demo7() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 查询前5个订单 ,list方法,将数据保存到二级缓存 session.createQuery("from Order where id <= 5").list(); transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); Iterator<Order> iterator = session.createQuery("from Order").iterate(); while (iterator.hasNext()) { // 前五个订单,可以从二级缓存获得 // 只需要产生5条查询语句 Order order = iterator.next(); // 返回代理对象 System.out.println(order.getMoney()); } transaction.commit();// 自动关闭Session}11.查询缓存
* 二级缓存缓存数据都是类对象数据,数据都是缓存在 "类缓存区域" ,二级缓存缓存PO类对象,条件(key)是id
查询缓存适用场合:
**应用程序运行时经常使用查询语句
**很少对与查询语句检索到的数据进行插入, 删除和更新操作
如果查询条件不是id查询, 缓存数据不是PO类完整对象 =====> 不适合使用二级缓存
查询缓存: 缓存的是查询数据结果, key是查询生成SQL语句 , 查询缓存比二级缓存功能更加强大
2)启用查询缓存 hibernate.cfg.xml
<property name="hibernate.cache.use_query_cache">true</property>query.setCacheable(true);@Test// 有些情况必须使用 查询缓存public void demo8() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 查询1号客户 的姓名 Query query = session.createQuery("select name from Customer where id =1 "); // 启用查询缓存 query.setCacheable(true); // 放入查询缓存 String name = (String) query.uniqueResult(); System.out.println(name); transaction.commit();// 自动关闭Session session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); // 查询1号客户 的姓名 Query query2 = session.createQuery("select name from Customer where id =1 "); // 启用查询缓存 query2.setCacheable(true);// 从查询缓存进行查询 String name2 = (String) query2.uniqueResult(); System.out.println(name2); transaction.commit();// 自动关闭Session}12.二级缓存性能监控
没有监测和性能参数而进行优化是毫无意义的(查询了100次,走了80次缓存,那么二级缓存才会有意义)。Hibernate 为其内部操作提供了一系列的示意图,因此可以从每个 SessionFactory抓取其统计数据。
SessionFactory 提供二级缓存监控方法,用来获得二级缓存命中次数
* Statistics getStatistics() 返回Statistics 对象
Statistics 对象提供了以下方法:
* long getQueryCacheHitCount() 获取查询缓存命中次数* long getQueryCacheMissCount() 获取查询缓存丢失次数* long getSecondLevelCacheHitCount() 获取二级缓存命中次数 * long getSecondLevelCacheMissCount() 获取二级缓存丢失次数 public class HibernateUtils { private static Configuration configuration; private static SessionFactory sessionFactory; static { configuration = new Configuration().configure(); sessionFactory = configuration.buildSessionFactory(); } public static Session openSession() { return sessionFactory.openSession(); } public static Session getCurrentSession() { return sessionFactory.getCurrentSession(); } public static SessionFactory getSessionFactory() { return sessionFactory; } // 获得命中率 public static long getSecondCacheHitCount() { return sessionFactory.getStatistics().getSecondLevelCacheHitCount(); } // 获得丢失率 public static long getSecondCacheMissCount() { return sessionFactory.getStatistics().getSecondLevelCacheMissCount(); }}<!-- 启用统计 --><property name="hibernate.generate_statistics">true</property>@Test// 监控二级缓存 查询性能public void demo9() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); System.out.println("命中:" + HibernateUtils.getSecondCacheHitCount()); // 0 System.out.println("丢失:" + HibernateUtils.getSecondCacheMissCount()); // 0 // 去一级找 没找到, 去二级找,没有 missCount + 1 Customer customer = (Customer) session.get(Customer.class, 1); System.out.println(customer); System.out.println("命中:" + HibernateUtils.getSecondCacheHitCount()); // 0 System.out.println("丢失:" + HibernateUtils.getSecondCacheMissCount()); // 1 // 去一级找 ,找到了 不去二级找,不会影响统计 Customer customer2 = (Customer) session.get(Customer.class, 1); // 一级缓存 System.out.println(customer2); System.out.println("命中:" + HibernateUtils.getSecondCacheHitCount()); // 0 System.out.println("丢失:" + HibernateUtils.getSecondCacheMissCount()); // 1 transaction.commit(); session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); // 去一级找 没找到,去二级找 找到了 hitCount+1 Customer customer3 = (Customer) session.get(Customer.class, 1); System.out.println(customer3); System.out.println("命中:" + HibernateUtils.getSecondCacheHitCount()); // 1 System.out.println("丢失:" + HibernateUtils.getSecondCacheMissCount()); // 1 transaction.commit();}<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"><hibernate-configuration> <!-- JDBC基本连接参数 --> <session-factory> <!-- 理解为连接池 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///hibernate3day4</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">abc</property> <!-- 配置方言 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- 常见其它配置 --> <property name="hibernate.show_sql">true</property> <!-- 控制台上打印SQL --> <property name="hibernate.format_sql">true</property> <!-- 控制台输出时,对SQL语句格式化 --> <!-- 测试环境 create/ create-drop 正式环境 update validate --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 自动建表 --> <property name="hibernate.connection.autocommit">true</property> <!-- 不配置隔离级别,将使用数据库默认隔离级别 oracle 2 , mysql 4 --> <!-- 使用 read committed 级别 --> <property name="hibernate.connection.isolation">2</property> <!-- 配置session 与线程绑定 --> <property name="hibernate.current_session_context_class">thread</property> <!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 配置使用了哪种二级缓存 --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <!-- 启用查询缓存 --> <property name="hibernate.cache.use_query_cache">false</property> <!-- 启用统计 --> <property name="hibernate.generate_statistics">true</property> <!-- 在核心配置文件中 引用 mapping 映射文件 --> <mapping resource="cn/itcast/domain/Customer.hbm.xml"/> <mapping resource="cn/itcast/domain/Order.hbm.xml"/> <!-- <mapping resource="cn/itcast/subclass/Employee.hbm.xml"/> --> <mapping resource="cn/itcast/joinedsubclass/Employee.hbm.xml"/> <mapping resource="cn/itcast/collectionmapping/Author.hbm.xml"/> <mapping resource="cn/itcast/collectionmapping/Article.hbm.xml"/> <!-- 配置二级缓存并发策略 --> <!-- 类级别缓存 --> <class-cache usage="read-write" class="cn.itcast.domain.Customer"/> <class-cache usage="read-write" class="cn.itcast.domain.Order"/> <!-- 集合缓存 --> <collection-cache usage="read-write" collection="cn.itcast.domain.Customer.orders"/> </session-factory></hibernate-configuration> ……其它问题还在整理中!!!