MyBatis 主流程详解:跟着一条 SQL 走遍完整执行链路
在阅读该文章之前,建议先看 MyBatis核心设计模式详解,或者两篇文章结合着看,互为补充。
目前国内 Java 后端持久层最主流的三大框架是 MyBatis-Plus、Spring Data JPA 和 MyBatis-Flex,其中 MyBatis-Plus 和 MyBatis-Flex 均基于 MyBatis 增强,足以证明 MyBatis 的核心地位。
下面我们跟着一个 SQL 请求从头到尾走一遍它的主流程。
示例代码
项目结构

项目代码
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.19</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</project>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- MyBatis自己实现的一个简易连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.5.101:3306/blog_system?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&autoReconnect=true"/>
<property name="username" value="hostonly_user"/>
<property name="password" value="123456"/>
<!-- 连接池配置 -->
<property name="poolMaximumActiveConnections" value="10"/> <!-- 最大活跃连接数 -->
<property name="poolMaximumIdleConnections" value="5"/> <!-- 最大空闲连接数 -->
<property name="poolMaximumCheckoutTime" value="20000"/> <!-- 最大 checkout 时间 -->
</dataSource>
</environment>
</environments>
<!-- 加载Mapper映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
User.java & UserMapper.java
//entity -> User
public class User {
private int id;
private String username;
private String email;
// 构造方法
public User(String username, String email) {
this.username = username;
this.email = email;
}
// Getters and Setters
// ......
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', email=" + email + "}";
}
}
//mapper -> UserMapper
public interface UserMapper {
// 查(单个)
User selectUserById(Integer id);
// 查(所有)
List<User> selectAllUsers();
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 根据ID查询用户 -->
<select id="selectUserById" resultType="com.example.entity.User">
SELECT id, username, email
FROM user
WHERE id = #{id}
</select>
<!-- 查询所有用户 -->
<select id="selectAllUsers" resultType="com.example.entity.User">
SELECT id, username, email FROM user
</select>
</mapper>
测试类
public class Main {
public static void main(String[] args) throws Exception {
// 加载配置
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 查询ID为1的用户
System.out.println("查询id为1的用户");
User user = mapper.selectUserById(1);
System.out.println(user);
System.out.println("-------------------------------------------------------");
System.out.println("查询所有的用户");
List<User> users = mapper.selectAllUsers();
users.forEach(e -> System.out.println(e.toString()));
}
}
}
测试结果:

Debug 追踪一条 SQL 的完整执行过程
下面以main方法中查询id为1的用户为例,一步一步debug
1. 获取SqlSessionFactory
// 加载配置
//1
String resource = "mybatis-config.xml";
//2
InputStream inputStream = Resources.getResourceAsStream(resource);
//3
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
前两句代码把mybatis-config.xml文件读到内存,封装成字节输入流。
重点看第三行代码
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
这行代码分成两部分
1. new SqlSessionFactoryBuilder()
创建构造器建造者对象(建造者模式),专门用来建工厂
2. build(inputStream)
在这一行打断点debug进去,会看到调用链路如下:SqlSessionFactoryBuilder 的 build(inputStream) -> SqlSessionFactory 的 build(inputStream, environment, properties),SqlSessionFactory 的 build方法里的关键代码如下
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
}
创建一个XMLConfigBuilder对象, 这个对象调用parse()方法,parse()方法如下
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
这个方法在项目启动时只加载一次,所以进入else分支,以 <configuration>作为根节点解析mybatis-config.xml,包括如下内容:
- 解析全局配置文件(环境、数据源、连接池、驼峰、插件)
- 解析所有 Mapper.xml/ 注解SQL
- 把每一条 SQL(Mapper.xml里的每一个<select|insert|update|delete> 标签对应一条SQL) + 参数规则 + 结果映射规则,统一封装成 MappedStatement
- 全部存入全局唯一的大容器:Configuration
调试截图:



parser.parse()执行结果返回一个Configuration。Configuration 可以理解为一个全局仓库,后面所有代理、执行器、SQL 查找、映射规则,全部从这里拿数据。我们接着看下面代码的最后一行。
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
}
var5 = this.build(parser.parse());
得到Configuration之后传给this.build()方法,然后调用下面的方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
最终创建一个SqlSessionFactory(默认实现为DefaultSqlSessionFactory)。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
//......
}
SqlSessionFactory可以理解为“数据库操作总工厂”,持有全局配置 Configuration(单例),主要负责生产 SqlSession(数据库操作会话)。
2. 获取SqlSession
接着看测试类main方法的下一行代码
try (SqlSession session = factory.openSession()) {
上一步拿到SqlSessionFactory(默认实现为DefaultSqlSessionFactory)之后,开始从工厂获取SqlSession,它是一次与数据库的会话(连接),用来执行 SQL、提交事务、关闭连接。debug进去看,它的调用链如下
openSession() -> openSessionFromDataSource() -> createSqlSession() -> return new DefaultSqlSession(configuration, executor, autoCommit),其中openSessionFromDataSource()的代码如下

主要实现的功能是:1) 从配置中拿到数据源和事务工厂,2) 用事务工厂创建事务对象,3)用事务对象创建SQL 执行器,4)createSqlSession()封装成 SqlSession 并返回。
createSqlSession() 会走到 return new DefaultSqlSession(configuration, executor, autoCommit),DefaultSqlSession的构造函数如下
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
最终得到的DefaultSqlSession对象如下图所示。需要注意一点:这个session是执行数据库操作的门面,它并不直接干活,而是将具体任务交给内部的 Executor(执行器)去完成。同时SqlSession 会维护一级缓存(一级缓存的作用域是 SqlSession 级别,默认开启,在同一个 Session 中重复查询会命中缓存)。

3. 获取代理对象
下面接着debug,来到
UserMapper mapper = session.getMapper(UserMapper.class);
session.getMapper(UserMapper.class)这个方法会调用DefaultSqlSession的getMapper(),再到
Configuration.getMapper() -> MapperRegistry.getMapper() ->
MapperProxyFactory的newInstance(sqlSession) -> newInstance(mapperProxy)。
MapperProxyFactory的newInstance(sqlSession)的代码如下:

final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache)
这里创建 MapperProxy。由下图可知,MapperProxy 实现了 InvocationHandler 接口,是真正处理方法调用的类。MapperProxy 的 invoke() 方法会拦截 Mapper 接口的所有自定义业务方法。

MapperProxyFactory的newInstance(mapperProxy)的代码实现如下图:

这个方法内部用JDK动态代理生成 UserMapper接口的代理对象,如下图。

4. 执行SQL(从代理到Executor)
继续调试下一行代码
User user = mapper.selectUserById(1);
在该行打断点,同时在MapperProxy的invoke()打一个断点,执行这行代码的时候会进入MapperProxy的invoke(),代理会拦截所有自定义业务方法调用。

被调用的方法不是 Object 类中声明的方法,然后会执行
this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession),代码如下:

MapperMethodInvoker是MyBatis 封装的方法执行器,专门用来执行 Mapper 接口的方法(如 selectList、insert 等)。methodCache 是方法级缓存,key 是 Mapper 接口的 Method 对象,value 是 MapperMethodInvoker(内部封装了 MapperMethod),避免重复解析 SQL 类型和返回值类型,大幅提升性能。
MapUtil.computeIfAbsent(methodCache, method, m -> {
代码解析:MapUtil.computeIfAbsent缓存不存在则创建,存在直接返回,从 methodCache 中拿 method 对应的执行器,没有就创建一个放进去。
if (!m.isDefault()),因为isDefault() = Java8 接口默认方法,Mapper 接口里的方法都不是 default,所以进 if,然后创建真正执行 SQL 的核心对象MapperMethod。
return new PlainMethodInvoker(
new MapperMethod(
this.mapperInterface, // Mapper接口
method, // 当前方法 - selectUserById(1)
this.sqlSession.getConfiguration() // 全局配置
)
);
MapperMethod 封装了如下内容:SQL 语句的类型(SELECT/INSERT/UPDATE/DELETE);参数解析逻辑;结果映射逻辑;执行 SQL 的实际操作(MapperMethod 本身不执行 SQL,它是通过调用 SqlSession 的方法来完成的)等。
获取到MapperMethod(PlainMethodInvoker 是方法执行器包装类,内部持有真正负责 SQL 调度的 MapperMethod,统一提供 invoke 入口。)之后,会走下面的invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
然后进入MapperMethod的execute方法

execute () 方法根据 Mapper 接口方法(select/update/insert/delete),自动判断 SQL 类型,调用对应的 SqlSession 方法,最终把结果返回。它是代理调用 → SQL 执行之间的总调度器。
因为selectUserById的标签是select,所以来到了SELECT分支,下图展示了MapperMethod的各属性的值

至此,请求从 JDK 动态代理层,正式进入 MyBatis 核心 SQL 执行流程。
继续debug:
sqlSession.selectOne(statement, parameter) -> ... -> DefaultSqlSession.selectList(statement, parameter, rowBounds, handler),DefaultSqlSession的selectList()方法实现如下

该方法主要做了两件事,一是从configuration获取了MappedStatement,二是把具体的数据查询任务交给了Executor(执行器)去执行,最终的查询结果通过sqlSession返回到业务调用处。正如我们前面所说的,session是执行数据库操作的门面,它并不直接干活,而是将具体任务交给内部的 Executor(执行器)去完成。
继续看executor.query()

方法里的ms.getBoundSql() 根据传入参数,解析动态 SQL,生成带 ? 占位符的标准 SQL 语句,并封装参数映射关系,供 JDBC PreparedStatement 使用。
经过query(ms, parameterObject, rowBounds, resultHandler,key, boundSql)方法,然后调用BaseExecutor.query(ms, parameter, rowBounds, resultHandler, key, boundSql) -> queryFromDatabase(ms, parameter, rowBounds,resultHandler, key, boundSql)。
BaseExecutor.query(ms, parameter, rowBounds, resultHandler, key, boundSql)主要代码如下,先查一级缓存,缓存没命中再走数据库查询
List<E> list;
try {
//1. 默认查询(未自定义结果处理器)时,优先从一级缓存获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存命中,处理存储过程的 OUT 参数(常规查询可忽略)
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 2. 缓存未命中(或者自定义了结果处理器),走数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
我们的SQL查询缓存没有命中,走数据库查询,调用queryFromDatabase方法

然后进入SimpleExecutor的doQuery()

doQuery() 是 Executor 真正执行查询的核心方法,主要做 4 件事:
1. 创建 StatementHandler:MyBatis 核心处理器,统筹参数赋值(ParameterHandler)、SQL 执行、结果映射(ResultSetHandler)三大环节。
2. 预处理 SQL(prepareStatement()):获取数据库连接(connection)、创建 JDBC PreparedStatement、把参数设置到 SQL 里(调用 ParameterHandler.setParameters() 将参数设置到 PreparedStatement 中,这是 MyBatis 处理 SQL 参数赋值的核心执行入口)。
3. 执行查询(handler.query):调用 JDBC 执行 SQL 得到原始结果集,再由内部 ResultSetHandler 将结果集映射封装为 Java 对象。
4. 关闭资源:finally 块中关闭 Statement,避免资源泄漏;连接归连接池管理,此处不关闭连接。
上面代码里的return handler.query(stmt, resultHandler)最终调用PreparedStatementHandler的query()

可以看到在这里调用JDBC执行SQL,然后调用DefaultResultSetHandler 的 handleResultSets(Statement stmt) 解析返回的ResultSet,代码如下

可以看到,while循环将数据库返回的 N 个结果集(ResultSet)按照定义的 ResultMap 规则(如果xml配置的是resultType,MyBatis自动创建一个默认的 ResultMap)一行行映射成 Java 对象,最后返回 List如下图(handleResultSets方法里的第二个while循环用于处理存储过程等场景返回的多个结果集)。

最终闭环:封装完对象 → 返回给 Mapper 代理方法 → 业务代码拿到结果。
总结
上面的整个debug过程可以简化为下面的流程:
- 加载配置:解析 mybatis-config.xml 及所有 Mapper 文件,存入全局单例 Configuration。
- 构建工厂:通过 SqlSessionFactoryBuilder 创建 DefaultSqlSessionFactory。
- 打开会话:factory.openSession() 创建 SqlSession(持有 Executor)。
- 获取代理:通过 JDK 动态代理生成 Mapper 接口代理对象(MapperProxy)。
- 调用接口方法:被 MapperProxy.invoke() 拦截,交给 MapperMethod.execute() 路由(决定是增/删/改/查)。
- Executor 执行:SqlSession 将操作委托给 Executor(先查询缓存 → 若未命中,则走数据库)。
- SQL预编译 & 设参:Executor 创建 StatementHandler,完成SQL预编译,调用 parameterize() 触发 ParameterHandler.setParameters() 为 ? 赋值。
- JDBC 执行:PreparedStatementHandler.query() 调用 PreparedStatement.execute(),执行 SQL,获取ResultSet。
- 结果集映射:同一个 query() 方法中紧接着调用 ResultSetHandler.handleResultSets(),通过反射将 ResultSet 封装为 Java 实体对象(或集合)。
- 结果返回:层层返回,最终代理对象将结果返回给业务调用方。
MyBatis框架的核心类如下
| 核心类 | 职责 |
| Configuration | 全局配置中心,持有所有 MappedStatement及其他配置信息 |
| MapperProxy | 动态代理,拦截 Mapper 接口方法调用 |
| Executor |
SQL 执行器,管理缓存、事务,调度 StatementHandler 完成数据库读写 |
| StatementHandler |
SQL 处理器,协调 ParameterHandler 和 ResultSetHandler,完成 SQL 执行的全流程 |
| ParameterHandler | 封装 SQL 参数,为 PreparedStatement 完成占位符参数赋值 |
| ResultSetHandler | 按照映射规则将 ResultSet 自动封装为 Java 对象 |
更多推荐




所有评论(0)