记一次 Mybatis 一级缓存清理无效引起的源码走读
今天对象在学习 Mybatis 时发现 org.apache.ibatis.session.SqlSession 对象的 clearCache() 方法并不能清理一级缓存, 同一 session 下相同查询条件返回的结果还是旧值。测试代码如下
上网搜索
网上搜索找到了相同问题, 并没有人解答。例如:https://www.iqismart.com/topi...
查看官方文档
http://www.mybatis.org/mybati...
SqlSession 实例有一个本地缓存在执行 update,commit,rollback 和 close 时被清理。要明确地关闭它(获取打算做更多的工作) ,你可以调用 clearCache()。
看起来, 没什么问题, 方法也没有被标记成废弃.
打印详细日志
先把日志配上, 看看有没有打印什么有用的信息, 添加 slf4j、logback 依赖,添加 logback.xml , 日志级别设置为 DEBUG 运行后未看到跟清理缓存有关的信息, 调整日志级别为 TRACE 后依旧没有.
<configuration>
<contextName>mybatis</contextName>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger %msg%n</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>mybatis.log</file>
<encoder>UTF-8</encoder>
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="stdout"/>
<appender-ref ref="file"/>
</root>
</configuration>
看 clearCache() 源码
上面方法都没有收获, 只能看源码了.第一步, 先看一下 clearCache() 做了什么, 下面会大规模贴图
注意 PerpetualCache 类的 cache 变量
org.apache.ibatis.cache.impl.PerpetualCache#cache 就是一个 HashMap
到此, clearCache() 已经完结, 最终就是调用一个 HashMap 的 clear() 方法
看 selectOne() 源码
这一步没有什么好看的, 就是封一层 selectList
第一个方法会间接调用第二个, 只是少了一个分页相关的 RowBounds
把传入的 statement 值变成 MappedStatement, 由于不是我们查看源码的重点, 可以直接跳过.
不过可以学习到 Mybatis 其实是把我们写的 xml 文件抽象成 MappedStatement , 在执行 sql 时需要先使用 statement (也就是我们 xml 中 select 标签中的 id) 去配置中 get 出整个 MappedStatement, MappedStatement 包含了 resultMaps 之类的, 一会儿 sql 返回时映射结果集很可能要用到.
这一步把 MappedStatement 变成 BoundSql, BoundSql 应该就是每条 SQL 的抽象.
还会根据 MappedStatement (xml 文件)、parameter (sql 参数)、rowBounds (分页信息)、BoundSql (SQL) 生成一个 CacheKey (缓存 key) .
已经跟我们想要了解的东西沾点边了.
这一步是取 MappedStatement 对象的 Cache , 暂时不知道是什么缓存(可能是二级缓存), 可以知道的是和刚才看 clearCache() 清理的不是同一个东西. 调试发现返回值是 null, 不关心继续往下看
这里到了 BaseExecutor 类, 152 行会根据 CacheKey 从 localCache 获取结果.
而且和 clearCache() 方法清理的是同一个缓存对象.
基本可以确定 Mybatis 就是在这里从一级缓存获取结果后返回, 需要重点关注.
阶段性成果
反复运行发现如下规律:
- 如果第二次查询前不加 sqlSession.clearCache(); 可以从 localCache get 出结果
- 如果第二次查询前加 sqlSession.clearCache(); localCache get 结果为空
由此可以得出结论:
sqlSession.clearCache() 方法是有效的, 清理一级缓存后第二次查询结果依然和第一次相同, 和 Mybatis 一级缓存并无关系.
既然如此, 要想知道结果, 只能继续往下跟踪, 看一级缓存为空后, Mybatis 是怎么处理的.
可以看出, 为空后调用了 queryFromDatabase 方法,从方法名可以理解, 会去数据库查询
第 322 行先往一级缓存设置一个占位符, 并无实际含义
第 324 行执行查询动作, 需要重点关注
第 326 行根据缓存 key 清理一级缓存
第 328 行重新设置一级缓存
第 330 行看到一个面熟的东西, 在 clearCache() 时会同时清理 localCache 和 localOutputParameterCache, 如果执行的是存储过程, 会把参数缓存起来
继续跟踪 doQuery 方法
先是获取 MappedStatement 的配置, 创建一个 StatementHandler, 加工成 JDBC 标准的 Statement , 这中间隐藏了无数细节, 还是那句话, 不是我们关心的重点, 继续跟踪 Query 方法
经过 RoutingStatementHandler 路由分发, 到达 SimpleStatementHandler
方法体只有三行
第一行拿出具体 SQL
第二行调用 statement.execute() 方法, 这里已经到了 JDBC 驱动层, mysql 驱动包会帮我们封装请求包发送给 mysql 服务器并把响应结果映射到 jdbc 规范的对象中
第三行处理返回结果集
其实执行完 execute 方法, 就可以从 PreparedStatement 对象 get 出想要的结果集, 但贸然 get 会影响 Mybatis 处理, 还是继续跟踪 handleResultSets 方法吧
方法一开始声明了一个 multipleResults , 这个就是最终的结果集.
接着分别处理 ResultMap 和 ResultSet, 把 mysql 返回的结果按照 xml 中的规则映射成指定对象
由于 xml 中的 select 并没有定义 resultSets , 只关注上半部分即可, 断点设在 198 行
可以看出 mysql 服务器返回的确实是旧值,
阶段性成果
至此可以确定一级缓存清理无效的问题和应用没有关系.
还能是什么问题呢, 难道是事务的隔离级别导致的, 应用只是简单的查询, 连事务管理器都没有配置, 要有问题也只能怀疑 mysql 服务器.
查询数据库的默认隔离级别
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
竟然是"可重复读", 好了, 原因找到, 此贴终结.
解决
解决办法就是把事务的默认隔离级别设置成 "读已提交".
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
记一次 Mybatis 一级缓存清理无效引起的源码走读的更多相关文章
- MyBatis 一级缓存、二级缓存全详解(一)
目录 MyBatis 一级缓存.二级缓存全详解(一) 什么是缓存 什么是MyBatis中的缓存 MyBatis 中的一级缓存 初探一级缓存 探究一级缓存是如何失效的 一级缓存原理探究 还有其他要补充的 ...
- Mybatis一级缓存、二级缓存详讲
Mybatis 一级缓存.二级缓存 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 查询缓存 首先,我们先看一下这个标题“查询缓存”,那就说明跟增.删.改是没有任何关联的,只有在查询 ...
- MyBatis 一级缓存与二级缓存
MyBatis一级缓存 MyBatis一级缓存默认开启,一级缓存为Session级别的缓存,在执行以下操作时一级缓存会清空 1.执行session.clearCache(); 2.执行CUD操作 3. ...
- MyBatis一级缓存引起的无穷递归
MyBatis一级缓存引起的无穷递归 引言: 最近在项目中参与了一个领取优惠劵的活动,当多个用户领取同一张优惠劵的时候,使用了数据库锁控制并发,起初的设想是:如果多个人同时领一张劵,第一个到达的人领取 ...
- mybatis一级缓存详解
mybatis缓存分为一级缓存,二级缓存和自定义缓存.本文重点讲解一级缓存 一:前言 在介绍缓存之前,先了解下mybatis的几个核心概念: * SqlSession:代表和数据库的一次会话,向用户提 ...
- 0065 MyBatis一级缓存与二级缓存
数据库中数据虽多,但访问频率却不同,有的数据1s内就会有多次访问,而有些数据几天都没人查询,这时候就可以将访问频率高的数据放到缓存中,就不用去数据库里取了,提高了效率还节约了数据库资源 MyBatis ...
- MyBatis 一级缓存避坑
MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选: package org.apache.ibatis.session; /** * @autho ...
- 关于mybatis 一级缓存引发的问题
场景: 由于在一个方法中存在多个不同业务操作 private void insertOrUpdateField(CompanyReport entity) { //计算并数据 calcReportDa ...
- MyBatis一级缓存(转载)
<深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 http://demo.netfoucs.com/luanlouis/article/details/41 ...
- Mybatis一级缓存和二级缓存总结
1:mybatis一级缓存:级别是session级别的,如果是同一个线程,同一个session,同一个查询条件,则只会查询数据库一次 2:mybatis二级缓存:级别是sessionfactory级别 ...
随机推荐
- idea配置gradle国内镜像源
项目文件中找到build.gradle文件,修改其中的buildscript和allprojects地址: buildscript { repositories { maven{ url 'http: ...
- 【Spring】Sring基础概念(黑马SSM学习笔记)
目录 Spring简介 Spring是什么 Spring发展 Spring优势 Spring体系结构 Spring快速入门 Spring程序开发步骤 不用Spring的一般步骤 使用Spring框架 ...
- Jackson ObjectMapper - 指定对象属性的序列化顺序
注释很有用,但在任何地方应用起来都会很痛苦.您可以配置整个 ObjectMapper 以这种方式工作 当前杰克逊版本: objectMapper.configure(MapperFeature.SOR ...
- K8S学习笔记之卸载K8S集群
阅读目录 0x00 概述 0x01 操作 0x00 概述 有时候需要卸载已安装在本机的K8S服务和服务,本文卸载的K8S面向使用kubeadm或者二进制方法安装的,不涉及使用rpm包安装的集群: 主 ...
- Qt编写物联网管理平台44-告警邮件转发
一.前言 上一篇文章说的是告警短信发送,这种效率非常高,缺点也很明显,需要购买特定的短信硬件设备支持才行,而且每条短信都要收费,如果要求发送的短信数量特别多,这个费用常年累月下来也是不少的,客户就不愿 ...
- Qt数据库应用13-通用数据库分页
一.前言 数据库分页展示,在所有的涉及到数据库记录的项目中都是需要的,除了简单的设备信息表.用户信息表这种很少几条几十条数据量的表除外,其余的日志记录表等都需要分页展示数据,少量的数据可以滚动条下拉查 ...
- Qt音视频开发23-通用视频控件
一.前言 在之前做的视频监控系统中,根据不同的用户需要,做了好多种视频监控内核,有ffmpeg内核的,有vlc内核的,有mpv内核的,还有海康sdk内核的,为了做成通用的功能,不同内核很方便的切换,比 ...
- Qt开源作品35-秘钥生成器
一.前言 在很多商业软件中,需要提供一些可以试运行的版本,这样就需要配套密钥机制来控制,纵观大部分的试用版软件,基本上采用以下几种机制来控制. 远程联网激活,每次启动都联网查看使用时间等,这种方法最完 ...
- [转]OpenCV使用之-----BruteForceMatcher无法使用
最近Opencv升级比较快,从2.4.0到2.4.1到2.4.2,使得我这个还在使用2.3.1的人很不好意思,而且听说新版本里添加了tbb并行功能,急着想用这些功能的我赶紧下了2.4.2. 按部就班的 ...
- V3Det&Bigdetection下载记录
V3Det dataset https://opendatalab.com/V3Det BigDetection