一、MyBatis 底层架构概览
MyBatis 的底层架构基于 JDBC 封装,通过配置驱动和反射机制实现灵活的 SQL 执行与对象映射。其核心组件可分为以下几类:
组件 | 作用 |
---|---|
SqlSessionFactory | 工厂类,负责创建 SqlSession (数据库会话),是 MyBatis 的核心入口。 |
SqlSession | 数据库会话,提供 selectOne() 、insert() 等方法,直接操作数据库。 |
Executor | 执行器,负责管理 Statement 的创建、重用和执行(如 SimpleExecutor 、BatchExecutor )。 |
StatementHandler | 语句处理器,负责将 MyBatis 的 MappedStatement 转换为 JDBC 的 PreparedStatement 或 Statement 。 |
ParameterHandler | 参数处理器,负责将 Java 对象参数转换为 JDBC 语句的参数(处理 #{} 占位符)。 |
ResultSetHandler | 结果集处理器,负责将 JDBC 结果集转换为 Java 对象(处理 resultMap 映射)。 |
TypeHandler | 类型处理器,负责 Java 类型与 JDBC 类型的相互转换(如 String ↔ VARCHAR )。 |
Configuration | 配置类,存储 MyBatis 的全局配置(如数据源、映射文件、类型处理器等)。 |
二、核心组件详解
1. SqlSessionFactory
:工厂与配置中心
SqlSessionFactory
是 MyBatis 的核心入口,通过 SqlSessionFactoryBuilder
读取配置文件(mybatis-config.xml
)或 Java 配置类创建。其核心职责是:
- 加载全局配置(数据源、事务管理器、插件等)。
- 注册映射文件(
Mapper.xml
)或注解定义的 SQL 语句。 - 创建
SqlSession
实例(每次数据库操作需通过SqlSession
执行)。
示例:创建 SqlSessionFactory
// 读取配置文件并构建 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2. SqlSession
:数据库会话
SqlSession
是 MyBatis 的“数据库连接代理”,封装了 JDBC 的 Connection
,提供以下功能:
- 执行 SQL:通过
selectOne()
、update()
等方法执行查询或更新。 - 管理事务:通过
commit()
、rollback()
提交或回滚事务。 - 获取 Mapper 接口代理:通过
getMapper(Class<T> type)
获取 Mapper 接口的动态代理对象(如UserMapper
)。
关键机制:
SqlSession
内部通过 Executor
管理 SQL 执行,每次调用 select()
或 update()
时,会从 Executor
获取一个 Statement
并执行。
3. Executor
:SQL 执行器
Executor
是 MyBatis 的“执行引擎”,负责管理 Statement
的生命周期(创建、重用、关闭),并根据配置选择不同的执行策略。MyBatis 提供了三种默认实现:
执行器类型 | 特点 | 适用场景 |
---|---|---|
SimpleExecutor | 每次执行 SQL 时创建新的 Statement ,执行后关闭。 | 默认执行器,适合大多数场景。 |
ReuseExecutor | 缓存 Statement (按 SQL 和参数缓存),重复使用以减少 JDBC 开销。 | 高频执行相同 SQL 的场景。 |
BatchExecutor | 批量执行 SQL(通过 addBatch() 和 executeBatch() ),减少网络 IO。 | 批量插入/更新场景(如百万级数据)。 |
执行流程:
SqlSession
调用Executor
的update()
或query()
方法。Executor
根据MappedStatement
(SQL 配置)创建或获取Statement
。StatementHandler
处理Statement
的参数设置和执行。
4. StatementHandler
:语句处理器
StatementHandler
是 SQL 执行的核心处理器,负责将 MyBatis 的 MappedStatement
转换为 JDBC 可执行的 Statement
,并处理以下关键逻辑:
- 参数替换:将
#{}
占位符替换为实际参数值(通过ParameterHandler
)。 - SQL 预编译:调用
Connection.prepareStatement()
生成预编译语句(防止 SQL 注入)。 - 结果集处理:调用
ResultSetHandler
处理查询结果。
核心方法:
prepare()
:创建PreparedStatement
或Statement
。parameterize()
:设置 SQL 参数(调用ParameterHandler
)。batch()
:批量执行 SQL(仅BatchExecutor
使用)。
5. ParameterHandler
:参数处理器
ParameterHandler
负责将 Java 对象参数转换为 JDBC 语句的参数,核心逻辑是解析 #{}
占位符并设置参数值。例如:
- 若参数是
User
对象,且#{name}
对应name
字段,则通过反射获取user.getName()
并设置到PreparedStatement
。 - 支持
#{}
的高级特性(如#{id, jdbcType=INT}
指定 JDBC 类型)。
示例:
假设 SQL 为 INSERT INTO user (name, age) VALUES (#{name}, #{age})
,ParameterHandler
会将 name
和 age
参数值设置到 PreparedStatement
的第 1、2 个位置。
6. ResultSetHandler
:结果集处理器
ResultSetHandler
负责将 JDBC 结果集(ResultSet
)转换为 Java 对象,核心依赖 ResultMap
(映射配置)。其处理逻辑包括:
- 简单映射:单表查询时,直接将
ResultSet
的列值映射到 Java 对象的字段(通过TypeHandler
转换类型)。 - 嵌套映射:多表关联查询时,通过
association
(一对一)、collection
(一对多)等标签递归映射子对象。
示例:
若查询结果包含 user_id
、user_name
和 order_id
、order_amount
,ResultMap
可配置将 user_id
和 user_name
映射到 User
对象,order_id
和 order_amount
映射到 Order
对象,并通过 collection
关联到 User
的 orders
属性。
7. TypeHandler
:类型处理器
TypeHandler
是 MyBatis 的“类型桥梁”,负责 Java 类型与 JDBC 类型的相互转换。MyBatis 内置了多种 TypeHandler
(如 StringTypeHandler
、IntegerTypeHandler
),也支持自定义。
核心方法:
setParameter()
:将 Java 类型转换为 JDBC 类型(设置到PreparedStatement
)。getResult()
:将 JDBC 类型转换为 Java 类型(从ResultSet
或CallableStatement
获取)。
自定义 TypeHandler
示例:
// 自定义类型处理器(将 Java 的 LocalDate 转换为 JDBC 的 DATE)
@MappedTypes(LocalDate.class)
@MappedJdbcTypes(JdbcType.DATE)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDate parameter, JdbcType jdbcType) throws SQLException {
ps.setDate(i, Date.valueOf(parameter)); // LocalDate → java.sql.Date
}
@Override
public LocalDate getNullableResult(ResultSet rs, String columnName) throws SQLException {
Date date = rs.getDate(columnName);
return date != null ? date.toLocalDate() : null; // java.sql.Date → LocalDate
}
}
三、SQL 执行全流程
通过以上组件的协作,MyBatis 的 SQL 执行流程可分为以下步骤:
1. 读取配置与初始化
SqlSessionFactory
加载 mybatis-config.xml
和映射文件(Mapper.xml
),解析 MappedStatement
(存储 SQL 语句、参数类型、结果类型等信息)并注册到 Configuration
中。
2. 获取 SqlSession
通过 SqlSessionFactory.openSession()
获取 SqlSession
,内部创建 Executor
(默认 SimpleExecutor
)。
3. 获取 Mapper 代理
通过 sqlSession.getMapper(UserMapper.class)
获取 UserMapper
接口的动态代理对象。代理对象的 invoke()
方法会拦截所有方法调用,转发到 SqlSession
执行。
4. 执行 SQL
- 参数处理:
Executor
调用StatementHandler.prepare()
创建PreparedStatement
,并通过ParameterHandler.setParameter()
设置参数值(解析#{}
占位符)。 - SQL 执行:
StatementHandler.parameterize()
执行PreparedStatement.execute()
(查询返回ResultSet
,更新返回影响行数)。 - 结果映射:若为查询,
ResultSetHandler.handleResultSets()
根据ResultMap
将ResultSet
转换为 Java 对象(递归处理嵌套映射)。
5. 事务管理
- 本地事务:通过
SqlSession.commit()
或rollback()
控制 JDBC 事务(基于Connection.setAutoCommit(false)
)。 - 分布式事务:需结合外部框架(如 Seata),MyBatis 本身不直接支持。
四、ORM 映射的核心机制
MyBatis 的 ORM 映射通过 MappedStatement
和 ResultMap
实现,核心是将 SQL 语句与 Java 对象绑定。
1. MappedStatement
:SQL 配置的元数据
MappedStatement
存储了 SQL 语句的所有元数据,包括:
- SQL 语句内容(
statementType
:STATEMENT
/PREPARED
/CALLABLE
)。 - 参数类型(
parameterType
:Java 类或别名)。 - 结果类型(
resultType
:Java 类或别名)。 - 结果映射(
resultMap
:复杂映射的 ID)。 - 缓存配置(是否启用一级/二级缓存)。
示例(XML 映射文件):
<mapper namespace="com.example.UserMapper">
<select id="getUserById" parameterType="Long" resultType="User">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
</mapper>
此配置会生成一个 MappedStatement
,关联 getUserById
方法、User
结果类型和 #{id}
参数。
2. ResultMap
:复杂映射的解决方案
当 SQL 查询涉及多表关联或字段名与对象属性名不一致时,需通过 ResultMap
显式定义映射规则。例如:
场景:数据库表 user
有字段 user_name
,而 Java 对象 User
的属性是 userName
(驼峰命名)。
ResultMap
配置:
<resultMap id="userMap" type="User">
<result column="user_name" property="userName"/> <!-- 字段名与属性名不一致时映射 -->
</resultMap>
使用 ResultMap
:
<select id="getUserById" parameterType="Long" resultMap="userMap">
SELECT user_name FROM user WHERE id = #{id}
</select>
五、缓存机制:提升性能的关键
MyBatis 提供了一级缓存和二级缓存,用于减少数据库查询次数,提升性能。
1. 一级缓存(本地缓存)
- 作用域:
SqlSession
级别(同一个SqlSession
中多次查询同一数据,仅执行一次 SQL)。 - 触发条件:
- 相同
SqlSession
。 - 相同
statementId
(即相同的 Mapper 方法)。 - 相同参数。
- 相同
- 失效场景:
SqlSession
关闭或提交/回滚事务。- 执行了
update
、delete
、insert
等修改操作(缓存会被清空)。
示例:
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询(执行 SQL)
User user1 = mapper.getUserById(1L);
// 第二次查询(直接从一级缓存获取)
User user2 = mapper.getUserById(1L);
session.close(); // 缓存失效
2. 二级缓存(全局缓存)
- 作用域:
Mapper
级别(所有SqlSession
共享同一缓存)。 - 启用条件:
- 在
mybatis-config.xml
中开启全局二级缓存:<setting name="cacheEnabled" value="true"/>
。 - 在
Mapper.xml
中声明<cache/>
(或通过注解@CacheNamespace
)。
- 在
- 失效场景:
- 缓存时间过期(通过
eviction
和flushInterval
配置)。 - 执行了修改操作(默认清空缓存,可通过
flushCache="false"
配置不自动清空)。
- 缓存时间过期(通过
示例:
<!-- mybatis-config.xml -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- UserMapper.xml -->
<mapper namespace="com.example.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="1024"/> <!-- LRU 策略,60秒刷新,最大1024条 -->
<select id="getUserById" parameterType="Long" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
六、动态 SQL:灵活处理复杂查询
MyBatis 支持通过 XML 标签或注解编写动态 SQL,根据不同条件拼接 SQL 语句(如 if
、foreach
、choose
等)。其底层通过 DynamicSqlSource
解析动态标签,生成最终的 SQL 语句。
示例(XML 动态 SQL):
<select id="getUserList" parameterType="UserQuery" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
<if test="orderBy != null">
ORDER BY ${orderBy}
</if>
</select>
<where>
标签自动处理WHERE
关键字和多余的AND
。<if>
标签根据参数是否存在动态拼接条件。${orderBy}
直接拼接字符串(需注意 SQL 注入风险,建议配合白名单校验)。
七、插件机制:扩展 MyBatis 功能
MyBatis 支持通过**插件(Interceptor)**拦截核心组件的方法(如 Executor.update()
、StatementHandler.parameterize()
),实现自定义逻辑(如分页、日志、性能监控)。
插件实现步骤:
- 定义拦截器类,实现
Interceptor
接口,重写intercept(Invocation invocation)
方法。 - 通过
@Intercepts
和@Signature
注解指定拦截的目标类和方法。 - 将拦截器注册到
SqlSessionFactory
的Configuration
中。
示例:分页插件(简化版)
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取分页参数(如 PageHelper 设置的 offset/limit)
Object parameter = invocation.getArgs()[1];
if (parameter instanceof Page) {
Page page = (Page) parameter;
// 修改 SQL 为带 LIMIT/OFFSET 的分页语句
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = ms.getBoundSql(parameter);
String originalSql = boundSql.getSql();
String pageSql = originalSql + " LIMIT " + page.getOffset() + ", " + page.getLimit();
// 替换 BoundSql 的 SQL
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
// 通过反射替换 Executor 的参数
MappedStatement newMs = copyFromNewBoundSql(ms, newBoundSql);
return invocation.proceed(); // 执行修改后的 SQL
}
return invocation.proceed();
}
}
八、总结
MyBatis 的底层原理围绕JDBC 封装和ORM 映射展开,通过核心组件(SqlSessionFactory
、SqlSession
、Executor
等)的协作,实现了 SQL 执行、参数处理、结果映射的高效流程。其优势在于:
- 简化 JDBC:通过配置和映射减少重复代码。
- 灵活扩展:支持动态 SQL、插件机制、自定义类型处理器。
- 性能优化:通过一级/二级缓存、批量执行器提升效率。
理解 MyBatis 的底层原理,能帮助开发者更高效地使用其特性(如优化 SQL 执行、解决缓存问题、扩展插件功能),并在遇到性能瓶颈或 bug 时快速定位问题。