mybatis一级缓存源码解读,从一个bug开始
文章目录
案发现场
描述
一个servce里面,mapper查询,不存在数据,在另一个service新增,然后回到第一个service查询,返回null。也就是新增的数据第一个service是看不见的。
mybatis-spring官网
源码走起
一级缓存
这个得从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是不会清除缓存的!!!
参考博客
改正
2020/03/30 这一天技术群有位老哥,问了一个问题,就是一个mapper去查询,一个service去查询,结果命中缓存了,因为开了事务。
注意了开了事务,开了事务,开了事务!!!
上面说了文章说了很多,重点:前提是开了事务,生命周期变成session级别,因为我们项目是使用声明式事务,只要service方法开头以get,select开头都会默认加readonly为true事务。
当你加了事务一切就变了,以session为声明周期,只有出现update,insert才会去查数据库~
解决方案:
mybatis.configuration.local-cache-scope=statement
mybatis-plus:
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐




所有评论(0)