MyBatis 是目前企业级 Java 项目使用最广泛的数据访问层框架。
相比 JPA/Hibernate,它的特点非常明显:
- SQL 自己写,可控性强
- 性能高、避免 ORM 过度抽象
- 对 DBA 友好
- 数据库主导项目的团队更倾向 MyBatis
更重要的是:
MyBatis 与 Spring Boot 的整合非常紧密,只需要极少量配置即可完成持久层搭建。
这篇文章就从"Spring Boot 整合 MyBatis"开始,讲到 MyBatis 的底层执行原理。
一、MyBatis 与 Spring Boot 是如何整合的?
在 Spring Boot 中使用 MyBatis,只需要加入一个依赖:
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
这个 Starter 会自动完成几个关键任务:
- 自动创建 SqlSessionFactory
- 自动创建 SqlSessionTemplate
- 自动扫描 @Mapper 注解的接口
- 为这些 Mapper 接口创建代理对象
- 自动加载 mapper XML 文件
换句话说:
MyBatis 的整合本质上是自动装配 SqlSessionFactory + 扫描 Mapper + 生成动态代理。
我们逐个拆开来看。
二、Mapper 为什么不用写实现类?(动态代理)
写过 MyBatis 的人都知道,你只定义一个接口:
java
@Mapper
public interface UserMapper {
User selectById(Long id);
}
但你不写实现类,它却能正常调用:
java
User user = userMapper.selectById(1L);
那么问题来了:
为什么一个没有实现类的接口可以被调用?
答案是:
因为 Spring Boot 帮你创建了一个 Mapper 的 JDK 动态代理对象。
核心逻辑在于:
- 启动时,MapperScannerRegistrar 扫描所有 @Mapper 接口
- 为每个接口构建一个 BeanDefinition,最终 Bean 是 MapperFactoryBean
- MapperFactoryBean.getObject() 返回的是一个 JDK 动态代理
- 代理对象拦截方法调用
- 根据方法名找到对应的 XML(或注解)里的 SQL
- 调用 SqlSession 执行 SQL
- 把结果映射成对象返回
本质代码逻辑类似:
java
return Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
new MapperProxy(sqlSession, mapperInterface, methodCache)
);
你调用:
java
userMapper.selectById(1)
底层发生的是:
- JDK 代理的 invoke() 被触发
- 找到 XML 中
<select id="selectById"> - 封装参数,执行 SQL
- 映射结果并返回
这就是 MyBatis 的魔法:"接口即 Mapper"。
三、MyBatis 的执行流程(非常重要)
在面试中如果问到 MyBatis 原理,你只要完整说出下面这段流程,面试官基本会认可你"真的懂"。
一个 MyBatis 查询的大致流程如下:
1. 创建 SqlSession
Spring Boot 会创建一个 SqlSessionTemplate,这个模板对象是线程安全的,内部持有一个 SqlSession。
2. 获取 Mapper 的代理对象
通过 JDK 动态代理生成 MapperProxy。
3. 代理拦截方法调用
MapperProxy.invoke() 被触发,然后从配置中找到对应的 MappedStatement。
每个 SQL 对应一个唯一 ID,例如:
namespace + id
比如:
UserMapper.xml 中的 <select id="selectById">
会在启动时注册为:
com.xxx.UserMapper.selectById
4. 封装参数
参数采用 ParameterHandler 封装。
5. 调用 Executor 执行 SQL
MyBatis 有三种 Executor:
- SimpleExecutor
- BatchExecutor
- ReuseExecutor
默认使用 SimpleExecutor。
6. 调用 StatementHandler 执行 SQL
生成 PreparedStatement
绑定参数
执行 SQL
7. 调用 ResultSetHandler 处理结果集
把查询结果转换为 Java 对象:
- 字段映射
- 下划线转驼峰
- 关联查询
- 类型转换
- 自动处理 List、Map 等结构
最终返回结果。
总结一句话:
MyBatis 的执行流程就是:
Mapper 代理 → 找 SQL → Executor → StatementHandler → JDBC → ResultSetHandler。
这是你在面试中必须能讲顺的一段。
四、MyBatis 的动态 SQL 原理:SQLSource + Ognl
MyBatis 的动态 SQL 功能非常强,比如:
xml
<select id="selectUser">
SELECT * FROM user
<where>
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
面试官可能问:
MyBatis 的动态 SQL 是怎么实现的?
底层逻辑如下:
- MyBatis 启动时解析 XML,把动态标签解析成一个"节点树"(SqlNode)
- 执行 SQL 时,根据当前传入参数,动态拼接 SQL
- 使用 Ognl 表达式计算条件(如 test="name != null")
- 最终生成 BoundSql,交给 JDBC 执行
也就是说:
MyBatis 的动态 SQL 是在运行期,根据参数动态生成 SQL,而不是在编译期生成。
这使得 SQL 更灵活,也更可读。
五、MyBatis 缓存机制(一级缓存 & 二级缓存)
缓存是 MyBatis 的另一个核心点。
1. 一级缓存(本地缓存)
默认开启,SqlSession 级别。
如果在同一个 SqlSession 中重复执行同样的查询,MyBatis 会直接从缓存返回结果:
java
User u1 = mapper.selectById(1);
User u2 = mapper.selectById(1);
两次查询只会打一次数据库。
特点:
- 默认开启
- 生命周期与 SqlSession 相同
- 任何更新操作都会清空一级缓存
- Spring 项目中,每个 HTTP 请求通常对应一个 SqlSession
2. 二级缓存(Mapper namespace 级别)
默认关闭,需要手动开启。
它的特点:
- 基于 Mapper namespace
- 多个 SqlSession 可以共享缓存
- 必须开启
<cache />标签 - 查询结果必须可序列化
- 注意与数据库一致性问题
简单示例:
xml
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/>
使用二级缓存必须谨慎,因为:
- 多线程更新可能导致脏数据
- 企业项目一般开启 Redis 等外部缓存,因此 MyBatis 二级缓存通常不使用
六、MyBatis 插件机制(Interceptor)
插件机制是 MyBatis 的高级扩展能力,可以拦截四大对象:
- Executor(执行器)
- ParameterHandler(参数处理器)
- StatementHandler(语句处理器)
- ResultSetHandler(结果集处理器)
你可以实现:
java
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {})
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置逻辑
Object result = invocation.proceed();
// 后置逻辑
return result;
}
}
插件机制常用于:
- SQL 打印
- 分页(如 PageHelper)
- 性能监控
- 审计字段(自动填充 created_at/updated_at)
这一块在面试中属于"加分项"。
七、Spring Boot 与 MyBatis 整合的自动装配流程
当加入 MyBatis Starter 时,Spring Boot 会做以下事:
1. 创建 SqlSessionFactory
读取:
- 数据源配置
- MyBatis 配置(mybatis-config.xml)
- Mapper XML 文件
- 类型别名
2. 创建 SqlSessionTemplate(线程安全)
这是你操作数据库的中心入口。
3. 扫描 Mapper 接口
根据:
- @Mapper
- @MapperScan
生成 BeanDefinition。
4. 为 Mapper 生成代理对象
使用 MapperProxyFactory 自动生成 JDK 动态代理。
5. 加载并解析 XML
每个 Mapper XML 中的 <select>, <update>, <insert>, <delete> 都会生成对应的 MappedStatement。
6. 整合事务管理
Spring Boot 自动整合 DataSourceTransactionManager,实现声明式事务(@Transactional)。
这一整套流程保证了 MyBatis 在 Spring Boot 中几乎是"零配置可用"。
八、MyBatis 常见面试高频题总结
下面这些问题,都是 MyBatis 面试必问的:
1. MyBatis 的核心原理是什么?
- Mapper 是动态代理
- SQL 映射到 MappedStatement
- Executor 执行 SQL
- StatementHandler 处理 JDBC
- ResultSetHandler 映射结果
2. 一级缓存与二级缓存的区别?
- 一级缓存:SqlSession 范围,默认开启
- 二级缓存:Mapper namespace 范围,需要手动开启
3. MyBatis 动态 SQL 如何实现?
- XML 解析为 SqlNode 树
- 执行时根据参数动态拼接 SQL
- 使用 Ognl 解析 test 表达式
4. MyBatis 怎么执行一条 SQL?
Mapper 代理 → Executor → StatementHandler → JDBC → ResultSetHandler
5. 为什么 Mapper 接口没有实现类也能工作?
因为 Spring Boot 使用 JDK 动态代理为 Mapper 创建代理对象。
6. MyBatis 插件能拦截什么?
Executor
ParameterHandler
StatementHandler
ResultSetHandler
九、总结
MyBatis 在企业中之所以如此流行,原因在于:
- SQL 自己写 ------ 更灵活,性能可控
- 与数据库结构保持强一致性
- 容易调试、容易排查问题
- 与 Spring Boot 集成之后几乎零配置可用
- Mapper 动态代理 + XML/注解映射机制让开发非常高效
掌握了:
- Mapper 动态代理
- MyBatis 的执行流程
- 缓存机制
- 动态 SQL 原理
- 插件机制
- Spring Boot 自动装配流程
你基本就已经掌握了 MyBatis 的核心体系。