案发现场

描述
  一个servce里面,mapper查询,不存在数据,在另一个service新增,然后回到第一个service查询,返回null。也就是新增的数据第一个service是看不见的。

mybatis-spring官网

mybatis官网
在这里插入图片描述

源码走起

一级缓存

  这个得从sqlSession说起,一级缓存以sqlSession commit结束,因为commit会清除缓存。
一级缓存用的是BaseExecutor,二级使用CachingExecutor

CacheKey

一级缓存使用key,value。key就是CacheKey。
在这里插入图片描述
Mybatis 的缓存使用了 mappedStementId + offset + limit + SQL + queryParams + environment 生成的hashcode作为 key。

sqlSession

SqlSessionTemplate
  一个创建sqlSession的类,相当重要!
在这里插入图片描述
commit相当重要,一旦没有commit就可能命中mybatis缓存。

SqlSessionUtils
在这里插入图片描述
这个决定了sqlSession是否要commit事务。也是相当重要

BaseExecutor

查询业务
在这里插入图片描述
新增,更新
在这里插入图片描述
clearLocalCache()清除缓存

实践一哈

第一个栗子
使用单元测试Junit,测试一个mapper查询两个相同的sql

你会发现他们之间commit事务了。导致清除缓存,重新到数据库查询。

if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }

在这里SqlSessionHolder为空,导致commit了。

第二个栗子
在这里插入图片描述
从StackOverflowError说到mybatis一级缓存

分析
  我们在idea debug模式下,看到第一次查询创建了一个sqlSession,查询完没有commit,也就是缓存没有清除。为啥?因为service级别调用会有个tx事务的概念,根据spring事务还是有区别的。
  这时SqlSessionHolder不为空,而且存了当前sqlSession。这个就是是否需要commit的依据!

  在新建数据的时候是在其他的service,它会创建新的sqlSession来处理,处理完commit。

  接着再返回原来的sqlSession再次查询数据,这个时候缓存不为空,命中缓存,查到的为空。

纠错

  网上很多说sping里面使用mybatis一级缓存会失效,其实分情况的。
  如果是mapper级别调用必然会commit事务,如果是service级别调用,如果没有insert,update,delete是不会清除缓存的!!!

参考博客

Mybatis 缓存系统源码解析

改正

2020/03/30 这一天技术群有位老哥,问了一个问题,就是一个mapper去查询,一个service去查询,结果命中缓存了,因为开了事务

注意了开了事务,开了事务,开了事务!!!

上面说了文章说了很多,重点:前提是开了事务,生命周期变成session级别,因为我们项目是使用声明式事务,只要service方法开头以get,select开头都会默认加readonly为true事务。

当你加了事务一切就变了,以session为声明周期,只有出现update,insert才会去查数据库~

解决方案:

mybatis.configuration.local-cache-scope=statement

mybatis-plus:
在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐