MyBatis(七):MyBatis缓存详解(一级缓存/二级缓存)

  1. 一级缓存

    ​ MyBatis一级缓存上SqlSession缓存,即在统一SqlSession中,在不执行增删改操作提交事务的前提下,对同一条数据进行多次查询时,第一次查询从数据库中查询,完成后会存入缓存,其余从缓存中直接读取。MyBatis一级缓存默认开启。

  2. 二级缓存

    ​ MyBatis二级缓存是命名空间NameSpace缓存,也可理解为二级缓存被多个SqlSession共享,是一个全局变量。

    ​ 二级缓存默认是关闭的,需要手动配置进行开启。开启二级缓存后,数据查询流程为:二级缓存->一级缓存->数据库。

    • 二级缓存开启

      1. 实体类需要实现Serializable接口

      2. 核心配置文件增加标签

      <settings> 
        <setting name="cacheEnabled" value="true"/> 
      </settings>
      
      1. 在需要开启的mapper中添加标签
      <!--开启二级缓存--> 
      <cache/>
      
      • eviction属性可以设置缓存回收策略,默认LRU策略

        • LRU – 最近最少回收,移除最长时间不被使用的对象
        • FIFO – 先进先出,按照缓存进入的顺序来移除它们
        • SOFT – 软引用,移除基于垃圾回收器状态和软引用规则的对象
        • WEAK – 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
      • flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值

      • readOnly: 是否只读;

        false读写(默认):MyBatis 觉得数据可能会被修改

        true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。

      • size : 缓存存放多少个元素

      • type: 指定自定义缓存的全类名(实现Cache 接口即可)

      • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

        例如:

        <cache eviction="LRU" flushInterval="1000*60*60*24*7" readOnly="true" blocking="false" type="MyCache" size="1000"/>
        
    • 二级缓存测试,多个SqlSession

      InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
      User param = new User();
      param.setId(1);
      
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
      User user1 = userDao1.findOne(param);
      System.out.println(user1);
      sqlSession1.close();
      
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
      User user2 = userDao2.findOne(param);
      System.out.println(user2);
      sqlSession2.close();
      

      控制台查看输出日志,发现缓存已命中—Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5

      14:26:48,608 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
      14:26:48,611 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      14:26:48,952 DEBUG PooledDataSource:406 - Created connection 543846639.
      14:26:48,952 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:48,957 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
      14:26:48,980 DEBUG findOne:159 - ==> Parameters: 1(Integer)
      14:26:49,004 DEBUG findOne:159 - <==      Total: 1
      com.rangers.entity.User{id=1, name='rangers', address='杭州'}
      14:26:49,007 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:49,010 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:49,011 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
      14:26:49,014 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
      com.rangers.entity.User{id=1, name='rangers', address='杭州'}
      
    • 二级缓存失效条件

      • 首次SqlSession未提交事务时,二级缓存无法命中

        SqlSession未提交时,执行结果未放入二级缓存中,这时候第二个SqlSession在查询时候是无法命中的

        例如:调整sqlSession1.close();在sqlSession2执行之后,就不会命中缓存

        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        User param = new User();
        param.setId(1);
        
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = userDao1.findOne(param);
        System.out.println(user1);
        
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        User user2 = userDao2.findOne(param);
        System.out.println(user2);
        
        sqlSession1.close();
        sqlSession2.close();
        

        查看控制台输出,发现两次缓存命中都是0.0,Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0

        14:49:39,073 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        14:49:39,075 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        14:49:39,385 DEBUG PooledDataSource:406 - Created connection 543846639.
        14:49:39,385 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,389 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        14:49:39,408 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        14:49:39,433 DEBUG findOne:159 - <==      Total: 1
        com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        14:49:39,434 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        14:49:39,434 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        14:49:39,489 DEBUG PooledDataSource:406 - Created connection 2079179914.
        14:49:39,489 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,491 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        14:49:39,492 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        14:49:39,495 DEBUG findOne:159 - <==      Total: 1
        com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        14:49:39,497 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,499 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,500 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        14:49:39,500 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,502 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,502 DEBUG PooledDataSource:363 - Returned connection 2079179914 to pool.
        
      • 更新提交事务,与一级缓存一样,增删改操作提交事务会清空缓存

        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        User param = new User();
        param.setId(1);
        
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = userDao1.findOne(param);
        System.out.println("sqlSession1查询结果:"+user1);
        sqlSession1.close();
        
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        param.setName("游骑兵");
        param.setAddress("西安");
        Boolean flag = userDao2.updateUser(param) > 0;
        System.out.println("执行更新操作结果:"+flag);
        sqlSession2.commit();
        sqlSession2.close();
        
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
        User user3 = userDao3.findOne(param);
        sqlSession3.close();
        System.out.println("sqlSession3查询结果:"+user3);
        

        查看控制台输出,经过sqlSession2提交事务后,sqlSession3的查询缓存命中率为0.0

        15:00:33,901 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        15:00:33,904 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,263 DEBUG PooledDataSource:406 - Created connection 543846639.
        15:00:34,263 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,267 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:00:34,290 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:00:34,314 DEBUG findOne:159 - <==      Total: 1
        sqlSession1查询结果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        15:00:34,317 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,321 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,321 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        15:00:34,321 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,322 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
        15:00:34,322 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,325 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
        15:00:34,325 DEBUG updateUser:159 - ==> Parameters: 游骑兵(String), 西安(String), 1(Integer)
        15:00:34,334 DEBUG updateUser:159 - <==    Updates: 1
        执行更新操作结果:true
        15:00:34,335 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,445 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,448 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,450 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        15:00:34,450 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        15:00:34,450 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,451 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
        15:00:34,451 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,453 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:00:34,454 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:00:34,458 DEBUG findOne:159 - <==      Total: 1
        15:00:34,458 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,461 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,462 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        sqlSession3查询结果:com.rangers.entity.User{id=1, name='游骑兵', address='西安'}
        
    • 多表操作时,对MyBatis二级缓存的影响

      1. 问题:未同一namespace下进行增删改提交操作时,其他namespace的缓存是无法感知的

      例如:两个Mapper文件,AMapper.xml和BMapper.xml,B修改了user表中的内容,A是感知不到的,那么再从A里查询如果用到了缓存,就是旧的数据。

      ​ 新增UserMapper1.xml ,修改命名空间为com.rangers.dao.IUserDao1

      InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
      User param = new User();
      param.setId(1);
      
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
      User user1 = userDao1.findOne(param);
      System.out.println("sqlSession1查询结果:"+user1);
      sqlSession1.close();
      
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      param.setName("游骑兵");
      param.setAddress("西安");
      Boolean flag = sqlSession2.update("com.rangers.dao.IUserDao1.updateUser",param) > 0;
      System.out.println("执行更新操作结果:"+flag);
      sqlSession2.commit();
      sqlSession2.close();
      
      SqlSession sqlSession3 = sqlSessionFactory.openSession();
      IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
      User user3 = userDao3.findOne(param);
      sqlSession3.close();
      System.out.println("sqlSession3查询结果:"+user3);
      

      ​ 查看控制台输出,发现sqlSession2成功更新用户信息后,sqlSession3进行查询时二级缓存命中,依旧是sqlSession1中的结果,查询结果错误

      15:33:54,792 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      15:33:55,164 DEBUG PooledDataSource:406 - Created connection 1165303897.
      15:33:55,165 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,168 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
      15:33:55,186 DEBUG findOne:159 - ==> Parameters: 1(Integer)
      15:33:55,212 DEBUG findOne:159 - <==      Total: 1
      sqlSession1查询结果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
      15:33:55,215 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,218 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,218 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
      15:33:55,219 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      15:33:55,220 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
      15:33:55,220 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,224 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
      15:33:55,225 DEBUG updateUser:159 - ==> Parameters: 游骑兵(String), 西安(String), 1(Integer)
      15:33:55,233 DEBUG updateUser:159 - <==    Updates: 1
      执行更新操作结果:true
      15:33:55,233 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,351 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,354 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,355 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
      15:33:55,357 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
      sqlSession3查询结果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
      
      
      1. 解决

        在查询的Mapper文件中引入cache-ref标签,指向执行增删改操作表的命名空间,例如在UserMapper.xml中增加一下标签

        <cache-ref namespace="com.rangers.dao.IUserDao1"/>
        

        在此执行,查看控制台输出,发现第三次查询已直接从数据库中进行查询,结果正确

        15:46:05,486 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
        15:46:05,489 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,788 DEBUG PooledDataSource:406 - Created connection 1165303897.
        15:46:05,789 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,793 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:46:05,822 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:46:05,842 DEBUG findOne:159 - <==      Total: 1
        sqlSession1查询结果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        15:46:05,845 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,848 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,848 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        15:46:05,849 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,849 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
        15:46:05,849 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,852 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
        15:46:05,852 DEBUG updateUser:159 - ==> Parameters: 游骑兵(String), 西安(String), 1(Integer)
        15:46:05,865 DEBUG updateUser:159 - <==    Updates: 1
        执行更新操作结果:true
        15:46:05,865 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,970 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,974 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,976 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        15:46:05,976 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
        15:46:05,976 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,976 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
        15:46:05,976 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,979 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:46:05,979 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:46:05,985 DEBUG findOne:159 - <==      Total: 1
        15:46:05,985 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,989 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,990 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        sqlSession3查询结果:com.rangers.entity.User{id=1, name='游骑兵', address='西安'}
        
    • 是否应该使用二级缓存

      二级缓存的注意事项:

      1. 缓存是以namespace为单位的,不同namespace下的操作互不影响。

      2. insert,update,delete操作会清空所在namespace下的全部缓存。

      3. 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace

      4. 多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。

        如果你遵守二级缓存的注意事项,那么你就可以使用二级缓存。

        如果不能使用多表操作,二级缓存不就可以用一级缓存来替换掉吗?而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。

    引用内容参考:https://www.cnblogs.com/cxuanBlog/p/11333021.html

给TA买糖
共{{data.count}}人
人已赞赏
经验教程

医学图像配准 | Voxelmorph 微分同胚 | MICCAI2019

2021-3-11 15:54:00

经验教程

自动化测试工具(基于WordCount作业)

2021-3-11 16:06:00

⚠️
免责声明:根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。 本站为个人博客非盈利性站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途,网站会员捐赠是您喜欢本站而产生的赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求。本站部份代码及教程来源于互联网,仅供网友学习交流,若您喜欢本文可附上原文链接随意转载。
无意侵害您的权益,请发送邮件至 momeis6@qq.com 或点击右侧 私信:momeis 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索