13---二级缓存

二级缓存

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>
    3) 配置二级缓存技术提供商       
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
    4) 配置缓存数据对象并发策略

       使用read-write 并发策略

       方式一 在hbm文件配置           

<class name="cn.itcast.domain.Customer" table="customers" catalog="hibernate3day4" >
    <!-- 类级别缓存 -->  
    <cache usage="read-write"/>
        <set name="orders" cascade="all-delete-orphan" inverse="true" >
            <!-- 关联集合级别缓存 -->
            <cache usage="read-write"/>
        </set>
</class>
       方式二 在cfg文件配置(集中配置)
<!-- 类级别缓存 -->
<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"/>  

    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
}
**我们发现执行这段代码只有一条SQL语句,那是因为查询customer3的数据是从二级缓存里面拿的,在放入一级缓存的同时也放入了二级缓存。

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,是默认配置,该属性对所有缓存数据都有效
* <defaultCache  <!-- 缓存属性配置  -->
    maxElementsInMemory="10000"
    eternal="false"  
    timeToIdleSeconds="120"  
    timeToLiveSeconds="120"  
    overflowToDisk="true"  
    maxElementsOnDisk="10000000"  
    diskPersistent="false"  
    diskExpiryThreadIntervalSeconds="120"  
    memoryStoreEvictionPolicy="LRU"  
/>  
    **自定义配置
<cache name="cn.itcast.domain.Customer"   // 自定义配置,该属性对cn.itcast.domain.Customer类缓存有效
    maxElementsInMemory="10000"  // 内存中最大对象数量 ,超过数量,数据会被缓存到硬盘  
    eternal="false" // 是否缓存为永久性 false 不永久  
    timeToIdleSeconds="120"  // 闲置时间,超过时间回收  
    timeToLiveSeconds="120"  // 存活时间,对象不管是否使用,到了时间回收  
    overflowToDisk="true"  // 是否可以缓存到硬盘  
    maxElementsOnDisk="10000000" // 硬盘缓存最大对象数量  
    diskPersistent="false"  // 当jvm结束时是否持久化对象 true false 默认是false  
    diskExpiryThreadIntervalSeconds="120"  // 指定专门用于清除过期对象的监听线程的轮询时间  
    memoryStoreEvictionPolicy="LRU"  
/>  
    *演示的时候要注意给写数据的时间(程序运行立马就commit了,我们可以在第9行设置断点,查看结果)
@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.查询缓存

    有人称查询缓存 为hibernate 第三级缓存 

二级缓存缓存数据都是类对象数据,数据都是缓存在 "类缓存区域" ,二级缓存缓存PO类对象,条件(key)id

    查询缓存适用场合:

        **应用程序运行时经常使用查询语句

        **很少对与查询语句检索到的数据进行插入删除和更新操作

    如果查询条件不是id查询, 缓存数据不是PO类完整对象 =====> 不适合使用二级缓存

查询缓存: 缓存的是查询数据结果, key是查询生成SQL语句  , 查询缓存比二级缓存功能更加强大 


适用查询缓存的步骤
    1)配置二级缓存(查询缓存依赖二级缓存) 

    2)启用查询缓存 hibernate.cfg.xml 

<property name="hibernate.cache.use_query_cache">true</property>
    3)必须在程序中指定使用查询缓存 
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() 获取二级缓存丢失次数 
工具类HibernateUtils
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();
}
}

进行监控之前,必须设置hibernate属性
<!-- 启用统计 -->
<property name="hibernate.generate_statistics">true</property>
hitCount/(hitCount + missCount) = 命中率 
查询二级缓存后,如果找到 hitCount +1 ,如果没找到 missCount+1 , 如果一级缓存找到了,就不会去查找二级缓存, hitCount、missCount 不变

@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();
}


附:Hibernate.cfg.xml文件
<?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>

















……其它问题还在整理中!!!

阅读更多

扫码向博主提问

寒夕若梦

非学,无以致疑;非问,无以广识
去开通我的Chat快问

没有更多推荐了,返回首页