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

MyBatis 作为 Java 持久层框架的 "顶流",其 "接口无实现却能执行 SQL" 的设计,是 "动态代理 + 配置驱动" 的经典实践。但多数开发者仅停留在 "会用" 层面,对其底层执行逻辑一知半解。本文从源码跟踪、原理推导、实战验证 三个维度,完整拆解 MyBatis 从Mapper接口调用到数据库返回结果的全流程,同时解析流程中的核心设计思想与性能优化点。

一、前置知识:MyBatis 的核心设计思想

在分析流程前,先明确 MyBatis 的两个核心设计思想(这是理解流程的基础):

  1. 配置驱动:将 SQL、参数映射、结果映射等逻辑从 Java 代码中剥离,通过 XML / 注解配置管理,降低业务代码与持久层的耦合;
  2. 动态代理 :通过 JDK 动态代理为Mapper接口生成代理类,拦截接口方法调用,将请求转发给 MyBatis 的核心执行组件。

二、核心组件深度解析:执行流程的 "执行者"

MyBatis 的执行流程由多个核心组件协作完成,每个组件的职责与设计细节如下:

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

三、执行全流程:从Mapper接口到数据库返回(源码 + 实战)

以 "根据用户 ID 查询用户信息" 为例,完整流程分为初始化阶段执行阶段,以下是每个步骤的源码跟踪与实战验证。

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

MyBatis 启动时,会完成 "配置解析→核心对象初始化" 的工作,这是后续执行的基础。

步骤 1:解析mybatis-config.xml,构建Configuration

MyBatis 通过XmlConfigBuilder解析全局配置文件,将数据源、映射器等信息存入Configuration

源码跟踪

java 复制代码
// 读取配置文件输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 创建XmlConfigBuilder,解析配置
XmlConfigBuilder parser = new XmlConfigBuilder(inputStream, null, null);
// 解析后生成Configuration
Configuration configuration = parser.parse();

parser.parse()的核心逻辑是解析<environments>(数据源、事务)、<mappers>(映射器)等标签,将信息存入Configuration

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

XmlConfigBuilder会调用XmlMapperBuilder解析Mapper.xml,将每个 SQL 标签封装为MappedStatement并注册到Configuration

实战验证 :假设UserMapper.xml中有如下 SQL:

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解析该标签时,会生成一个MappedStatement,其idcom.example.mapper.UserMapper.getById,并将其添加到ConfigurationmappedStatements(一个Map)中。

源码片段

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

通过SqlSessionFactoryBuilderConfiguration封装为SqlSessionFactory(单例对象)。

实战代码

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

SqlSessionFactory是 MyBatis 的 "入口",后续所有数据库操作都通过它创建的SqlSession完成。

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

当业务代码调用userMapper.getById(1L)时,MyBatis 的执行流程正式启动,以下是每个步骤的细节:

步骤 1:创建SqlSession

SqlSessionFactory中获取SqlSession,它代表一次数据库连接(非线程安全,需在方法内创建并关闭)。

实战代码

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

源码逻辑

SqlSessionFactory.openSession()会创建DefaultSqlSession,同时初始化ExecutorTransaction

java 复制代码
// DefaultSqlSessionFactory的openSession方法
@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接口的代理对象,这是 MyBatis "接口无实现却能执行 SQL" 的核心。

实战代码

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

源码跟踪

SqlSession.getMapper()会调用MapperRegistry.getMapper(),通过 JDK 动态代理生成代理类:

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

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

最终生成的代理对象是MapperProxy(实现了InvocationHandler),它会拦截所有Mapper接口的方法调用。

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

当调用userMapper.getById(1L)时,代理对象的MapperProxy.invoke()方法会被触发,将请求转发给SqlSession

源码片段

java 复制代码
// MapperProxy的invoke方法
@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方法
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据SQL类型(SELECT/INSERT/UPDATE/DELETE)调用SqlSession的对应方法
    switch (command.getType()) {
        case SELECT:
            // 处理查询,调用sqlSession.selectOne()
            result = sqlSession.selectOne(command.getName(), param);
            break;
        // 省略其他类型的处理...
    }
    return result;
}
步骤 4:匹配MappedStatement,执行 SQL

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

源码跟踪

java 复制代码
// DefaultSqlSession的selectOne方法
@Override
public <T> T selectOne(String statement, Object parameter) {
    // 调用selectList,取第一个结果
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
        return list.get(0);
    }
    // 省略异常处理...
}

// DefaultSqlSession的selectList方法
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    // 获取MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 交给Executor执行
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
步骤 5:Executor调度执行,完成参数与结果映射

Executor.query()会调用StatementHandler处理 SQL 预编译,ParameterHandler处理参数绑定,ResultSetHandler处理结果映射。核心流程

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

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

实战代码

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

四、流程中的设计亮点与性能优化

1.Executor的缓存设计CachingExecutor会缓存查询结果(二级缓存),避免重复查询数据库;可通过@CacheNamespace开启。

2.BatchExecutor的批处理优化 :对于批量插入 / 更新操作,使用ExecutorType.BATCH创建SqlSession,可减少数据库连接次数,提升性能:

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

3.参数映射的预编译安全ParameterHandler#{id}的处理是预编译(PreparedStatement),避免 SQL 注入;而${id}是字符串拼接,需谨慎使用。

五、实战验证:跟踪执行流程

通过 IDEA 的断点调试,可清晰看到流程的执行顺序:

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

六、总结

MyBatis 的执行流程是 "配置解析→代理生成→请求转发→组件协作→结果返回 " 的完整链路,其核心是通过 "动态代理" 屏蔽了Mapper接口的实现细节,通过 "配置驱动" 分离了 SQL 与业务代码。理解这一流程,不仅能解决日常开发中的持久层问题,更能深入体会 MyBatis 的设计思想,为自定义持久层框架或性能优化提供思路。

相关推荐
2301_818732062 小时前
前端一直获取不到后端的值,和数据库字段设置有关 Oracle
前端·数据库·sql·oracle
BXCQ_xuan2 小时前
解决飞牛nas更新后挂载硬盘提示“数据库读写失败”
数据库·飞牛nas
栗子叶2 小时前
阅读MySQL实战45讲专栏总结
数据库·mysql·innodb·主从同步·数据库原理
一只鹿鹿鹿2 小时前
springboot集成工作流教程(全面集成以及源码)
大数据·运维·数据库·人工智能·web安全
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-数据库设计关联关系设计
服务器·网络·数据库
一直都在5722 小时前
Spring3整合MyBatis实现分页查询和搜索
mybatis
李慕婉学姐2 小时前
Springboot七彩花都线上鲜花订购平台rzb8b4z2(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
码农阿豪2 小时前
时序数据爆发增长,企业如何破解存储与分析困局?
数据库·mysql·金仓
定偶2 小时前
用MySQL玩转数据可视化的技术
数据库·mysql·信息可视化