第3篇-事务管理——SpringManagedTransaction的秘密
约 1768 字大约 6 分钟
2026-05-24
四、MyBatis-Spring 整合的全链路#
4.1 从 Mapper 方法调用到 SQL 执行#
让我们梳理一下,从调用 UserMapper.selectById() 到 SQL 在数据库中执行的完整链路:
1. 调用 UserMapper.selectById()(实际上是 MapperProxy 代理对象)
↓
2. MapperProxy.invoke() 方法被调用
↓
3. MapperMethod.execute() 方法被调用
↓
4. SqlSessionTemplate.selectOne() 方法被调用
↓
5. SqlSessionInterceptor.invoke() 方法被调用
↓
6. 获取 SqlSession(通过 SqlSessionUtils.getSqlSession())
↓
7. 调用 SqlSession.selectOne() 方法
↓
8. Executor.query() 方法被调用
↓
9. 调用 SpringManagedTransaction.getConnection() 获取连接
↓
10. DataSourceUtils.getConnection() 被调用
↓
11. 从 TransactionSynchronizationManager 中获取连接(如果当前有事务)
或者从数据源中获取连接(Druid 连接池)
↓
12. 使用连接创建 Statement,执行 SQL
↓
13. 返回结果白话文解释:
- 整个链路很长,但核心就是:MyBatis 通过
SpringManagedTransaction,使用 Spring 管理的事务连接。 - 如果当前有事务,所有的 SQL 都会使用同一个连接(从而保证事务的原子性)。
- 如果当前没有事务,每次 SQL 都会使用新的连接(但
SqlSession可能会缓存连接,取决于配置)。
五、事务传播行为:Spring 如何管理多个事务?#
5.1 事务传播行为的原理#
你可能会问:如果方法 A 调用了方法 B,它们都有 @Transactional 注解,那事务是怎么传播的?
答案是:Spring 通过 TransactionSynchronizationManager 来管理。
// 类路径:org.springframework.transaction.support.TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {
// 【核心】事务资源(key:数据源,value:ConnectionHolder)
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
// 【核心】获取连接(重要!)
public static Object getResource(Object key) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
return map.get(key);
}
// 【核心】绑定资源(重要!)
public static void bindResource(Object key, Object value) throws IllegalStateException {
Map<Object, Object> map = resources.get();
if (map == null) {
map = new HashMap<Object, Object>();
resources.set(map);
}
map.put(key, value); // 【注意】将连接绑定到当前线程
}
}白话文解释:
- 事务资源:
TransactionSynchronizationManager内部使用了一个ThreadLocal,存放了"数据源 → 连接"的映射关系。 - 获取连接:当 MyBatis 需要连接时,会通过
getResource(dataSource)获取。 - 绑定资源:当事务开始时,Spring 会把连接绑定到当前线程(
bindResource())。这样,同一个事务中的后续 SQL 都会使用这个连接。
你可能会问:如果方法 A 调用了方法 B,它们都有 @Transactional 注解,那连接是怎么传递的?答案是:取决于事务传播行为:
PROPAGATION_REQUIRED(默认):如果当前有事务,就加入;否则,就创建新事务。PROPAGATION_REQUIRES_NEW:挂起当前事务,创建新事务。PROPAGATION_SUPPORTS:如果当前有事务,就加入;否则,就以非事务方式执行。
六、埋下伏笔:MyBatis 的插件机制#
说到这里,你可能会问:MyBatis 的插件(比如分页插件)是如何拦截 SQL 执行的?
这个问题,我们会在下一篇文章(系列三第4篇)中详细解答。这里先预告一下核心内容:
- MyBatis 的插件是通过动态代理实现的(类似 AOP)。
- 插件可以拦截
Executor、StatementHandler、ParameterHandler、ResultSetHandler的方法。 - 插件的使用场景:分页、乐观锁、数据权限过滤等。
下一篇文章的预告:《MyBatis 系列(四):SQL 执行的全链路(StatementHandler)》
七、总结与思考#
今天我们一起深入了 MyBatis-Spring 整合的核心 —— SpringManagedTransaction,了解了它如何通过 DataSourceUtils 获取连接,以及 Spring 的事务传播行为是如何工作的。
让我们用几个问题来检验一下今天的学习成果:
7.1 SpringManagedTransaction 的作用是什么?
- 它是 MyBatis 与 Spring 事务管理的"桥梁"。
- 它让 MyBatis 使用 Spring 管理的事务连接(而不是自己管理)。
- 它的
getConnection()方法会通过DataSourceUtils.getConnection()获取连接(如果当前有事务,就从TransactionSynchronizationManager中获取;否则,就从数据源中获取)。
7.2 MyBatis 为什么不能自己管理事务?
- 因为在实际开发中,事务通常是由 Spring 的
@Transactional注解管理的。 - 如果 MyBatis 自己也管理事务,就会发生冲突(比如 MyBatis 提交了事务,但 Spring 还希望回滚)。
7.3 如何保证"同一个事务中,使用同一个连接"?
- Spring 通过
TransactionSynchronizationManager来管理。 - 当事务开始时,Spring 会把连接绑定到当前线程(
bindResource())。 - 当 MyBatis 需要连接时,会通过
getResource()获取(从而保证使用同一个连接)。
7.4 事务传播行为是如何工作的?
- Spring 通过
TransactionSynchronizationManager来管理事务资源。 - 不同的传播行为(比如
REQUIRED、REQUIRES_NEW)会有不同的逻辑(加入当前事务、挂起当前事务并创建新事务等)。
八、扩展阅读#
【扩展点】自定义事务管理器#
你可以自定义事务管理器,实现特殊的业务逻辑。比如,你可以实现一个"多数据源事务管理器",用于多数据源场景(比如一个事务中涉及两个数据库)。
// 【扩展点】自定义事务管理器示例
public class MultiDataSourceTransactionManager extends AbstractPlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 【注意】为每个数据源创建连接,并绑定到线程
for (DataSource dataSource : dataSources) {
Connection con = dataSource.getConnection();
con.setAutoCommit(false);
TransactionSynchronizationManager.bindResource(dataSource, new ConnectionHolder(con));
}
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
// 【注意】提交每个数据源的事务
for (DataSource dataSource : dataSources) {
ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
holder.getConnection().commit();
}
}
// ... 其他方法(doRollback、doCleanupAfterCompletion 等)
}场景:如果你的应用涉及多个数据库(比如"分库分表"场景),你就需要自定义事务管理器,保证"跨库事务"的原子性。
如何使用自定义事务管理器? 在 Spring 配置中:
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager() {
// 【注意】使用自定义事务管理器
return new MultiDataSourceTransactionManager(dataSource1, dataSource2);
}
}好了,今天的文章就到这里。下一篇,我们将深入 StatementHandler,看看它是如何创建 Statement、设置参数、执行 SQL、处理结果集的。敬请期待!
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我继续创作的动力!
系列文章目录:
- 系列一:Spring 事务系列(已完成)
- 第1篇:@Transactional 注解的"前世今生"
- 第2篇:AOP 代理 —— 事务的"隐身衣"
- 第3篇:事务传播机制源码深度解析
- 第4篇:事务隔离级别的实现原理
- 第5篇:事务管理器的设计哲学"
- 第6篇:事务资源的绑定与传递
- 第7篇:提交、回滚与异常规则
- 系列二:数据源系列(已完成)
- 第1篇:JDBC 规范与 DriverManager 的 legacy
- 第2篇:Druid 连接池的整体架构
- 第3篇:连接的借与还(getConnection 全流程)
- 第4篇:Druid 过滤链与监控统计
- 第5篇:MySQL 驱动的 JDBC 实现
- 第6篇:连接的生命周期与事务边界
- 系列三:MyBatis 系列(进行中)
- 第1篇:从 Mapper 接口到 SQL 执行的奇幻漂流(已完成)
- 第2篇:Executor 执行器的三种形态(已完成)
- 第3篇:事务管理 —— SpringManagedTransaction 的秘密(本文)
- 第4篇:SQL 执行的全链路(StatementHandler)
- 第5篇:插件机制与责任链
- 系列四:总结系列(待开始)