谈谈 Mybatis 的工作原理?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  1. 全局视野:面试官不仅仅是想知道你 "会用" MyBatis,更是想考察你能否从整体架构层面说清楚 MyBatis 从启动到执行 SQL 的完整链路,而不是零散地背几个概念。

  2. 核心组件认知:看你能否准确说出 MyBatis 的核心组件(SqlSessionFactorySqlSessionExecutorMappedStatement 等)及其职责。

  3. 源码级深度:高级面试中,面试官会期望你能深入到源码层面,说清 Mapper 代理、SQL 解析、参数设置、结果映射等关键环节的底层机制。

核心答案

MyBatis 的核心工作原理可以用一句话概括:读取配置 → 构建 SqlSessionFactory → 创建 SqlSession → 获取 Mapper 代理 → 执行 SQL → 结果映射

整个流程分为 7 个阶段,涵盖了从配置加载到返回结果的完整链路。下面逐一展开。

深度解析

一、核心组件总览

先认识 MyBatis 的核心组件,它们各司其职:

组件职责生命周期
SqlSessionFactoryBuilder读取配置,构建 SqlSessionFactory方法级别,用完即弃
SqlSessionFactory创建 SqlSession 的工厂应用级别,全局单例
SqlSession执行 SQL 的会话对象,面向开发者的 API请求 / 方法级别,用完必须关闭
ExecutorSQL 执行器,负责查询、更新、事务、缓存SqlSession 级别
MappedStatement一条 SQL 的所有元数据(SQL 文本、参数映射、结果映射等)应用级别,启动时创建
StatementHandlerJDBC Statement 操作封装SQL 执行级别
ParameterHandler参数设置(Java → JDBC)SQL 执行级别
ResultSetHandler结果映射(JDBC → Java)SQL 执行级别
TypeHandlerJava 类型和 JDBC 类型的转换器应用级别

二、阶段一:读取配置

MyBatis 启动时需要读取两类配置文件:

// 加载配置文件
InputStream config = Resources.getResourceAsStream("mybatis-config.xml");

// mybatis-config.xml 中引入 Mapper XML
// <mappers>
//     <mapper resource="mapper/UserMapper.xml"/>
// </mappers>

mybatis-config.xml 包含:数据源配置、事务管理器、别名、插件、缓存设置、Mapper 注册等。

Mapper XML 包含:具体的 SQL 语句、参数映射、结果映射等。

三、阶段二:构建 SqlSessionFactory

这是 MyBatis 初始化的核心步骤,SqlSessionFactoryBuilder 通过 XMLConfigBuilder 解析配置文件,最终构建出 Configuration 对象:

Configuration 是 MyBatis 的 "大脑",几乎所有全局配置和 SQL 映射信息都存在这个对象中。其中最关键的是 MappedStatement 集合——Mapper XML 中的每一个 <select><insert><update><delete> 标签都会被解析为一个 MappedStatement 对象,以 namespace + "." + id 为 key 存储。

四、阶段三:创建 SqlSession

SqlSession session = sqlSessionFactory.openSession();

openSession() 做了以下事情:

  • 从数据源获取一个 JDBC Connection
  • 创建 Executor(根据配置选择 SimpleExecutor / ReuseExecutor / BatchExecutor
  • 如果有插件,对 Executor 做代理包装
  • 组装成 DefaultSqlSession 返回

SqlSession 是开发者操作 MyBatis 的主要入口,它提供了 selectOne()selectList()insert()update()delete() 等方法。但在实际开发中,我们很少直接使用这些方法,而是通过 Mapper 代理来调用。

五、阶段四:获取 Mapper 代理

UserMapper mapper = session.getMapper(UserMapper.class);

这一步是 MyBatis 的精妙之处——你只定义了接口,MyBatis 帮你生成实现类

Mapper 代理的核心是 MapperProxy(实现了 InvocationHandler)。当调用接口方法时,invoke() 方法会:

  • 根据接口全限定名 + 方法名,从 Configuration 中找到对应的 MappedStatement
  • 判断方法类型(SELECT / INSERT / UPDATE / DELETE)
  • 委托给 SqlSession 的对应方法执行

这就是为什么我们只写接口不写实现类,MyBatis 就能帮我们执行 SQL。

六、阶段五:执行 SQL

这是最核心的执行阶段,涉及多个组件的协作:

SQL 执行涉及 3 个核心处理器的协作:

  • Executor(执行器):总调度,负责缓存判断、事务管理。先查缓存,缓存未命中才查数据库。
  • StatementHandler(语句处理器):负责创建 PreparedStatement、设置参数、执行 SQL。
  • ParameterHandler(参数处理器):通过 TypeHandler 将 Java 参数转换为 JDBC 参数,设置到 PreparedStatement 中。

七、阶段六:结果映射

SQL 执行完成后,ResultSetHandler 接手:

  • ResultSetHandler 遍历 ResultSet,根据 resultMapresultType 配置,将每一行数据映射为一个 Java 对象
  • 映射过程中,TypeHandler 负责类型转换(如 BIGINTLongVARCHARString
  • 通过反射调用 setter 方法完成属性赋值
  • 结果写入一级缓存,SqlSession 关闭时写入二级缓存

八、Spring 整合后的变化

生产环境中 MyBatis 通常和 Spring 整合使用,主要变化在于:

独立 MyBatisSpring 整合后
手动创建 SqlSessionFactorySqlSessionFactoryBean 自动创建
手动获取 SqlSessionSqlSessionTemplate 自动管理
手动获取 Mapper@MapperScan 自动扫描注册
手动管理事务@Transactional 声明式事务
手动关闭 SqlSessionSpring 自动关闭
// Spring Boot 中的典型用法
@Mapper    // 标记为 Mapper 接口
public interface UserMapper {
    @Select("SELECT * FROM t_user WHERE id = #{id}")
    User selectById(Long id);
}

// 直接注入使用,无需关心 SqlSession
@Autowired
private UserMapper userMapper;

九、常见误区

  1. 误区一:"MyBatis 启动时就连接数据库执行 SQL"
    • 不是。启动时只解析 XML 配置和 SQL 语句,构建 Configuration 对象。SQL 执行是在调用 Mapper 方法时才发生的。
  2. 误区二:"SqlSession 是线程安全的"
    • 不是。SqlSession 不是线程安全的,每个线程应该使用自己的 SqlSession。Spring 整合后通过 SqlSessionTemplate 解决了这个问题(内部为每个线程创建独立的 SqlSession)。
  3. 误区三:"Mapper 接口的实现类是 MyBatis 帮你生成的"
    • 不是生成实现类,而是通过 JDK 动态代理 在运行时生成代理对象。没有 .class 文件生成,代理对象是在内存中动态创建的。

面试高频追问

  1. 追问一:MyBatis 中 Executor 有几种类型?
    • 三种:SimpleExecutor(默认,每次都创建新 Statement)、ReuseExecutor(复用 Statement)、BatchExecutor(批量执行)。在 mybatis-config.xml 中通过 defaultExecutorType 配置。
  2. 追问二:为什么 Mapper 接口不需要实现类?
    • 因为 MyBatis 使用 JDK 动态代理,通过 MapperProxy 在运行时拦截接口方法调用,根据方法名找到对应的 MappedStatement,然后委托给 SqlSession 执行。接口方法名 + 全限定名就是 SQL 的唯一标识。
  3. 追问三SqlSessionFactory 为什么是单例的?
    • Configuration 对象很重(包含所有 SQL 映射、缓存、插件等),创建成本高。SqlSessionFactory 是线程安全的,全局只需要一个实例。Spring 整合后由 SqlSessionFactoryBean 保证单例。

常见面试变体

  • 变体一:"说说 MyBatis 的架构设计?"
  • 变体二:"MyBatis 从加载配置到执行 SQL 的完整流程?"
  • 变体三:"MyBatis 的 Mapper 接口是怎么和 XML 关联的?"

记忆口诀

读配置建工厂,开 Session 拿代理;代理调方法找 Statement,Executor 调度 StatementHandler 执行;ParameterHandler 设参数,ResultSetHandler 映射结果,全程 Configuration 做大脑。

总结

MyBatis 的工作原理可以概括为:读取 XML 配置 → 构建 Configuration → 创建 SqlSessionFactory → 打开 SqlSession → JDK 动态代理获取 Mapper → Executor 调度 → StatementHandler 执行 SQL → ResultSetHandler 完成结果映射。其中 Configuration 是核心大脑,MappedStatement 是每条 SQL 的元数据封装,Mapper 代理让开发者只需定义接口就能执行 SQL。