Appearance
MyBatis 的 Mapper、Executor 和 SQL 执行链路
先说结论
- Mapper 本身只是接口入口,真正执行 SQL 的核心在
Executor - 一次 MyBatis 调用会依次经过 Mapper 代理、
MappedStatement、参数处理、Statement 执行和结果映射 - 真正排障时,很多问题都能归到三层:SQL 本身、执行器行为、映射结果
一、先把三个角色分清楚
1. Mapper
Mapper 更像“DAO 的声明式门面”。
你调用 userMapper.selectById(1) 时,并不是接口自己有实现,而是 MyBatis 给它生成了代理对象。
2. MappedStatement
这是 MyBatis 对一条 SQL 的完整描述,里面会保存:
- SQL 来源
- 入参映射
- 返回值映射
- 执行类型
- 缓存配置
可以把它理解成“这条 SQL 的运行说明书”。
3. Executor
Executor 是真正干活的执行器。
它负责:
- 调一级缓存
- 创建 StatementHandler
- 执行查询或更新
- 返回结果
常见实现里,最常见的是 SimpleExecutor、ReuseExecutor、BatchExecutor。
二、一次查询大致会经过什么
以一个普通 select 为例,链路大致是:
- 调用 Mapper 接口方法。
- 代理对象定位到对应的
MappedStatement。 - MyBatis 根据参数构造
BoundSql。 - Executor 创建 StatementHandler。
- StatementHandler 负责预编译、设参、执行。
- ResultSetHandler 把结果集映射成对象。
- 返回给调用方,并视情况写入一级缓存。
这条链路一旦理顺,后面很多问题就不再神秘,比如:
- 为什么同一个会话里查两次可能不走库
- 为什么批量写和普通写行为不同
- 为什么动态 SQL 最后能生成一条完整 SQL
三、Executor 到底有什么区别
1. SimpleExecutor
每次执行都新建 Statement,简单直接,最容易理解。
2. ReuseExecutor
会复用 Statement,适合 SQL 模板重复较多的场景,但收益通常没有很多人想象得那么大。
3. BatchExecutor
适合批量更新、批量插入。
它真正的价值在于减少网络往返和数据库交互次数,但也会带来:
- 错误定位更靠后
- 事务边界更敏感
- 批量失败处理更复杂
四、平时最容易在哪几层出问题
1. SQL 和参数层
比如:
- 动态 SQL 拼错
- 参数名不一致
#{}/${}用错
2. 执行器与缓存层
比如:
- 一级缓存导致“为什么刚更新完又查到旧值”
- 批处理没及时 flush
- 不同 SqlSession 行为不一致
3. 结果映射层
比如:
- 字段名和属性名对不上
- 嵌套结果映射导致对象结构异常
- 返回多行却只接单对象
五、排查时更实用的顺序
如果线上发现 MyBatis 行为不符合预期,我更建议按这个顺序看:
- 先看最终 SQL 是什么。
- 再看参数有没有正确绑定。
- 再看是不是执行器、缓存或批处理行为影响了结果。
- 最后看结果映射是不是把数据“接错了”。
很多时候不是 MyBatis 太复杂,而是没先把问题落到正确层级。
总结
MyBatis 的 Mapper、Executor 和 SQL 执行链路可以简单理解成:Mapper 负责入口,MappedStatement 负责描述,Executor 负责执行。只要把这三层分清,后面无论是查缓存、看批处理,还是排查结果映射问题,都会顺很多。