MyBatis源码分析のSql执行流程
文章目录
- 前言
- 一、准备工作
- 1.1、newExecutor
- 二、执行Sql
- 2.1、getMappedStatement
- 2.2、query
- 三、Cache装饰器的执行时机
- 四、补充
- 总结
前言
本篇主要介绍MyBatis解析配置文件完成后,执行sql的相关逻辑:
public class Main {public static void main(String[] args) throws Exception {//将xml构筑成configuration配置类Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//解析xml,注册成SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);try (SqlSession session = sqlSessionFactory.openSession()) {User user = session.selectOne("com.example.mybatis.mapper.UserMapper.selectById", 1);System.out.println(user);}}}
在上面的案例中,真正执行sql的核心是**User user = session.selectOne(“com.example.mybatis.mapper.UserMapper.selectById”, 1);**这一行代码。
一、准备工作
**sqlSessionFactory.openSession()**这一行代码,是执行sql前的准备工作:
首先会去获取默认的执行器类型:
执行器类型是一个枚举类:
- SIMPLE是默认执行器,每执行一次 SQL,就创建一个新的 Statement 对象。
- REUSE是可重用执行器,如果执行相同 SQL(完全一样的 SQL 语句,包括查询条件,参数),会复用之前缓存的Statement。
- BATCH是批处理执行器,支持批量执行多个 SQL,一起发送给数据库。
可以在配置文件中配置:
<configuration><settings><!-- 设置默认执行器类型:SIMPLE / REUSE / BATCH --><setting name="defaultExecutorType" value="BATCH"/></settings>
</configuration>
在获取到执行器类型后,就会进入openSessionFromDataSource方法,开启Session,在这里传入的事务隔离级别默认为null,是否手动提交事务为false(默认不会自动提交):
在 **tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);这一行代码中,会去创建事务,默认的是原生的JDBC事务:
在final Executor executor = configuration.newExecutor(tx, execType);**这一行代码,会跳转到newExecutor方法。
1.1、newExecutor
在newExecutor方法中,主要做了三件事:
- 实例化具体的
Executor对象。 - 使用装饰器模式,对
Executor进行包装。 - 处理插件的情况。
实例化具体的Executor对象。
案例中默认的是simple
还会检查是否开启了二级缓存(是否真正启用二级缓存 = cacheEnabled 为 true + Mapper 中配置了 cache标签,cacheEnabled 是默认为true的)。
这里同样使用了装饰器模式,创建CachingExecutor实例时,将当前的Executor作为参数赋值给CachingExecutor的delegate属性,而当前的Executor的实际类型是SimpleExecutor。
最终的效果是CachingExecutor中持有了一个SimpleExecutor:
SimpleExecutor、ReuseExecutor、BatchExecutor都继承自一个公共的抽象类BaseExecutor,并且实现了Executor顶级接口。而CachingExecutor同样也实现了Executor顶级接口。
SimpleExecutor/ReuseExecutor/BatchExecutor 专注于 SQL 的执行逻辑;而CachingExecutor专注于 缓存处理(缓存命中、缓存写入、缓存失效)。装饰器的目的就是将CachingExecutor和某个BaseExecutor具体的子类进行聚合。
简单理解:
CachingExecutor负责缓存。SimpleExecutor负责真正执行数据库操作。
最后还会进行插件逻辑的处理:
什么是MyBatis的插件?简单来说,是通过动态代理机制,对MyBatis内部行为进行拦截,MyBatis只允许拦截以下四种类型的核心接口方法(常见的分页插件就是基于此实现):
| 类型 | 接口 | 常见场景 |
|---|---|---|
| Executor | 执行器 | 拦截sql,做 SQL 日志、分页 |
| ParameterHandler | 参数处理器 | 拦截参数设置,修改参数值 |
| ResultSetHandler | 结果集处理器 | 修改结果集,比如字段映射处理 |
| StatementHandler | 语句处理器 | 修改 SQL 语句,如 SQL 重写、SQL 审计 |
如果需要使用插件,需要进行配置,先自定义一个插件类:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.mapping.MappedStatement;import java.util.Properties;@Intercepts({@Signature(type = Executor.class, // 拦截 Executormethod = "query", // 方法名args = {MappedStatement.class, Object.class, org.apache.ibatis.session.RowBounds.class, ResultHandler.class})
})
public class MySimpleInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println(">>> MyBatis 插件开始拦截:方法 = " + invocation.getMethod().getName());Object result = invocation.proceed(); // 执行原方法System.out.println(">>> MyBatis 插件结束拦截");return result;}@Overridepublic void setProperties(Properties properties) {// 可以在 XML 中配置插件参数System.out.println(">>> 插件参数:" + properties);}
}
然后在xml中进行设置,注意:MyBatis 对configuration内部各个子元素的顺序有严格要求:
configuration
properties
settings
typeAliases
typeHandlers
objectFactory
objectWrapperFactory
reflectorFactory
plugins ← 插件必须在这之前出现
environments
databaseIdProvider
mappers
<plugins><plugin interceptor="com.example.MySimpleInterceptor"><property name="logLevel" value="DEBUG"/></plugin>
</plugins>
进入pluginAll方法后,首先会去遍历所有自定义的插件,然后执行它的plugin方法:
默认的plugin方法:
创建动态代理
如果配置了插件,最后newExecutor拿到的对象是代理对象。

最后回到openSessionFromDataSource方法,将上一步得到的执行器,以及configuration属性,autoCommit标记,再次包装成DefaultSqlSession对象返回。到这一步为止,准备工作就完成了

二、执行Sql
执行sql,本质上就是调用上一步得到的SqlSession其中封装好底层JDBC的selectOne(查询单条),selectList(查询全部)等方法,这里以selectOne为例:
传入的参数是mapper路径+查询方法名。以及查询条件
继续调用selectList(注意下方的分支,这里是根据条件查询单条,如果查出多条则会报错)。
继续跟踪,这里会再加上一个默认的分页参数。
执行sql查询的底层方法:

2.1、getMappedStatement
在调用执行器的query方法之前,首先会去根据参数中的路径+方法名,去解析出的xml文件的configuration结果中查找该mapper以及方法是否存在。
这里的id就是mapper路径 + 方法名
最终找到的是该方法在mapper.xml中对应解析出的MappedStatement
2.2、query
query会拿到上一步得到的MappedStatement,去执行sql。此时如果设置过自定义插件,就会转到Plugin的invoke方法,首先查找这个方法是不是被声明拦截过:

符合条件,则会进入目标自定义拦截器的intercept方法,执行自定义的逻辑和目标方法:
然后执行目标方法,因为当前的Executor类型是CachingExecutor,所以进入它的query方法,在query方法中,同样做了三件事:
- 根据参数生成对应的 SQL(带 ? 占位符)和参数映射。
- 构建缓存键 CacheKey,用于标识这个 SQL 和参数是否在一级/二级缓存中已有结果。
- 调用主查询方法。
getBoundSql方法中,实际会调用sqlSource的getBoundSql,会根据动态和静态sql,走不同的实现类:
最终会得到:
然后在**CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);**这一行代码所调用的方法中,完成缓存key的设置:
会根据类名 + 方法名 + sql + 分页等参数生成一个全局唯一的缓存key
最后在query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);代码中执行sql的操作:
首先会去判断是否开启了二级缓存,如果开启了,就能拿到在解析mapper.xml时构建的装饰器对象:(包装完成的层次:SynchronizedCache线程同步缓存区->LoggingCache统计命中率以及打印日志->SerializedCache序列化->LruCache最少使用->PerpetualCache默认。)会进入if分支,经过处理后,走 一、准备工作 中创建的具体执行器。
如果获取不到二级缓存,就直接走一、准备工作 中创建的具体执行器。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 获取当前 MappedStatement 所对应的二级缓存对象(如果 mapper.xml 中配置了 <cache/>)Cache cache = ms.getCache();// 判断是否配置了二级缓存if (cache != null) {// 判断是否需要在本次查询前刷新缓存(一般是增删改操作,或者某些查询配置了 flushCache=true)flushCacheIfRequired(ms);// 如果当前查询语句允许使用缓存(useCache=true),且没有自定义的 ResultHandler// ➜ 自定义 ResultHandler 无法缓存结果if (ms.isUseCache() && resultHandler == null) {// 确保当前 SQL 中没有 OUT 参数(例如存储过程),否则不能缓存ensureNoOutParams(ms, boundSql);// 从事务缓存管理器中尝试获取缓存数据@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);// 如果缓存未命中,则执行真正的数据库查询if (list == null) {// 通过内部真正的执行器(delegate)查询数据库list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将查询结果放入缓存(注意:此时只是放入事务缓存,等事务提交后才真正写入二级缓存)tcm.putObject(cache, key, list); }// 返回缓存中的数据或数据库查询结果return list;}}// 如果没有配置缓存,或不满足缓存使用条件,直接走真实执行器查询数据库return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
执行query方法,最终底层调用的是JDBC的execute,并且在执行完成后,对返回值进行解析和封装。


三、Cache装饰器的执行时机
当执行session.commit();时,会从SynchronizedCache开始,逐个向下调用putObject方法,这里体现的是责任链模式
如果开启二级缓存,sql的返回值又需要封装成对象,则该对象必须实现序列化接口,否则在SerializedCache这一个cache会抛出异常。
责任链执行完成。

四、补充
上面案例中的执行sql方式,使用的是
User user = session.selectOne("com.example.mybatis.mapper.UserMapper.selectById", 1);
也可以通过下面的方式。
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.selectById(1);
通过这种方式,是创建目标Mapper接口的动态代理。
然后在执行mapper中对应方法时,会被代理到MapperProxy的invoke方法:

在execute方法中对于增删改查的sql进行分派,后续的操作和上文中提到的类似。

总结
MyBatis源码中,一条sql的执行,分为两大阶段:
- 准备阶段:体现是openSession方法,首先创建事务对象(默认的是JDBC的事务),创建执行器,并通过装饰器模式,包装到
CachingExecutor中。还会进行插件逻辑的处理。如果有插件,就会创建CachingExecutor代理对象并返回,否则返回普通的CachingExecutor对象。最终会将CachingExecutor对象和解析xml得到的configuration对象一起封装成SqlSession对象。 - 执行阶段:根据参数生成对应的 SQL(带 ? 占位符)和参数映射,构建缓存键 CacheKey,用于标识这个 SQL 和参数是否在一级/二级缓存中已有结果,最后会判断是否配置了二级缓存:
- 配置了, 从事务缓存管理器中尝试获取缓存数据:
- 获取不到,查询数据库。
- 获取到,返回结果。
- 没有配置,直接查询数据库。
- 配置了, 从事务缓存管理器中尝试获取缓存数据:
在事务提交,调用commit方法时,才会从SynchronizedCache开始去执行责任链,放入二级缓存。
四大拦截器的执行时机:
- Executor是在执行query时触发,上面已经提到过。
- StatementHandler 在执行prepare时触发,是在执行sql语句前的预处理阶段。


- ParameterHandler 是在预处理阶段的准备参数时触发:

- ResultSetHandler 是在sql执行完成,处理返回值结果的时候触发:

相关文章:
MyBatis源码分析のSql执行流程
文章目录 前言一、准备工作1.1、newExecutor 二、执行Sql2.1、getMappedStatement2.2、query 三、Cache装饰器的执行时机四、补充总结 前言 本篇主要介绍MyBatis解析配置文件完成后,执行sql的相关逻辑: public class Main {public static void main(Str…...
Java 大视界 -- Java 大数据中的知识图谱可视化与交互分析技术(141)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
万字长文详解|蚂蚁数据湖深度探索与业务应用实践
作者介绍:黄超傑,蚂蚁数据智能部实时数据团队数据研发工程师,致力于数据湖技术在蚂蚁落地应用、蚂蚁广告实时数仓建设、蚂蚁数据成本治理等工作。 在开始之前 如果: 你想要提升数据时效,能够接受分钟级延迟你好奇准实…...
网络空间安全(36)数据库权限提升获取webshell思路总结
一、获取数据库访问权限 寻找漏洞: SQL注入:这是最常见的方法之一。攻击者通过SQL注入漏洞,可以在数据库执行任意SQL语句,从而获取数据库中的数据,甚至可能获取数据库的访问权限。配置文件泄露:有时&#x…...
前端面经分享(25/03/19)
北京一家做协同办公软件出海的公司,技术一面,20k-40k,要求3-5年 详细聊了一下上家公司的项目上家公司的项目是不做了吗,离职原因是什么,你觉得公司的这个产品怎么样在做AI类的业务时,作为前端感觉跟常规业务…...
火山引擎(豆包大模型)(抖音平台)之火山方舟的Prompt的使用测试
前言 在大模型的使用过程当中,Prompt的使用非常的关键。原来,我对Prompt的理解不深,觉得Prompt的产生并不是很有必要。但是,自从使用了火山方舟中的“Prompt优解”之后,感受加深了,觉得Prompt是我们和大模型…...
AI助力临床诊疗:AI工具帮助确认病情
在临床诊疗中,准确确认病情是治疗成功的关键。然而,传统的诊疗方法往往依赖医生的经验和有限的检查手段,容易出现误诊或漏诊的情况。随着人工智能(AI)技术的飞速发展,AI工具在临床诊疗中的应用逐渐崭露头角…...
【平台优化】大数据集群一个客户端参数引起的任务性能差的问题
大数据集群一个客户端参数引起的任务性能差的问题 背景介绍排查过程任务慢的具体原因Executor中数据内存往磁盘溢写结果数据写入分区路径 分析解决方案 结语&思考 背景介绍 随着业务量不断扩大,平台逐步发展成HDFS多联邦的架构,这个过程中ÿ…...
【OCR】总结github上开源 OCR 工具:让文字识别更简单
前言 在数字化的时代,光学字符识别(OCR)技术成为了我们处理文档、图像文字信息的得力助手。它能够将图像中的文字信息转换为可编辑和可处理的文本数据,极大地提高了信息处理的效率。今天,我要给大家介绍一些优秀的开源…...
为wordpress核心functions文件瘦身
在wordpress主题开发过程中,常会用到functions.php这个文件,通过这个文件,可以让wordpress主题变得很强大。但是随着,功能的越来越多functions文件也会变得越来越臃肿庞大。要修改起functions文件来,也就越麻烦。特别是…...
Jmeter使用之http请求默认值
一、添加【HTTP请求默认值】 1、添加方式 【线程组】---->右击【添加】---->【配置元件】---->【HTTP请求默认值】 2、作用 在发送http请求的时候,自带默认值设置的参数(常用:【协议】、【服务器名称或IP】、端口号、内容编码&…...
【QT】】qcustomplot的初步使用二
功能预期: 在简单显示曲线()基础上加功能: 1.在曲线区域里,X轴可以随鼠标滚轮变化将图像缩放、随鼠标左键进行曲线移动; 2.在曲线区域里,Y轴的量程可以随Y轴数据大小自适应; 3.可以…...
让 Google Play 成为助力 PC 游戏增长的最佳平台
作者 / Google Play 游戏副总裁兼总经理 Aurash Mahbod 我们正在加强多平台游戏产品,并将在今年的游戏开发者大会 (GDC) 上发布激动人心的消息。我们将为用户带来更多游戏,提供更多跨设备畅玩游戏的方式,并进一步优化游戏体验。在游戏开发者大…...
脚本一键式启动Nginx、Mysql、Redis
此脚本包含拉取镜像、数据卷挂载、容器启动三大部分,可一键式安装三大环境 新建一个depoy.sh文件在服务器上,然后复制以下内容。 给脚本文件添加执行权限 chmod x depoy.sh # 文件的当前目录下 如果需要修改数据库MYSQL密码和Reids密码 MYSQL_ROO…...
路径问题(greedy):地下城游戏
题目描述: 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健…...
LS-NET-008-OSPF、BGP、RIP三大路由协议
LS-NET-008-OSPF、BGP、RIP三大路由协议 从网络层级、协议特性和厂商实现三个维度对比OSPF、BGP、RIP三大路由协议: 一、网络协议分层架构对比 graph TD L7[应用层] --> BGP(TCP/179) L7 --> RIP(UDP/520) L4[网络层] --> OSPF(IP协议号89) L4 -->|报…...
electron框架(3.0)主程序与桥梁与渲染,以及之间的通信
每个页面程序通过渲染和主进程通信,主进程根据需求调用Native Api来实现功能。 实际,每个页面和主程序通信时,需要建个桥梁来管理它们的通信,preload.js(自己创建),来管理实现通信。 ----创建preload.js定义桥梁js&a…...
python写入excel多个sheet表 以及追加sheet表
python写入excel多个sheet表 以及追加sheet表 写入多个sheet表在excel追加sheet表 可将不同DataFrame分别写入指定Sheet(如初始写入"箱_4"和"箱_2"),并通过封装函数append_to_excel支持动态追加新Sheet到现有文件&#x…...
【大模型微调】使用Llama Factory实现中文llama3微调
【大模型微调】使用Llama Factory实现中文llama3微调 github链接 为什么不用基座模型:95%用的英文数据训练,训练效果不好 所以用的Llama3-99-Chinese-Chat(别人微调过的再微调)...
群体智能优化算法-牛顿-拉夫逊优化算法(Newton-Raphson-Based Optimizer, NRBO,含Matlab源代码)
摘要(Abstract) 牛顿-拉夫逊优化算法(Newton-Raphson-Based Optimizer, NRBO)是一种新型群体智能优化算法,受牛顿-拉夫逊方法求解非线性方程的启发。NRBO 结合牛顿-拉夫逊搜索规则(Newton-Raphson Search …...
JS—原型与原型链:2分钟掌握原型链
个人博客:haichenyi.com。感谢关注 一. 目录 一–目录二–原型三–原型链 二. 原型 什么是原型? 每个JavaScript对象都有一个原型,这个原型也是一个对象。比方说 function Person(name) {this.name name; } let person new Person(&quo…...
Canal 解析与 Spring Boot 整合实战
一、Canal 简介 1.1 Canal 是什么? Canal 是阿里巴巴开源的一款基于 MySQL 数据库增量日志解析(Binlog)中间件,它模拟 MySQL 的从机(Slave)行为,监听 MySQL 主机的二进制日志(Binl…...
「数据会说话」:让AI成为你的数据分析魔法师 ✨
文章目录 「数据会说话」:让AI成为你的数据分析魔法师 ✨1. 核心技术 🛠️1.1 LIDA智能可视化引擎1.1.1 核心优势1.1.2 核心功能 1.2 前端交互框架 2. 系统架构设计 🏗️2.1 功能模块组成2.2 用户隔离与数据安全 🔒2.2.1 用户身份…...
图论——Prim算法
53. 寻宝(第七期模拟笔试) 题目描述 在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。 不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通…...
2025年汽车加气站操作工考试精选题库
汽车加气站操作工题库中题目及答案: 单项选择题 1、按压力容器的设计压力分为( )个压力等级。 A. 3 B. 4 C. 5 答案:B 2、缓冲罐的安装位置在天然气压缩机( )。 A. 前 B. 后 C. 中间 答案&#…...
1. 初识golang微服务-gRPC
单体架构 在这里插入图片描述 微服务架构 RPC架构(远程过程调用) 服务端实例代码: package mainimport ("fmt""net""net/rpc""time" )type Hello struct { }func (h Hello) SayHello(req stri…...
C++20 指定初始化器
指定初始化器 对于聚合,C20 提供了一种方法来指定应该用传递的初始值初始化哪个成员,但只能使用它 来跳过参数。 假设有以下聚合类型: struct Value {double amount{0};int precision{2};std::string unit{"Dollar";}; }; 那么&#x…...
【从零开始学习计算机科学】设计模式(二)工厂模式、抽象工厂模式、单例模式、建造者模型、原型模式
【从零开始学习计算机科学】设计模式(二)工厂模式、抽象工厂模式、单例模式、建造者模型、原型模式 工厂模式主要特点类型适用场景抽象工厂模式主要特点工作原理适用场景举例优点缺点总结单例模式主要特点工作原理适用场景优点缺点总结建造者模式主要特点工作原理适用场景优点…...
视频翻译器免费哪个好?轻松玩转视频直播翻译
你是不是觉得看外语视频很麻烦?每次遇到喜欢的外语电影、电视剧或动漫,总是要等字幕组的翻译,或者因为语言不通而错过精彩的情节。 这个时候,掌握多语种直播翻译方案就显得尤为重要,有了实时字幕,看外语视…...
mysql 数据库异常排查
1、简单查询语句无法执行 线上问题记录,有个删除语句走不了索引导致锁表,其他所有引用该表的操作需要等待删除成功后才能执行,导致服务不可用 排查思路,先看是否有长时间未提交的事务 SELECT * FROM information_schema.INNOD…...
