谈谈 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/
面试考察点
-
全局视野:面试官不仅仅是想知道你 "会用" MyBatis,更是想考察你能否从整体架构层面说清楚 MyBatis 从启动到执行 SQL 的完整链路,而不是零散地背几个概念。
-
核心组件认知:看你能否准确说出 MyBatis 的核心组件(
SqlSessionFactory、SqlSession、Executor、MappedStatement等)及其职责。 -
源码级深度:高级面试中,面试官会期望你能深入到源码层面,说清 Mapper 代理、SQL 解析、参数设置、结果映射等关键环节的底层机制。
核心答案
MyBatis 的核心工作原理可以用一句话概括:读取配置 → 构建 SqlSessionFactory → 创建 SqlSession → 获取 Mapper 代理 → 执行 SQL → 结果映射。
整个流程分为 7 个阶段,涵盖了从配置加载到返回结果的完整链路。下面逐一展开。
深度解析
一、核心组件总览
先认识 MyBatis 的核心组件,它们各司其职:
| 组件 | 职责 | 生命周期 |
|---|---|---|
SqlSessionFactoryBuilder | 读取配置,构建 SqlSessionFactory | 方法级别,用完即弃 |
SqlSessionFactory | 创建 SqlSession 的工厂 | 应用级别,全局单例 |
SqlSession | 执行 SQL 的会话对象,面向开发者的 API | 请求 / 方法级别,用完必须关闭 |
Executor | SQL 执行器,负责查询、更新、事务、缓存 | SqlSession 级别 |
MappedStatement | 一条 SQL 的所有元数据(SQL 文本、参数映射、结果映射等) | 应用级别,启动时创建 |
StatementHandler | JDBC Statement 操作封装 | SQL 执行级别 |
ParameterHandler | 参数设置(Java → JDBC) | SQL 执行级别 |
ResultSetHandler | 结果映射(JDBC → Java) | SQL 执行级别 |
TypeHandler | Java 类型和 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,根据resultMap或resultType配置,将每一行数据映射为一个 Java 对象- 映射过程中,
TypeHandler负责类型转换(如BIGINT→Long,VARCHAR→String) - 通过反射调用 setter 方法完成属性赋值
- 结果写入一级缓存,
SqlSession关闭时写入二级缓存
八、Spring 整合后的变化
生产环境中 MyBatis 通常和 Spring 整合使用,主要变化在于:
| 独立 MyBatis | Spring 整合后 |
|---|---|
手动创建 SqlSessionFactory | 由 SqlSessionFactoryBean 自动创建 |
手动获取 SqlSession | SqlSessionTemplate 自动管理 |
| 手动获取 Mapper | @MapperScan 自动扫描注册 |
| 手动管理事务 | @Transactional 声明式事务 |
手动关闭 SqlSession | Spring 自动关闭 |
// Spring Boot 中的典型用法
@Mapper // 标记为 Mapper 接口
public interface UserMapper {
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
}
// 直接注入使用,无需关心 SqlSession
@Autowired
private UserMapper userMapper;
九、常见误区
- 误区一:"MyBatis 启动时就连接数据库执行 SQL"
- 不是。启动时只解析 XML 配置和 SQL 语句,构建
Configuration对象。SQL 执行是在调用 Mapper 方法时才发生的。
- 不是。启动时只解析 XML 配置和 SQL 语句,构建
- 误区二:"
SqlSession是线程安全的"- 不是。
SqlSession不是线程安全的,每个线程应该使用自己的SqlSession。Spring 整合后通过SqlSessionTemplate解决了这个问题(内部为每个线程创建独立的SqlSession)。
- 不是。
- 误区三:"Mapper 接口的实现类是 MyBatis 帮你生成的"
- 不是生成实现类,而是通过 JDK 动态代理 在运行时生成代理对象。没有
.class文件生成,代理对象是在内存中动态创建的。
- 不是生成实现类,而是通过 JDK 动态代理 在运行时生成代理对象。没有
面试高频追问
- 追问一:MyBatis 中
Executor有几种类型?- 三种:
SimpleExecutor(默认,每次都创建新Statement)、ReuseExecutor(复用Statement)、BatchExecutor(批量执行)。在mybatis-config.xml中通过defaultExecutorType配置。
- 三种:
- 追问二:为什么 Mapper 接口不需要实现类?
- 因为 MyBatis 使用 JDK 动态代理,通过
MapperProxy在运行时拦截接口方法调用,根据方法名找到对应的MappedStatement,然后委托给SqlSession执行。接口方法名 + 全限定名就是 SQL 的唯一标识。
- 因为 MyBatis 使用 JDK 动态代理,通过
- 追问三:
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。