zoukankan      html  css  js  c++  java
  • MyBatis 二级缓存实现详解及使用注意事项

    二级缓存介绍

    在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。

    img

    二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

    当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

    二级缓存配置

    要正确的使用二级缓存,需完成如下配置的。

    首先,在MyBatis的配置文件中开启二级缓存。

    <setting name="cacheEnabled" value="true"/>
    

    然后,在MyBatis的 Mapper XML 中加入 cache 或者 cache-ref 标签。

    cache标签用于声明这个namespace需要使用二级缓存,并且可以自定义配置。

    <cache/>   
    
    • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
    • eviction: 定义回收的策略,常见的有FIFO,LRU。
    • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
    • size: 最多缓存对象的个数。
    • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
    • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

    cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

    <cache-ref namespace="mapper.StudentMapper"/>
    

    二级缓存实验

    public void testCacheWithoutCommitOrClose() throws Exception {
        SqlSession sqlSession1 = factory.openSession(false);  // 不自动提交事务
        SqlSession sqlSession2 = factory.openSession(false);  // 不自动提交事务
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
        System.out.println(studentMapper.getStudentById(1)); // 查询数据库
        System.out.println(studentMapper2.getStudentById(1)); // 查询数据库,二级缓存不起作用
    }
    
    public void testCacheWithCommitOrClose() throws Exception {
        SqlSession sqlSession1 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession2 = factory.openSession(false);   // 不自动提交事务
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    
        System.out.println(studentMapper.getStudentById(1)); // 查询数据库
        sqlSession1.commit(); // 提交事务
        System.out.println(studentMapper2.getStudentById(1)); // 查询缓存,二级缓存起作用
    }
    
    public void testCacheWithUpdate() throws Exception {
        SqlSession sqlSession1 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession2 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession3 = factory.openSession(false);   // 不自动提交事务
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
    
        System.out.println(studentMapper.getStudentById(1)); // 查询数据库
        sqlSession1.commit(); // 提交事务
        System.out.println(studentMapper2.getStudentById(1)); // 查询缓存,二级缓存起作用
    
        studentMapper3.updateStudentName("方方",1); // 更新数据
        sqlSession3.commit(); // 提交事务
        System.out.println(studentMapper2.getStudentById(1)); // 查询数据库,说明清除了二级缓存
    }
    
    public void testCacheWithDiffererntNamespace() throws Exception {
        SqlSession sqlSession1 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession2 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession3 = factory.openSession(false);   // 不自动提交事务
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
    
        System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // 关联查询数据库
        sqlSession1.close();
        System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 查询缓存,二级缓存起作用
    
        classMapper.updateClassName("特色一班",1); // 更新其中一个表的数据
        sqlSession3.commit(); // 提交更新事务
        System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 仍然查询缓存,引起了脏读
    }
    
    // 让ClassMapper引用StudenMapper的命名空间,这样两个映射文件对应的SQL操作都会使用同一块缓存
    public void testCacheWithDiffererntNamespace() throws Exception {
        SqlSession sqlSession1 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession2 = factory.openSession(false);   // 不自动提交事务
        SqlSession sqlSession3 = factory.openSession(false);   // 不自动提交事务
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
    
        System.out.println(studentMapper.getStudentByIdWithClassInfo(1)); // 关联查询数据库
        sqlSession1.close();
        System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 查询缓存,二级缓存起作用
    
        classMapper.updateClassName("特色一班",1); // 更新其中一个表的数据
        sqlSession3.commit(); // 提交更新事务
        System.out.println(studentMapper2.getStudentByIdWithClassInfo(1)); // 关联查询数据库,二级缓存失效
    }
    

    总结

    1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
    2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
    3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高

    参考:https://mybatis.org/mybatis-3/zh/configuration.html#settings

    http://mybatis.org/spring/zh/factorybean.html

    https://tech.meituan.com/2018/01/19/mybatis-cache.html

    https://blog.csdn.net/u010349169/article/details/41408341

    https://blog.csdn.net/mengsofts/article/details/88074790

  • 相关阅读:
    ceil函数 floor函数 floor函数 round函数 取整函数
    Havel–Hakimi algorithm(判断度数序列是否可图)
    最小费用最大流
    数论 (大数,小费马定理,欧拉定理,威尔逊定理,快速数论变换(NNT)模版)
    Give Candies(费马小定理)
    Yet Another Crosses Problem
    并查集
    杭电多校第一场-M-Code
    2018ICPC赛后总结
    营业额统计
  • 原文地址:https://www.cnblogs.com/danhuang/p/12787319.html
Copyright © 2011-2022 走看看