MyBatis 执行流程源码级深度解析:从 Mapper 接口到 SQL 执行的全链路逻辑

一、核心设计思想(理解流程的基础)

MyBatis 作为 Java 持久层框架,核心设计围绕两大思想展开:

  1. 配置驱动:将 SQL 语句、参数映射、结果映射等逻辑从 Java 业务代码中剥离,通过 XML 或注解配置管理,降低业务层与持久层的耦合度,便于 SQL 维护和调整。
  2. 动态代理:利用 JDK 动态代理为 Mapper 接口生成代理类,拦截接口方法调用,将数据库操作请求转发给 MyBatis 核心执行组件,实现 "接口无实现却能执行 SQL"。

二、核心组件详解(执行流程的核心执行者)

组件名 核心职责 设计细节
Configuration 全局配置中心,存储所有 MyBatis 配置信息 单例对象;包含 MappedStatement、数据源、插件、类型处理器等,是 MyBatis 的 "大脑"
SqlSessionFactory 创建 SqlSession 的工厂类 基于 Configuration 构建,单例存在,项目启动时初始化一次
SqlSession 数据库会话,封装执行器与事务管理 非线程安全,代表一次数据库连接;业务代码通过它操作数据库
Executor SQL 执行器,负责 SQL 调度、缓存管理、批处理 三种实现:1. SimpleExecutor(默认,简单执行)2. BatchExecutor(批处理)3. CachingExecutor(带二级缓存)
MappedStatement 封装单个 SQL 的所有信息(SQL 语句、参数 / 结果映射、执行方式等) 对应 Mapper.xml 中的一个 SQL 标签(如 <select>);ID 为 "接口全限定名 + 方法名"
ParameterHandler 解析接口参数,绑定到 SQL 占位符 默认实现 DefaultParameterHandler;支持 #{}(预编译)和 ${}(字符串拼接)
ResultSetHandler 将数据库返回的 ResultSet 映射为 Java 对象 默认实现 DefaultResultSetHandler;支持简单对象、集合、嵌套结果映射
StatementHandler 操作 JDBC 的 Statement/PreparedStatement 执行 SQL MyBatis 与 JDBC 的 "桥梁";负责 SQL 预编译、参数设置、执行触发

三、MyBatis 完整执行流程(初始化 + 执行阶段)

以 "根据用户 ID 查询用户信息(userMapper.getById(1L))" 为例,拆解全流程:

(一)初始化阶段:加载配置,构建核心对象

项目启动时完成,为后续执行打下基础。

步骤 1:解析全局配置文件,构建 Configuration

通过 XmlConfigBuilder 解析 mybatis-config.xml,将数据源、事务、映射器等配置存入 Configuration。

复制代码
// 核心代码
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
XmlConfigBuilder parser = new XmlConfigBuilder(inputStream, null, null);
Configuration configuration = parser.parse(); // 解析配置并生成Configuration

核心逻辑:解析 <environments>(数据源 / 事务)、<mappers>(映射器)等标签,将配置信息统一存入 Configuration 单例。

步骤 2:解析 Mapper.xml,注册 MappedStatement

XmlConfigBuilder 调用 XmlMapperBuilder 解析 Mapper.xml,将每个 SQL 标签封装为 MappedStatement 并注册到 Configuration。实战示例(UserMapper.xml):

xml

复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <select id="getById" parameterType="Long" resultType="com.example.entity.User">
        SELECT id, name, age FROM user WHERE id = #{id}
    </select>
</mapper>

源码核心逻辑

复制代码
// XmlMapperBuilder解析<select>标签
private void processSelectElement(XNode node) {
    String id = node.getStringAttribute("id"); // 获取SQL标签id
    String parameterType = node.getStringAttribute("parameterType");
    String resultType = node.getStringAttribute("resultType");
    // 构建MappedStatement
    MappedStatement ms = buildMappedStatement(id, parameterType, resultType, node);
    // 注册到Configuration(key:com.example.mapper.UserMapper.getById)
    configuration.addMappedStatement(ms);
}
步骤 3:构建 SqlSessionFactory

通过 SqlSessionFactoryBuilder 将 Configuration 封装为 SqlSessionFactory(单例),作为 MyBatis 操作数据库的入口。

复制代码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

(二)执行阶段:从 Mapper 接口调用到结果返回

业务代码调用 Mapper 接口方法时触发,核心是 "动态代理 + 组件协作"。

步骤 1:创建 SqlSession

从 SqlSessionFactory 获取 SqlSession,代表一次数据库连接(非线程安全,建议方法内创建)。

复制代码
// openSession(true):自动提交事务;默认false(手动提交)
SqlSession sqlSession = sqlSessionFactory.openSession(true);

源码逻辑(DefaultSqlSessionFactory):

复制代码
@Override
public SqlSession openSession(boolean autoCommit) {
    // 获取环境配置(数据源、事务管理器)
    Environment environment = configuration.getEnvironment();
    // 创建事务
    Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), autoCommit);
    // 创建Executor(默认SimpleExecutor)
    Executor executor = configuration.newExecutor(tx);
    // 创建DefaultSqlSession
    return new DefaultSqlSession(configuration, executor, autoCommit);
}
步骤 2:生成 Mapper 动态代理对象

通过 SqlSession.getMapper() 生成 Mapper 接口的代理对象,这是 "接口无实现却能执行 SQL" 的核心。

复制代码
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

源码跟踪

复制代码
// MapperRegistry.getMapper():获取代理工厂并生成代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    return mapperProxyFactory.newInstance(sqlSession);
}

// MapperProxyFactory.newInstance():生成JDK动态代理
public T newInstance(SqlSession sqlSession) {
    // 创建InvocationHandler(代理逻辑实现)
    MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 生成代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
                                      new Class[]{mapperInterface}, 
                                      mapperProxy);
}

最终生成的代理对象由 MapperProxy(实现 InvocationHandler)接管,拦截所有接口方法调用。

步骤 3:拦截接口方法调用,转发请求

调用 userMapper.getById(1L) 时,MapperProxy.invoke() 触发,将请求转发给 SqlSession。源码核心

复制代码
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 过滤Object类方法(如toString())
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    }
    // 转发请求到SqlSession
    return cachedMapperMethod(method).execute(sqlSession, args);
}

// MapperMethod.execute():根据SQL类型调用SqlSession对应方法
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case SELECT:
            result = sqlSession.selectOne(command.getName(), param); // 查询单条
            break;
        // 省略INSERT/UPDATE/DELETE处理逻辑
    }
    return result;
}
步骤 4:匹配 MappedStatement,交给 Executor 执行

SqlSession.selectOne() 从 Configuration 中找到对应 MappedStatement,交给 Executor 执行。

复制代码
// DefaultSqlSession.selectOne()
@Override
public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);
    return list.size() == 1 ? list.get(0) : null;
}

// DefaultSqlSession.selectList()
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    // 获取MappedStatement(key:接口全限定名+方法名)
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 交给Executor执行
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
步骤 5:Executor 调度组件,完成 SQL 执行与结果映射

Executor 协调 ParameterHandlerStatementHandlerResultSetHandler 完成核心操作:

  1. 参数绑定 :ParameterHandler 将入参 1L 绑定到 SQL 的 #{id} 占位符,生成 PreparedStatement;
  2. 执行 SQL :StatementHandler 调用 PreparedStatement.execute(),发送 SQL 到数据库;
  3. 结果映射:ResultSetHandler 将数据库返回的 ResultSet 转换为 User 对象。
步骤 6:关闭 SqlSession,释放资源

SqlSession 非线程安全,执行完成后必须关闭,释放数据库连接等资源(建议在 finally 块中执行)。

复制代码
finally {
    if (sqlSession != null) {
        sqlSession.close();
    }
}

四、设计亮点与性能优化

1. Executor 缓存优化

CachingExecutor 支持二级缓存,缓存查询结果避免重复访问数据库;通过 @CacheNamespace 注解开启 Mapper 级别的二级缓存。

2. 批处理优化(BatchExecutor)

批量插入 / 更新时,使用 ExecutorType.BATCH 创建 SqlSession,减少数据库连接次数,提升性能:

复制代码
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

3. 参数映射安全优化

  • #{}:通过 PreparedStatement 预编译处理,避免 SQL 注入,推荐使用;
  • ${}:直接字符串拼接,存在 SQL 注入风险,仅适用于固定值(如表名、排序字段)。

五、实战验证:断点调试跟踪流程

通过 IDEA 断点调试,验证执行顺序:

  1. MapperProxy.invoke() 打断点,观察接口方法被拦截;
  2. DefaultSqlSession.selectOne() 打断点,观察 MappedStatement 匹配过程;
  3. Executor.query() 打断点,观察 SQL 执行与结果映射。

六、完整实战代码示例

复制代码
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisDemo {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            // 1. 加载配置文件,构建SqlSessionFactory
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            // 2. 创建SqlSession
            sqlSession = sqlSessionFactory.openSession(true); // 自动提交事务

            // 3. 获取Mapper代理对象
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 4. 调用接口方法(触发动态代理与SQL执行)
            User user = userMapper.getById(1L);
            System.out.println("查询结果:" + user);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5. 关闭SqlSession
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

// UserMapper接口
public interface UserMapper {
    User getById(Long id);
}

// User实体类
class User {
    private Long id;
    private String name;
    private Integer age;

    // 省略getter/setter/toString
}

总结

  1. 核心流程:MyBatis 执行链路为 "配置解析→代理生成→请求转发→组件协作→结果返回",动态代理屏蔽了 Mapper 接口实现细节,配置驱动分离了 SQL 与业务代码;
  2. 核心组件:Configuration 是配置中心,Executor 是执行调度核心,MappedStatement 封装 SQL 信息,三者是流程的核心支撑;
  3. 性能优化:二级缓存、批处理 Executor、预编译参数绑定是提升 MyBatis 性能的关键手段。
相关推荐
山峰哥2 小时前
SQL优化实战:从索引策略到执行计划的极致突破
数据库·sql·性能优化·编辑器·深度优先
弹简特2 小时前
【JavaEE18-后端部分】 MyBatis 入门第二篇:使用注解完成增删改查(含有参数传递底层原理)
spring boot·mybatis
总要冲动一次2 小时前
离线安装 percona-xtrabackup-24
linux·数据库·mysql·centos
lcrml3 小时前
nacos2.3.0 接入pgsql或其他数据库
数据库
小王不爱笑1323 小时前
SpringBoot 自动装配深度解析:从底层原理到自定义 starter 实战(含源码断点调试)
java·spring boot·mybatis
阿达_优阅达3 小时前
告别手工对账:xSuite 如何帮助 SAP 企业实现财务全流程自动化?
服务器·数据库·人工智能·自动化·sap·企业数字化转型·xsuite
IvorySQL3 小时前
IvorySQL v5 发布后,我们想听听大家的使用体验
数据库·postgresql·开源
Maverick063 小时前
01- Oracle核心架构:理解数据库如何运转
数据库·oracle·架构
TDengine (老段)3 小时前
TDengine IDMP 组态面板 —— 画布
大数据·数据库·物联网·时序数据库·tdengine·涛思数据