《MyBatis》 第二章:核心架构与工作流程
本章我们将深入 MyBatis 的内部结构,通过源码和流程图,看清一次 SQL 执行的全貌。
2.1 架构总览:四大核心组件
MyBatis 的架构可以分为三层:接口层、核心处理层、基础支撑层。

组件职责:
-
SqlSession:门面接口,提供增删改查方法。
-
Executor:执行器,调度 StatementHandler、缓存、事务。
-
StatementHandler:封装 JDBC Statement 操作。
-
ParameterHandler:参数处理。
-
ResultSetHandler:结果集映射。
-
Configuration:全局配置,持有所有元数据。
-
TypeHandler:Java 类型 <-> JDBC 类型转换。
-
Transaction:事务抽象。
-
DataSource:数据源。
-
Cache:一级/二级缓存。
2.2 启动流程:SqlSessionFactory 的构建

关键源码:XMLConfigBuilder.parseConfiguration() 依次解析各个配置节点,最终生成 Configuration 对象。
2.3 会话创建:SqlSession 的生命周期
// DefaultSqlSessionFactory.java
private SqlSession openSessionFromDataSource(ExecutorType execType,
TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = environment.getTransactionFactory();
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建 Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
}
}
SqlSession 内部持有 Executor,而 Executor 可能包含一级缓存。因此 SqlSession 是非线程安全的,最佳实践是每次数据库操作都新建,使用完后立即关闭。
2.4 Mapper 代理:从接口到 SQL
当调用 sqlSession.getMapper(UserMapper.class) 时,返回的是 MapperProxy 的代理对象。
// MapperProxy.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 缓存 MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行
return mapperMethod.execute(sqlSession, args);
}
MapperMethod.execute() 根据 SQL 类型(INSERT、UPDATE、DELETE、SELECT)调用 sqlSession 的对应方法,并处理返回值转换。
2.5 一次查询的全流程(带源码分析)
我们以 UserMapper.selectById(1L) 为例,跟踪核心源码。
2.5.1 Executor.query()
// DefaultSqlSession.java
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
executor 可能是 SimpleExecutor、ReuseExecutor、BatchExecutor,也可能是被插件代理后的对象。我们看 BaseExecutor 的模板方法:
// BaseExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存未命中,执行数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
2.5.2 queryFromDatabase 调用 doQuery
queryFromDatabase 最终调用子类的 doQuery。以 SimpleExecutor 为例:
// SimpleExecutor.java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter,
rowBounds, resultHandler, boundSql);
// 准备 Statement(通过 Transaction 获取 Connection)
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
2.5.3 StatementHandler 处理流程
StatementHandler 的 query() 由子类 PreparedStatementHandler 实现:
// PreparedStatementHandler.java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
2.5.4 参数设置(ParameterHandler)
在 prepareStatement 阶段会调用 handler.parameterize(stmt):
// DefaultParameterHandler.java
public void setParameters(PreparedStatement ps) {
// 遍历参数映射,使用 TypeHandler 设置值
for (int i = 0; i < parameterMappings.size(); i++) {
// ...
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
2.5.5 结果集映射(ResultSetHandler)

DefaultResultSetHandler 的 handleResultSets() 会遍历 ResultSet,根据 ResultMap 创建对象并填充属性。
2.6 设计模式全景图
| 设计模式 | 体现 | 源码位置 |
|---|---|---|
| Builder 模式 | 解析配置构建 Configuration | XMLConfigBuilder、XMLMapperBuilder |
| 工厂模式 | 创建 SqlSession、Executor 等 | SqlSessionFactory、Configuration.newExecutor() |
| 代理模式 | Mapper 接口实现、插件拦截 | MapperProxy、Plugin |
| 模板方法模式 | 定义算法骨架,子类实现细节 | BaseExecutor、BaseStatementHandler |
| 策略模式 | 根据 Executor 类型选择执行策略 | Executor 的三种实现 |
| 装饰器模式 | 为 Executor 增加二级缓存 | CachingExecutor |
| 责任链模式 | 多个插件按顺序拦截 | InterceptorChain |
2.7 本章小结
-
MyBatis 的启动过程是解析配置文件构建
Configuration。 -
SqlSession是非线程安全的,生命周期应短。 -
Mapper 接口通过动态代理将方法调用转换为 SQL 执行。
-
一次查询涉及
Executor、StatementHandler、ParameterHandler、ResultSetHandler的协作。 -
框架运用了多种设计模式,具备良好的扩展性。
面试题
1. MyBatis 的 Executor 三种类型分别适用于什么场景?BATCH 模式下为何无法获取自增主键?
参考思路:
SIMPLE:默认,每次新建 Statement,适合普通场景。
REUSE:复用 PreparedStatement,适合循环执行同构 SQL。
BATCH:批量执行,适合批量插入/更新,性能高。BATCH 无法获取自增主键的原因:JDBC 的
executeBatch()返回的是int[]影响行数,数据库返回的主键无法在此过程中获取。如需主键,可改用逐条插入或使用支持批量返回主键的数据库驱动特性(如 MySQL 的rewriteBatchedStatements=true)。
2. 请描述从 Mapper 接口方法调用到 SQL 执行的完整调用链。
参考思路:
Mapper 接口方法 →MapperProxy.invoke()→MapperMethod.execute()→SqlSession.selectOne()→Executor.query()→StatementHandler.query()→ParameterHandler.setParameters()→Statement.execute()→ResultSetHandler.handleResultSets()。
3. SqlSession 为什么是线程不安全的?在 Spring 整合后如何保证?
参考思路:
SqlSession内部持有Executor,Executor包含一级缓存和延迟加载队列,多线程共享会导致数据混乱。Spring 整合使用
SqlSessionTemplate,它通过ThreadLocal为每个线程绑定独立的 SqlSession,方法执行完毕后自动关闭,从而保证线程安全。
更多推荐



所有评论(0)