第5篇-事务管理器的设计哲学
约 1491 字大约 5 分钟
2026-05-24
四、TransactionSynchronizationManager:事务资源的绑定机制"
TransactionSynchronizationManager 是 Spring 事务的"资源管理器"。
它使用 ThreadLocal 来管理当前线程的事务资源。
4.1 ThreadLocal 的使用"
// 类路径:org.springframework.transaction.support.TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {
/**
* 【核心】:存储事务资源的 ThreadLocal。
*
* key:资源工厂(通常是 DataSource)
* value:资源(通常是 ConnectionHolder)
*/
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
/**
* 【核心方法】:绑定资源到当前线程。
*
* @param key 资源工厂(通常是 DataSource)
* @param value 资源(通常是 ConnectionHolder)
*/
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// 如果 ThreadLocal 中没有 Map,就创建一个
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
// 把资源放到 Map 中
Object oldValue = map.put(actualKey, value);
// 如果已经有一个资源了,抛异常!
if (oldValue != null) {
throw new IllegalStateException(
"Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
}
}
/**
* 【核心方法】:从当前线程获取资源。
*
* @param key 资源工厂(通常是 DataSource)
* @return 资源(通常是 ConnectionHolder),或 null
*/
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doGetResource(actualKey);
}
/**
* 实际获取资源的方法。
*/
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// 透明地处理已标记为 void 的 ResourceHolder...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// 如果 Map 为空了,清理 ThreadLocal...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
/**
* 【核心方法】:从当前线程解除绑定资源。
*
* @param key 资源工厂(通常是 DataSource)
* @return 之前绑定的资源
*/
public static Object unbindResource(Object key) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
if (value == null) {
throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread");
}
return value;
}
/**
* 实际解除绑定资源的方法。
*/
@Nullable
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// 如果 Map 为空了,清理 ThreadLocal...
if (map.isEmpty()) {
resources.remove();
}
// 透明地处理已标记为 void 的 ResourceHolder...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
value = null;
}
return value;
}
}设计哲学 3:**ThreadLocal 管理事务资源"
Spring 使用 ThreadLocal 来管理事务资源:
当前线程
|
+-- ThreadLocal<Map<DataSource, ConnectionHolder>>
|
+-- key:DataSource1 → value:ConnectionHolder1
+-- key:DataSource2 → value:ConnectionHolder2这样做的好处是:
事务资源与线程绑定:
- 每个线程有自己独立的事务资源
- 不会出现"线程 A 的事务资源被线程 B 使用"的情况
支持多数据源:
- Map 的 key 是 DataSource
- 这样,不同的 DataSource 可以有自己的连接
自动清理:
- 事务提交或回滚后,
doCleanupAfterCompletion()会清理 ThreadLocal
- 事务提交或回滚后,
五、事务资源的传递机制"
现在,我们来看看:事务资源是如何传递的?
5.1 事务开始时:绑定资源"
1. 调用 transactionManager.getTransaction()
↓
2. 调用 doBegin()
↓
3. 从数据源获取连接
↓
4. 【核心】:调用 TransactionSynchronizationManager.bindResource(dataSource, connectionHolder)
↓
5. 连接被绑定到当前线程的 ThreadLocal 中5.2 事务执行时:获取资源"
1. MyBatis 执行 SQL
↓
2. 调用 DataSourceUtils.getConnection(dataSource)
↓
3. 【核心】:调用 TransactionSynchronizationManager.getResource(dataSource)
↓
4. 从当前线程的 ThreadLocal 中获取 ConnectionHolder
↓
5. 从 ConnectionHolder 中获取 Connection
↓
6. 使用这个 Connection 执行 SQL5.3 事务提交/回滚时:使用资源"
1. 调用 transactionManager.commit()
↓
2. 调用 doCommit()
↓
3. 从事务对象中获取 ConnectionHolder
↓
4. 从 ConnectionHolder 中获取 Connection
↓
5. 调用 Connection.commit() 或 Connection.rollback()5.4 事务结束时:清理资源"
1. 调用 doCleanupAfterCompletion()
↓
2. 【核心】:调用 TransactionSynchronizationManager.unbindResource(dataSource)
↓
3. 从当前线程的 ThreadLocal 中移除 ConnectionHolder
↓
4. 恢复连接的隔离级别、只读属性
↓
5. 恢复连接的自动提交模式
↓
6. 释放连接(返回给连接池)六、扩展点总结"
在这篇文章中,我们提到了以下几个扩展点:
| 扩展点 | 场景 | 如何扩展 |
|---|---|---|
| 自定义事务管理器 | 你想支持新的事务资源(比如,某个新数据库) | 继承 AbstractPlatformTransactionManager,实现模板方法 |
| 自定义资源绑定逻辑 | 你想用其他方式管理事务资源(比如,用 InheritableThreadLocal) | 修改 TransactionSynchronizationManager 的 ThreadLocal |
| 多数据源支持 | 你有多个数据库,想用不同的事务管理器 | 配置多个 PlatformTransactionManager Bean,并使用 @Transactional(transactionManager = "...") 指定 |
| 自定义事务同步 | 你想在事务提交/回滚时执行一些操作(比如,清理缓存) | 实现 TransactionSynchronization 接口,并注册到 TransactionSynchronizationManager |
七、小结与伏笔"
好了,到这里,我们已经把事务管理器的"设计哲学"揭开了。
让我们来回顾一下整个流程:
PlatformTransactionManager定义了事务管理的 3 个核心方法:getTransaction()、commit()、rollback()AbstractPlatformTransactionManager使用了模板方法模式:- 父类定义算法骨架(处理传播行为、提交、回滚)
- 子类实现具体步骤(
doBegin()、doCommit()、doRollback()等)
DataSourceTransactionManager是 Spring 提供的 JDBC 事务管理器:doBegin()会:获取连接、设置隔离级别、关闭自动提交、绑定连接到当前线程doCommit()会:获取连接,调用Connection.commit()doRollback()会:获取连接,调用Connection.rollback()
TransactionSynchronizationManager使用 ThreadLocal 来管理事务资源:bindResource():绑定资源到当前线程getResource():从当前线程获取资源unbindResource():从当前线程解除绑定资源
🔮 伏笔:MyBatis 是如何获取到事务连接的?
你可能会注意到,TransactionSynchronizationManager 把连接绑定到了当前线程。
那么问题来了:
- MyBatis 执行 SQL 时,是如何获取到这个连接的?
- MyBatis 的
SqlSession是如何与 Spring 的事务集成的? - MyBatis 的一级缓存与 Spring 事务有什么关系?
这些问题,我们会在系列三(MyBatis 系列)中详细讲解。
下一篇预告:《Spring 事务(六):事务资源的绑定与传递》
在这篇文章中,我们会讲解:
TransactionSynchronizationManager的五大 ThreadLocal- 事务资源(连接)是如何绑定、获取、传递的
- 事务同步(TransactionSynchronization)的工作原理
- 如何自定义事务同步
写在最后:这篇文章比较长,但我觉得值得。因为只有当理解了事务管理器的设计哲学,你才能真正理解 Spring 事务的工作原理。如果你有什么疑问,欢迎留言讨论!