MyBatis(六)缓存机制 之 二级缓存

一、二级缓存简介

  1、简介

    (1)二级缓存(second level cache),全局作用域缓存;
    (2)二级缓存默认不开启,需要手动配置;
    (3)MyBatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现 Serializable 接口;
    (4)二级缓存在 SqlSession 关闭或提交之后才会生效;

  2、说明

    二级缓存是全局缓存,基于 namespace 级别的缓存,一个 namespace 对应一个二级缓存;

    工作机制:

    (1)一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    (2)如果当前会话关闭了,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容;

    (3)每一个 namespace 对应一个二级缓存,不同 namespace 查出的数据会放在自己对应的缓存中(map)

sqlSession===EmployeeMapper==>Employee
             DepartmentMapper==>Department

  

二、二级缓存的使用步骤

  1、全局配置文件中开启二级缓存

<setting name = "cacheEnabled" value="true" /> 默认是 false(不开启)

  

  2、在每个 mapper.xml 中配置使用二级缓存 <cache></cache>

    二级缓存需要在 mapper.xml 中配置 <cache> 标签,所以二级缓存来说是 映射文件级别的。不管 SqlSession 是怎么创建的,只要是访问的同一个映射文件中的 SQL 语句,就可以被缓存。

  3、注意: POJO 需要实现 Serializable 接口

三、二级缓存工作机制

  工作机制:

  1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

  2、如果会话关闭:一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;

  3、不同namespace查出的数据会放在自己对应的缓存中(map), 效果:数据会从二级缓存中获取,查出的数据都会被默认先放在一级缓存中。

  4、只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中;

  5、如果 sqlSession 没有关闭,还是会从一级缓存查询;

四、二级缓存相关的属性(<cache>标签中的属性)

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024" type=""></cache>

  (1)cache 开启二级缓存

  (2)eviction 缓存的回收策略

LRU:最近最少使用的,移除最长时间不被使用的对象。
FIFO:先进先出,按对象进入缓存的顺序来移除它们。
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。
VEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。

  (3)flushInterval 缓存刷新间隔,单位毫秒

      缓存多长时间清空一次,默认不清空,设置一个毫秒值

      默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
                不设置,代表缓存永远不刷新,如果满了,使用缓存回收策略来处理。

  (4)readOnly 是否只读 true/false

    true:只读缓存:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                    会给所调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。只读缓存,当获取对象时,会从缓存中给返回,一定不能修改,如果修改了,可能造成返回的对象与数据库中的记录不一致,造成脏读的现象。
    false:读写缓存:mybatis觉得获取的数据可能会被修改。
                mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢。
                会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是 false。

  (5)size 缓存中存放多少元素,引用数据,正整数

      代表缓存最多可以存储多少个对象,太大容易导致内存溢出,还严重消耗内存;太小则没意义;

  (6)type:用来设置第三方缓存,指定自定义缓存的全类名;实现cache接口即可。

五、代码示例

  1、使用二级缓存

    代码:

    /**
      * 二级缓存
      * @throws IOException
      */
     @Test
     public void testSecondLevelCache() throws IOException {
          SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
          SqlSession sqlSession = sqlSessionFactory.openSession();
          SqlSession sqlSession2 = sqlSessionFactory.openSession();
          try {
               EmployeeMapperCache mapper = sqlSession.getMapper(EmployeeMapperCache.class);
               EmployeeMapperCache mapper2 = sqlSession2.getMapper(EmployeeMapperCache.class);


               Employee emp01 = mapper.getEmpById(1);
               System.out.println("emp01 = " + emp01);
               sqlSession.close();

               //第二次查询是从二级缓冲中拿到的数据,并没有发送新的SQL
               //缓存命中率 Cache Hit Ratio [com.njf.mybatis.dao.EmployeeMapperCache]: 0.5
               Employee emp02 = mapper2.getEmpById(1);
               System.out.println("emp02 = " + emp02);
               sqlSession2.close();

               System.out.println(emp01 == emp02);
          } finally {
               sqlSession.close();
          }
     }

    运行结果:

  命中率是指:后面的 SQL 语句在缓存中,有多少 SQL 命中(即与缓存中的 SQL重复)
        而这里的命中率为0.5,是因为二级缓存必须在 SqlSession 关闭或提交之后才会生效,上面的代码中,sqlSession关闭后,把查询结果放在二级缓存中,这时再去查询正好命中。

  2、未开启二级缓存

    代码:

    /**
      * 未启用二级缓存
      * @throws IOException
      */
     @Test
     public void testSecondLevelCacheDisable() throws IOException {
          SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
          SqlSession sqlSession = sqlSessionFactory.openSession();
          SqlSession sqlSession2 = sqlSessionFactory.openSession();
          try {
               DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
               DepartmentMapper mapper2 = sqlSession2.getMapper(DepartmentMapper.class);

               Department dept01 = mapper.getDeptById(1);
               System.out.println("dept01 = " + dept01);
               sqlSession.close();

               Department dept02 = mapper2.getDeptById(1);
               System.out.println("dept02 = " + dept02);
               sqlSession2.close();

               System.out.println(dept01 == dept02);
          } finally {
               sqlSession.close();
               sqlSession2.close();
          }
     }

    运行结果:

     可以看到发送了两条SQL语句,正是因为没有使用二级缓存,当第一个 sqlSession 关闭后,没有把查询结果放在二级缓存中,所以再次查询时会重新发送 SQL 语句。

原文地址:https://www.cnblogs.com/niujifei/p/15243666.html