本质是「事务同步器的绑定范围」和「事务传播行为(默认 REQUIRED)」的协同问题
一、核心前提(关键背景)
1. 事务传播行为(默认 REQUIRED)的本质
Spring 事务默认传播行为 REQUIRED 的规则是:
- 如果当前存在活跃事务,就加入这个事务;
- 如果当前无活跃事务,就新建一个事务。
2. 事务同步器的绑定规则
TransactionSynchronizationManager 的同步器集合是绑定到「当前事务」+「当前线程」 的:
- 同步器通过
ThreadLocal<Set<TransactionSynchronization>>维护,仅属于「当前活跃事务」; - 只有当
TransactionSynchronizationManager.isSynchronizationActive()为true(即当前线程有活跃事务且同步功能开启)时,注册的同步器才会被记录; - 事务完成(提交 / 回滚)后,该事务对应的同步器集合会被清空。
二、问题根源:内层事务的同步器「无可用的活跃事务」绑定
我们拆解整个执行流程,就能明确为什么内层的 registerSynchronization 不生效:
步骤 1:外层事务执行,注册同步器 A
@Transactional // 外层事务(事务T1)
public void outerMethod() {
// 1. 此时线程中存在活跃事务T1,同步功能开启
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 外层T1提交后执行
innerMethod(); // 调用内层事务方法
}
});
}
- 此时线程绑定事务 T1,
isSynchronizationActive()=true,同步器 A 被成功注册到 T1 的同步器集合中。
步骤 2:外层事务 T1 提交,触发同步器 A 的 afterCommit
- 事务 T1 的提交流程:
beforeCommit→ 提交数据库事务 →afterCommit→afterCompletion; - 当执行到
afterCommit时,事务 T1 已经提交完成(数据库层面已持久化),但 T1 的「同步器生命周期」还没结束(要等afterCompletion执行完); - 关键:此时
TransactionSynchronizationManager中,T1 的「同步功能已关闭」(isSynchronizationActive()=false),且 T1 不再是「活跃事务」。
步骤 3:afterCommit 中调用内层事务方法(默认 REQUIRED)
@Transactional // 内层事务(默认 REQUIRED)
public void innerMethod() {
// 2. 此时线程中无活跃事务(T1已提交),因此新建事务T2
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 期望T2提交后执行,但实际不生效
}
});
}
这里的核心矛盾:
- 内层方法
innerMethod因REQUIRED传播行为,新建了事务 T2; - 但在
innerMethod中注册同步器时,T2 的「同步功能未开启」 ------ 因为 Spring 只有在「事务执行阶段(beforeCommit / 提交 / 回滚)」才会开启同步功能,而afterCommit是外层事务 T1 的「收尾阶段」,此时 Spring 不会为 T2 开启同步器注册的上下文。
步骤 4:内层事务 T2 执行完,同步器无法触发
- 内层事务 T2 提交时,由于其同步功能未开启,注册的同步器并未被绑定到 T2 的生命周期中;
- 即使 T2 提交成功,也没有任何逻辑去遍历执行它的同步器,因此内层的
afterCommit永远不会执行。
三、更直观的关键结论
外层事务的 afterCommit 是「事务已提交、同步功能已关闭」的阶段,此时调用的内层事务(无论新建还是复用),其 TransactionSynchronizationManager 的同步功能未激活,注册的同步器无法被绑定到内层事务的生命周期,最终无法触发。
四、解决方案:让内层事务的同步器生效
核心思路是:让内层事务的同步器注册到「自身事务的活跃上下文」中,而非外层事务的收尾阶段。
方案 1:调整内层事务的调用时机(推荐)
将内层事务的调用从外层的 afterCommit 移到「外层事务执行阶段」(而非收尾阶段),此时内层事务加入外层事务(REQUIRED),同步器可正常注册:
@Transactional // 外层事务T1
public void outerMethod() {
// 1. 外层事务执行阶段(非afterCommit),调用内层方法
innerMethod();
// 外层自己的同步器(可选)
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 外层T1提交后的逻辑(无需调用内层事务)
}
});
}
@Transactional // 内层事务(加入T1)
public void innerMethod() {
// 2. 此时T1活跃,同步功能开启,同步器可正常注册并触发
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 会随T1的提交触发(因为内层加入了T1)
System.out.println("内层同步器执行");
}
});
}
方案 2:内层事务强制新建(REQUIRES_NEW)+ 手动保证同步功能激活
若必须在 afterCommit 中调用内层事务,需将内层事务的传播行为改为 REQUIRES_NEW(强制新建独立事务),并确保内层事务执行时同步功能开启:
@Transactional // 外层事务T1
public void outerMethod() {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 外层T1提交后,调用内层方法(新建T2)
innerMethod();
}
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // 强制新建T2
public void innerMethod() {
// 先检查并开启同步功能(Spring事务管理器默认会开启,此处显式确认)
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.initSynchronization(); // 手动开启(可选,Spring已自动处理)
}
// 注册内层T2的同步器,此时T2活跃,同步功能开启,可正常触发
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
System.out.println("内层同步器执行"); // 会随T2的提交触发
}
});
}
五、补充注意事项
REQUIRES_NEW会新建独立事务,需注意事务隔离性(如内层事务提交后,外层事务若回滚,内层无法回滚);- 避免在
afterCommit中执行核心业务逻辑(失败无法回滚),仅用于通知、缓存更新等非核心操作; - 内层事务的同步器触发时机是「内层事务 T2 提交后」,而非外层 T1,需确认业务逻辑是否依赖这个顺序;
- 若内层事务无
@Transactional注解,调用时不会新建事务,registerSynchronization会直接抛出IllegalStateException(无活跃事务)。
总结
内层同步器不生效的核心原因是:外层 afterCommit 阶段无活跃的「同步功能开启的事务」,内层事务即使新建,其同步器也无法绑定到有效事务上下文。解决的关键是让内层事务的同步器注册到「自身事务的活跃阶段」(而非外层事务的收尾阶段),要么调整调用时机,要么强制内层新建事务并保证同步功能激活。