Hibernate---二级缓存

  • 观察问题:
    • 进行两次次查询操作,查询的都是同一个对象,打印两次查询出的对象
    @Test
    public void testCache(){
        Employee employee = (Employee) this.session.get(Employee.class,310);
        Employee employee2 = (Employee) this.session.get(Employee.class,310);
        System.out.println(employee);
        System.out.println(employee2);
    }
  • 从结果中可以发现程序只执行了一次查询操作,这是因为同一个会话中相同id的持久化类对象会保存在Session的缓存中,所以第二次打印的时候,就不需要在此查询数据库了.

在这里插入图片描述

  • 如果在第一次打印完之后,关闭了Session对象,那第二次在打印的时候会执行几次查询?
   @Test
    public void testCache(){
        Employee employee = (Employee) this.session.get(Employee.class,310);
        System.out.println(employee);

        //提交事务和关闭session
        this.transaction.commit();
        this.session.close();

        //重新获取session
        this.session = this.sessionFactory.openSession();
        this.transaction = this.session.beginTransaction();
        Employee employee2 = (Employee) this.session.get(Employee.class,310);
        System.out.println(employee2);
    }
  • 从结果中的值进行了两次的查询操作

在这里插入图片描述

  1. 缓存的作用
  • 缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
  • Hibernate中提供了两个级别的缓存
    • 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的
    • 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
  • SessionFactory 的缓存可以分为两类:
  • 内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据(.hbm.xml 文件中的数据)的复制. 该内置缓存是只读的.
  • 外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘

什么数据适合放到二级缓存中?

  • 适合放入二级缓存中的数据:
    • 很少被修改
    • 不是很重要的数据, 允许出现偶尔的并发问题
  • 不适合放入二级缓存中的数据:
    • 经常被修改
    • 财务数据, 绝对不允许出现并发问题
    • 与其他应用程序共享的数据
  • Hibernate 二级缓存的架构

在这里插入图片描述

  • Hibernate本身并没有实现二级缓存,需要使用插件来进行二级缓存管理

管理 Hibernate 的二级缓存

  • Hibernate 的二级缓存是进程或集群范围内的缓存
  • 二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
  1. EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 对 > Hibernate 的查询缓存提供了支持
  2. OpenSymphony OSCache:可作为进程范围内的缓存, 存放数据的物理介质可以使内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
  3. SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
  4. JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
  • 4种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)
    在这里插入图片描述
  1. 使用EHCache插件配置二级缓存.
    1. 加入插件jar包
    2. 配置ehcache.xml文件:在Hibernate下载的开发包中 \hibernate-orm\etc\ehcache.xml

在这里插入图片描述

在这里插入图片描述
3. 配置hibernate.cfg.xml文件

  • 配置启用的hibernate的二级缓存
<!--启用二级缓存-->
<property name="cache.use_second_level_cache">true</property>

在这里插入图片描述

  • 配置二级缓存使用的产品
<!--配置而二级缓存使用的产品-->
<property name="hibernate.cache.region.factory_class">
	org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
  • 配置那些类要使用二级缓存
  • 在Employee.hbm.xml文件中配置二级缓存
 <!--配置使用二级缓存-->
 <cache usage="read-write"/>

在这里插入图片描述

  • 再次启动测试类
  • 此时只发送了一条查询语句

在这里插入图片描述

设置集合使用二级缓存

  • 默认情况下,设置了类级别的二级缓存,但是与之关联的集合属性中的内容是不会保存到二级缓存中的.
  • 示例:Department类中有一个emps集合保存的是Employess类实例化对象集合
 @Test
    public void testSetCache(){
        Department department = (Department) this.session.get(Department.class,287);
        System.out.println(department.getName());
        System.out.println(department.getEmps().size());

        //提交事务和关闭session
        this.transaction.commit();
        this.session.close();

        //重新获取session
        this.session = this.sessionFactory.openSession();
        this.transaction = this.session.beginTransaction();

        Department department2 = (Department) this.session.get(Department.class,287);
        System.out.println(department2.getName());
        System.out.println(department2.getEmps().size());
    }
  • 执行结果:
    • 从执行结果中可以发现,在第一次查询过后第二次查询emps集合数量时,又进行了一次查询操作.
Hibernate: 
    select
        department0_.ID as ID0_0_,
        department0_.NAME as NAME0_0_ 
    from
        GG_DEPARTMENT department0_ 
    where
        department0_.ID=?
部门 = 6
Hibernate: 
    select
        emps0_.DEPT_ID as DEPT5_0_1_,
        emps0_.ID as ID1_,
        emps0_.ID as ID1_0_,
        emps0_.NAME as NAME1_0_,
        emps0_.EMAIL as EMAIL1_0_,
        emps0_.SALARY as SALARY1_0_,
        emps0_.DEPT_ID as DEPT5_1_0_ 
    from
        GG_EMPLOYEE emps0_ 
    where
        emps0_.DEPT_ID=?
10
部门 = 6
Hibernate: 
    select
        emps0_.DEPT_ID as DEPT5_0_1_,
        emps0_.ID as ID1_,
        emps0_.ID as ID1_0_,
        emps0_.NAME as NAME1_0_,
        emps0_.EMAIL as EMAIL1_0_,
        emps0_.SALARY as SALARY1_0_,
        emps0_.DEPT_ID as DEPT5_1_0_ 
    from
        GG_EMPLOYEE emps0_ 
    where
        emps0_.DEPT_ID=?
10
  • 如果要配置集合的二级缓存有两种方式
  1. 在ehcache.xml文件中配置
<collection-cache 
usage="read-write" 
collection="com.atguigu.hibernate.entities.Department.emps"/>
  1. 在Department.hbm.xml文件中配置
<!--设置集合二级缓存-->
<cache usage="read-write"/>

在这里插入图片描述

  • 设置完之后再次执行测试方法
    • 发现在第二次查询集合数量时就直接输出了,没有再次执行查询操作

在这里插入图片描述

ehcache.xml配置文件

  • 当缓存过多的时候,会占满内存空间,因此可以配置将缓存储存到磁盘中
  • 修改ehcache.xml文件中的<diskStore>标签
<diskStore path="d:\\tempDirectory"/>
  • Hibernate在Session的关闭时会自动清楚掉磁盘中的缓存文件

在这里插入图片描述

  • ehcache配置文件中有一个默认的缓存配置,当没有自定义配置缓存的时候就会使用默认的缓存

在这里插入图片描述

  • 如果要自定义缓存策略可以使用<cache>标签
  • <cache> 设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
    缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache…/>
  • Hibernate在不同的缓存区域保存不同的类/集合。
    • 对于类而言,区域的名称是类名。如:com.atguigu.domain.Customer
    • 对于集合而言,区域的名称是类名加属性名。如com.atguigu.domain.Customer.orders
    <cache name="mao.shu.vo.Department"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="60"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

查询缓存

  • 默认情况下,设置的缓存对HQL和QBC查询时是无效的,但可以通过以下方式使其有效:
  1. 在hibernate配置文件中声明开启查询缓存
<!--开启查询缓存-->
<property name="cache.use_query_cache">true</property>
  1. 调Query.setCacheable()方法
    @Test
    public void testQueryCache(){
        String hql = "FROM Employee";
        Query query = this.session.createQuery(hql);
        query.setCacheable(true);

        System.out.println(query.list().size());

        Criteria criteria = this.session.createCriteria(Department.class);
        criteria.setCacheable(true);
    }
  • 查询缓存依赖于二级缓存.

更新时间戳缓存

  • 时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
  • T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
  • T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
  • T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 - UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果

Query的iterator方法

  • Query 接口的 iterator() 方法
  • 同 list() 一样也能执行查询操作
  1. list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
  2. Iterator() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
  • 当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID 的对象, 如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select 语句到数据库中加载特定的实体对象
  • 大多数情况下, 应考虑使用 list() 方法执行查询操作. iterator() 方法仅在满足以下条件的场合, 可以稍微提高查询性能:
    要查询的数据表中包含大量字段
    启用了二级缓存, 且二级缓存中可能已经包含了待查询的对象
  • 该方法存在很多特定条件,不建议使用
    @Test
    public void tesetQueryIterator(){
        Query queryTest = this.session.createQuery("FROM Employee ");
        List<Employee> test = queryTest.list();
      Query query = this.session.createQuery("FROM Employee ");
      Iterator<Employee> list = query.iterate();
      while(list.hasNext()){
          System.out.println(list.next().getName());
      }
    }
  • 当第一次使用查询已经将集合数据保存到了二级缓存之中,所以第二次使用iterator()方法查询的时候,并没有触发"select"语句,而是直接打印输出了.
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43386754/article/details/87990501