zoukankan      html  css  js  c++  java
  • MyBatis 示例-缓存

    MyBatis 提供两种类型的缓存,一种是一级缓存,另一种是二级缓存,本章通过例子的形式描述 MyBatis 缓存的使用。

    测试类:com.yjw.demo.CacheTest

    一级缓存

    MyBatis 默认开启一级缓存。一级缓存是相对于同一个 SqlSession 而言的,所以在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 Mapper 的方法,往往只执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession 都只会取出当前缓存的数据,而不会再次发送 SQL 到数据库。

    测试方法:

    /**
     * 一级缓存
     */
    @Test
    public void l1Cache() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        long startTime1 = System.currentTimeMillis();
        sqlSession.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions");
        LOGGER.info("第一次查询执行时间:" + (System.currentTimeMillis() - startTime1));
        long startTime2 = System.currentTimeMillis();
        sqlSession.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions");
        LOGGER.info("第二次查询执行时间:" + (System.currentTimeMillis() - startTime2));
        sqlSession.close();
    }
    2019-09-16 10:16:02.133  INFO 26268 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
    2019-09-16 10:16:02.148 DEBUG 26268 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student 
    2019-09-16 10:16:02.210 DEBUG 26268 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==> Parameters: 
    2019-09-16 10:16:02.242 DEBUG 26268 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : <==      Total: 3
    2019-09-16 10:16:02.243  INFO 26268 --- [           main] com.yjw.demo.CacheTest                   : 第一次查询执行时间:825
    2019-09-16 10:16:02.244  INFO 26268 --- [           main] com.yjw.demo.CacheTest                   : 第二次查询执行时间:1

    对比两次查询的日志内容,第二次查询没有执行 SQL 语句,显然第二次查询是从缓存中获取的数据。

    二级缓存(不建议使用)

    MyBatis 默认不开启二级缓存。二级缓存是 SqlSessionFactory 层面上的 ,二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis 要求返回的 POJO 必须是可序列化的,也就是要求实现 Serializable 接口,配置的方法很简单,只需要在映射 XML 文件配置 <cache /> 元素就可以开启缓存了。

    MyBatis 二级缓存是基于 namespace 的,缓存的内容是根据 namespace 存放的,可以认为 namespace 就是缓存的 KEY 值 。

    <cache />

    这样的一条语句里面,很多设置是默认的,如果我们只是这样配置,那么就意味着:

    • 映射语句文件中的所有 select 语句将会被缓存;
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存;
    • 缓存会使用默认的 Least Recently Used(LRU,最近最少使用的)算法来收回;
    • 根据时间表,比如 No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新;
    • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用;
    • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,不干扰其他调用者或线程所做的潜在修改。

    另外我们还可以通过<cache-res />配置实现多个 namespace 共用同一个二级缓存,即同一个 Cache 对象。

    如上图所示,namespace2 共用了 namespace1 的 Cache 对象。

    二级缓存可以和一级缓存共存,通过下图来理解 MyBatis 的两层缓存结构。

    当应用程序通过 SqlSession2 执行定义在命名空间 namespace2 中的查询操作时,SqlSession2 首先到 namespace2 对应的二级缓存中查找是否缓存了相应的结果对象。如果没有,则继续到 SqlSession2 对应的一级缓存中查找是否缓存了相应的结果对象,如果依然没有,则访问数据库获取结果集并映射成结果对象返回。 最后,该结果对象会记录到 SqlSession 对应的一级缓存以及 namespace2 对应的二级缓存中,等待后续使用。另外需要注意的是,上图中的命名空间 namespace2 和 namespace3 共享了同一个二级缓存对象,所以通过 SqlSession3 执行命名空间 namespace3 中的完全相同的查询操作(只要该查询生成的 CacheKey 对象与上述 SqlSession2 中的查询生成 CacheKey 对象相同即可)时,可以直接从二级缓存中得到相应的结果对象。 

    案例:

    我们通过案例测试一下二级缓存,首先实体类必须实现 Serializable 接口,在 StudentMapper 文件中添加如下配置:

    <!-- 二级缓存 -->
    <cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />

    测试方法:

    /**
     * 二级缓存
     */
    @Test
    public void l2Cache() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        long startTime1 = System.currentTimeMillis();
        sqlSession1.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
                new StudentQuery());
        LOGGER.info("第一个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime1));
        sqlSession1.commit();
        sqlSession1.close();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        long startTime2 = System.currentTimeMillis();
        sqlSession2.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
                new StudentQuery());
        LOGGER.info("第二个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime2));
        sqlSession2.commit();
        sqlSession2.close();
    }
    2019-09-16 14:33:13.848 DEBUG 22372 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
    2019-09-16 14:33:15.748  INFO 22372 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
    2019-09-16 14:33:15.764 DEBUG 22372 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student 
    2019-09-16 14:33:15.844 DEBUG 22372 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==> Parameters: 
    2019-09-16 14:33:15.885 DEBUG 22372 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : <==      Total: 3
    2019-09-16 14:33:15.887  INFO 22372 --- [           main] com.yjw.demo.CacheTest                   : 第一个SqlSession查询执行时间:2304
    2019-09-16 14:33:15.890 DEBUG 22372 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.5
    2019-09-16 14:33:15.891  INFO 22372 --- [           main] com.yjw.demo.CacheTest                   : 第二个SqlSession查询执行时间:1

    从日志中可以看出,第二次查询没有执行 SQL 语句,日志中还打印了缓存命令率:Cache Hit Ratio,所以第二次 Session 执行是从缓存中获取的数据。

    二级缓存详细配置介绍:

    <cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />
    • eviction:缓存回收策略,目前 MyBatis 提供一下策略;
    • LRU:最近最少使用的,移除最长时间不用的对象;
    • FIFO:先进先出,按对象进入缓存的顺序来移除它们;
    • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象;
    • WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象。这里采用的是 LRU,移除最长时间不用的对象;
    • flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果不配置它,那么当 SQL 被执行的时候才会去刷新缓存;
    • size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大,设置过大会导致内存溢出,这里配置的是1024个对象;
    • readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存。

    二级缓存的问题:

    • 脏数据:因为二级缓存是基于 namespace 的,比如在 StudentMapper 中存在一条查询 SQL,它关联查询了学生证件信息,这个时候开启了二级缓存,在 StudentMapper 对应的缓存中就会存在学生证件的数据,如果更新了学生证件信息的数据,那么在 StudentMapper 中就存在了脏数据;
    • 全部失效:insert、update 和 delete 语句会刷新同一个 namespace 下的所有缓存数据,参考如下例子;
    /**
     * 测试二级缓存全部失效问题,只要执行了insert、update、delete
     * 就会刷新同一个 namespace 下的所有缓存数据
     */
    @Test
    public void l2CacheInvalid() {
        // 缓存listByConditions的数据
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        long startTime1 = System.currentTimeMillis();
        sqlSession1.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
                new StudentQuery());
        LOGGER.info("第一个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime1));
        sqlSession1.commit();
        sqlSession1.close();
    
        // 缓存getByPrimaryKey的数据
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        long startTime2 = System.currentTimeMillis();
        sqlSession2.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.getByPrimaryKey",
                1L);
        LOGGER.info("第二个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime2));
        sqlSession2.commit();
        sqlSession2.close();
    
        // 执行insert语句使上面所有缓存失效
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        StudentDO studentDO = new StudentDO();
        studentDO.setName("赵六");
        studentDO.setSex(Sex.MALE);
        studentDO.setSelfcardNo(4444L);
        studentDO.setNote("zhaoliu");
        sqlSession3.insert("com.yjw.demo.mybatis.biz.dao.StudentDao.insertByAutoInc", studentDO);
        sqlSession3.commit();
        sqlSession3.close();
    
        // 再次执行上面缓存的数据,查看缓存是否已经失效
        SqlSession sqlSession4 = sqlSessionFactory.openSession();
        long startTime4 = System.currentTimeMillis();
        sqlSession4.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.listByConditions",
                new StudentQuery());
        LOGGER.info("第四个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime4));
        sqlSession4.commit();
        sqlSession4.close();
    
        // 缓存getByPrimaryKey的数据
        SqlSession sqlSession5 = sqlSessionFactory.openSession();
        long startTime5 = System.currentTimeMillis();
        sqlSession5.selectList("com.yjw.demo.mybatis.biz.dao.StudentDao.getByPrimaryKey",
                1L);
        LOGGER.info("第五个SqlSession查询执行时间:" + (System.currentTimeMillis() - startTime5));
        sqlSession5.commit();
        sqlSession5.close();
    }
    2019-09-16 14:47:43.489 DEBUG 14940 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
    2019-09-16 14:47:44.258  INFO 14940 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
    2019-09-16 14:47:44.274 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student 
    2019-09-16 14:47:44.328 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==> Parameters: 
    2019-09-16 14:47:44.369 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : <==      Total: 3
    2019-09-16 14:47:44.371  INFO 14940 --- [           main] com.yjw.demo.CacheTest                   : 第一个SqlSession查询执行时间:1015
    2019-09-16 14:47:44.377 DEBUG 14940 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
    2019-09-16 14:47:44.378 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student where id = ? 
    2019-09-16 14:47:44.380 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : ==> Parameters: 1(Long)
    2019-09-16 14:47:44.382 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : <==      Total: 1
    2019-09-16 14:47:44.383  INFO 14940 --- [           main] com.yjw.demo.CacheTest                   : 第二个SqlSession查询执行时间:7
    2019-09-16 14:47:44.383 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.insertByAutoInc   : ==>  Preparing: insert into t_student (name, sex, selfcard_no, note) values ( ?, ?, ?, ? ) 
    2019-09-16 14:47:44.388 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.insertByAutoInc   : ==> Parameters: 赵六(String), 1(Integer), 4444(Long), zhaoliu(String)
    2019-09-16 14:47:44.474 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.insertByAutoInc   : <==    Updates: 1
    2019-09-16 14:47:44.476 DEBUG 14940 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
    2019-09-16 14:47:44.477 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student 
    2019-09-16 14:47:44.477 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : ==> Parameters: 
    2019-09-16 14:47:44.481 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.listByConditions  : <==      Total: 4
    2019-09-16 14:47:44.481  INFO 14940 --- [           main] com.yjw.demo.CacheTest                   : 第四个SqlSession查询执行时间:5
    2019-09-16 14:47:44.482 DEBUG 14940 --- [           main] com.yjw.demo.mybatis.biz.dao.StudentDao  : Cache Hit Ratio [com.yjw.demo.mybatis.biz.dao.StudentDao]: 0.0
    2019-09-16 14:47:44.483 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : ==>  Preparing: select id, name, sex, selfcard_no, note from t_student where id = ? 
    2019-09-16 14:47:44.483 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : ==> Parameters: 1(Long)
    2019-09-16 14:47:44.485 DEBUG 14940 --- [           main] c.y.d.m.b.d.StudentDao.getByPrimaryKey   : <==      Total: 1
    2019-09-16 14:47:44.486  INFO 14940 --- [           main] com.yjw.demo.CacheTest                   : 第五个SqlSession查询执行时间:4

    从上面的日志信息可以看出,四次查询操作,都执行了 SQL 语句,第四个和第五个查询没有从缓存中获取数据,因为第三个执行语句(insert)把当前 namespace 下的所有缓存都失效了。

    鉴于二级缓存存在如上两个问题,所以在项目中不建议使用 MyBatis 的二级缓存。

    MyBatis 实用篇

    MyBatis 概念

    MyBatis 示例-简介

    MyBatis 示例-类型处理器

    MyBatis 示例-传递多个参数

    MyBatis 示例-主键回填

    MyBatis 示例-动态 SQL

    MyBatis 示例-联合查询

    MyBatis 示例-缓存

    MyBatis 示例-插件

    求关注,求点赞,《架构学习》持续更新、完善、纠正 https://www.yuque.com/yinjianwei/vyrvkf
  • 相关阅读:
    C# 委托/Func() 中 GetInvocationList() 方法的使用 | 接收委托多个返回值
    蒋廷黻著《中国近代史》-中国近代屈辱史读后感
    ASP.NET Core 上传多文件 超简单教程
    Python
    Python
    Python
    Python
    Python
    Python
    Python
  • 原文地址:https://www.cnblogs.com/yinjw/p/11757126.html
Copyright © 2011-2022 走看看