概述
在《AOP 原理剖析:代理创建、拦截链与通知顺序》(AOP 上篇)中,我们深入拆解了 Spring AOP 的两种代理创建方式、ReflectiveMethodInvocation 的递归拦截链,以及通知顺序的控制机制。在《AOP 进阶:AspectJ 集成、LTW 织入与工程实践》(AOP 下篇)中,我们又进一步探讨了编译期与类加载期织入。本文将以上下篇的 AOP 知识基座为起点,展示 AOP 的能力如何被赋予具体功能------它将不再是"日志打印"这样的简单切面,而是需要精确控制传播、回滚与资源清理的事务管理器调用链。
声明式事务是 Spring AOP 最经典、最复杂的应用。2003 年 Rod Johnson 在《Expert One-on-One J2EE》中对 EJB CMT 的批判,奠定了声明式事务管理思想的雏形。而今天,Spring 的 @Transactional 仅凭一个注解就能完整管理事务,其背后是一套精密的事务拦截器系统在无声运转。本文将拆解 TransactionInterceptor 的源码,追踪从代理入口到事务完成的全过程,帮助专家真正理解"声明式"强大背后的执行清单。
核心要点:
- 事务代理基础设施 :
@EnableTransactionManagement注册了TransactionInterceptor、TransactionAttributeSource和BeanFactoryTransactionAttributeSourceAdvisor,三者构成了事务 AOP 的基础骨架。 - TransactionInterceptor 源码拆解 :
invoke方法在TransactionAspectSupport.invokeWithinTransaction中组织事务全生命周期:获取事务定义→事务管理器→开启事务→执行目标→提交/回滚→清理资源。 - 传播行为实现 :
AbstractPlatformTransactionManager.getTransaction根据PROPAGATION_REQUIRED、REQUIRES_NEW、NESTED等传入定义,执行事务复用、挂起、保存点创建等分支逻辑。 - 回滚规则判断 :
TransactionAttribute.rollbackOn(ex)遍历一组RollbackRule,以instanceof语义匹配异常,默认对RuntimeException与Error回滚。 - 事务同步机制 :
TransactionSynchronizationManager管理线程绑定的资源和回调列表,beforeCommit、afterCommit、afterCompletion等回调在事务提交/回滚前后被精确触发。 - 与 AOP 拦截链的协作 :
TransactionInterceptor的Order默认为Ordered.LOWEST_PRECEDENCE,确保在用户自定义切面之后执行,形成"外层切面环绕内层事务"的标准模型。
文章组织架构说明:
- AOP承接回顾:简要回顾前文核心------代理创建、拦截器链与递归执行机制,为事务拦截器在拦截链中的位置和协作方式奠定基础。
- @EnableTransactionManagement 解析与基础设施注册 :剖析注解如何通过
TransactionManagementConfigurationSelector导入配置,注册TransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并展示纯手动配置验证。 - TransactionInterceptor 源码拆解 :深入
invoke→invokeWithinTransaction全链路,逐行分析事务属性获取、目标方法执行、异常判断与资源清理。 - 事务管理器调用链 :以
AbstractPlatformTransactionManager为骨架,讲解getTransaction、commit、rollback的模板方法模式,为后续传播行为分支打基础。 - 传播行为分支深入 :重点拆解
handleExistingTransaction中REQUIRED、REQUIRES_NEW、NESTED的源码分支,以及挂起/恢复、保存点机制。 - 回滚规则与判断机制 :从
@Transactional属性到RollbackRule列表,再到底层rollbackOn的instanceof匹配算法,阐明默认行为与自定义覆盖。 - 事务同步机制 :
TransactionSynchronizationManager如何管理回调列表,以及beforeCommit、afterCommit、afterCompletion的精确执行时机,并演示编程式注册。 - 生产事故排查 :以三个真实事故(
REQUIRES_NEW失效、checked 异常不回滚、同步回调连接泄漏)展示理解偏差导致的严重后果。 - 面试高频专题:15 道层层递进的面试题,涵盖源码级原理与系统设计题,独立于正文,适用于人才考察与自我检验。
一、AOP 承接与事务代理总览
1.1 回顾:代理创建与拦截链
在 AOP 上篇中,我们已理清:Spring 使用 ProxyFactory 或自动代理创建器(如 AbstractAutoProxyCreator)为目标 Bean 生成 JDK 动态代理或 CGLIB 子类代理。每当调用代理对象的方法时,会进入 ReflectiveMethodInvocation 的递归调用链,逐一执行匹配的 MethodInterceptor(通知),最终通过 invokeJoinpoint 调用目标方法。
这个拦截链的每一个组件都是一个 Advice 节点,按照 Order 值排序。用户自定义的切面(如日志、权限校验)通常会配置较高的优先级(较小 Order 值),以确保在业务逻辑之前执行横切逻辑。事务作为一种"环绕"型基础能力,则被安排在拦截链的末端------这种设计保证了用户切面可以影响事务内部执行的环境(如修改线程上下文),并在事务边界内异常时能被事务回滚逻辑捕获。
1.2 @Transactional 不是日志切面
@Transactional 注解所承载的语义,远比一句"打印日志"复杂。它要求在单次 proceed() 调用中,完整控制以下 JDBC 资源生命周期:
- 从连接池获取数据库连接(如有必要)。
- 将连接的自动提交关闭,开启数据库事务。
- 执行目标业务方法(可能含有多次数据操作)。
- 若业务成功,提交事务并释放连接。
- 若业务异常,根据规则决定回滚事务并释放连接。
- 在整个过程中,处理事务嵌套时的挂起与恢复。
这一切都由 TransactionInterceptor 在代理的拦截器链中完成。它本身实现 MethodInterceptor,不需要额外适配器。其默认 Order 值为 Ordered.LOWEST_PRECEDENCE(即 Integer.MAX_VALUE),在拦截器链的最末端执行,所以用户切面总是"包裹"在事务外部。
下文将逐步展示,这个"用户切面→事务切面→目标方法"的执行顺序,如何与 Spring 的 Order 机制协同工作。
主旨概括 :该序列图描绘了 TransactionInterceptor 在拦截器链末尾执行,以"外层用户切面、内层事务切面"的顺序包裹目标方法。
逐层分解 :客户端调用代理对象;代理首先执行用户自定义切面(Order 更小);用户切面内调用 proceed() 将控制权传递给事务拦截器;事务拦截器完成资源获取、事务开启、目标方法执行、提交/回滚和清理;之后控制返回用户切面;最终返回客户端。
设计原理 :将事务作为最内层拦截器,确保用户切面中的数据库操作(如有)均被事务覆盖;同时事务的边界也包含用户切面的异常处理,切面抛出的异常如符合规则也会触发回滚。LOWEST_PRECEDENCE 的设计正是遵循"基础组件应在执行链内侧"的原则。
工程联系与结论 :在真实项目中,如果我们需要在事务开启前执行某些操作(如路由数据源),可以将相关切面的 Order 调整为比事务拦截器更小(优先级更高);反之,若要确保逻辑在事务提交后执行,可以注册 TransactionSynchronization 而非增加切面顺序。
二、@EnableTransactionManagement 解析与基础设施注册
2.1 注解入口:TransactionManagementConfigurationSelector
在 Spring 配置类上标注 @EnableTransactionManagement,会触发一系列基础设施的注册。其内部逻辑如下:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
@Import 导入了 TransactionManagementConfigurationSelector,它会根据 mode 属性决定加载哪个配置类:
java
public class TransactionManagementConfigurationSelector
extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {TransactionManagementConfigUtils.
TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
对于默认的 PROXY 模式,会导入两个类:
AutoProxyRegistrar:向容器注册InfrastructureAdvisorAutoProxyCreator,使基础设施级 Advisor 自动生效。ProxyTransactionManagementConfiguration:直接注册事务拦截器及相关 Bean。
2.2 ProxyTransactionManagementConfiguration 注册的核心 Bean
这是一个 @Configuration 类,定义了三个核心 Bean:
(1)TransactionAttributeSource
它负责从被代理的类和方法上读取 @Transactional 属性,并将其封装为 TransactionAttribute 对象。典型的实现是 AnnotationTransactionAttributeSource,内部组合了 SpringTransactionAnnotationParser(解析 @Transactional)以及 JtaTransactionAnnotationParser 等。
(2)TransactionInterceptor
核心拦截器,构造函数注入 TransactionAttributeSource 和 TransactionManager(或 TransactionManager Bean,通过 PlatformTransactionManager 类型查找)。
java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(
TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
(3)BeanFactoryTransactionAttributeSourceAdvisor
这是一个 Advisor(通知器),负责将 TransactionInterceptor 与 Pointcut 绑定。其 Pointcut 由 TransactionAttributeSource 决定------如果方法或类上存在事务属性(即有 @Transactional),则该 Advisor 匹配成功,从而将 TransactionInterceptor 应用到目标代理上。
java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource) {
BeanFactoryTransactionAttributeSourceAdvisor advisor =
new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor(transactionAttributeSource));
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
值得注意的是,TransactionInterceptor 自身就是 Advice(实现了 MethodInterceptor),不需要像普通 Aspect 那样再用 Advice 适配。
2.3 事务基础设施注册流程图
主旨概括 :该流程图展示了从开启 @EnableTransactionManagement 到目标 Bean 被自动代理的完整 Bean 注册链路。
逐层分解 :注解通过 @Import 导入配置选择器;选择器根据代理模式加载 ProxyTransactionManagementConfiguration;该配置类依次注册属性源、拦截器和通知器;通知器将拦截器作为 Advice 绑定;InfrastructureAdvisorAutoProxyCreator 自动为匹配的 Bean 创建代理。
设计原理 :Spring 采用 @Import + ImportSelector 的方式,使注解能够根据模式选择不同的配置类,实现了代理模式和 AspectJ 模式的解耦。将事务基础设施 Bean 的 @Role 设为 ROLE_INFRASTRUCTURE,可与用户 Bean 区分,避免与普通业务 Bean 混淆。
工程联系与结论 :在纯 Spring 项目(非 Spring Boot)中,要启用声明式事务,必须显式添加 @EnableTransactionManagement。Spring Boot 的自动配置类 TransactionAutoConfiguration 实际上会为我们完成类似的工作,因此我们在启动类上通常无需再添加该注解。
2.4 内联示例:验证基础设施注册
下面是一个基于 Spring 5.x、纯 Java 配置的示例,用于验证基础设施 Bean 是否成功注册。
java
@Configuration
@EnableTransactionManagement
@ComponentScan("com.example.txdemo")
public class TxConfig {
@Bean
public DataSource dataSource() {
// 使用H2内存数据库
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
验证代码:
java
public class TxInfrastructureTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(TxConfig.class);
// 检查基础设施Bean
TransactionInterceptor interceptor =
ctx.getBean(TransactionInterceptor.class);
System.out.println("TransactionInterceptor: " + interceptor);
TransactionAttributeSource tas =
ctx.getBean(TransactionAttributeSource.class);
System.out.println("TransactionAttributeSource: " + tas);
BeanFactoryTransactionAttributeSourceAdvisor advisor =
ctx.getBean(BeanFactoryTransactionAttributeSourceAdvisor.class);
System.out.println("Advisor: " + advisor);
System.out.println("Advisor Advice: " + advisor.getAdvice());
ctx.close();
}
}
运行后控制台将打印三个基础设施实例,证明框架已自动装配。Advisor 的 Advice 就是 TransactionInterceptor 本身。
三、TransactionInterceptor 源码拆解
3.1 类层次与接口实现
TransactionInterceptor 的类继承结构如下:
java
TransactionInterceptor
extends TransactionAspectSupport
implements MethodInterceptor
TransactionAspectSupport 包含了事务拦截的核心模板逻辑,其中最重要的方法是 invokeWithinTransaction。
3.2 invoke 方法入口
java
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor {
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 获取目标类(可能是代理后的类)
Class<?> targetClass = (invocation.getThis() != null ?
AopUtils.getTargetClass(invocation.getThis()) : null);
// 委托给父类的模板方法
return invokeWithinTransaction(invocation.getMethod(), targetClass,
new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
}
invoke 方法的工作很简单:提取目标类,并将 invocation.proceed() 封装为回调 InvocationCallback,然后全部委派给 TransactionAspectSupport.invokeWithinTransaction。
3.3 invokeWithinTransaction 全链路拆解
这是事务 AOP 最核心的方法,位于 TransactionAspectSupport 中。我们对其进行逐段拆解:
java
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 1. 获取事务属性来源(TransactionAttributeSource)
TransactionAttributeSource tas = getTransactionAttributeSource();
// 2. 从方法或类上获取TransactionAttribute(可能是null)
final TransactionAttribute txAttr = (tas != null ?
tas.getTransactionAttribute(method, targetClass) : null);
// 3. 根据属性获取确定的事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 4. 切点标识(通常为方法全限定名),用于事务同步
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 分情况:CallbackPreferring 模式(编程式事务)或常规声明式
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 声明式事务标准路径
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// 执行目标方法(或下一个拦截器)
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 异常处理:根据回滚规则决定提交或回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 正常返回后提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
// 编程式事务路径(通常不用于@Transactional),略...
}
}
逐段解读:
- 获取
TransactionAttribute:这是@Transactional注解解析后的结果,包含了传播行为、隔离级别、超时、readOnly 以及回滚规则等全部定义。如果方法上没有事务属性(没有@Transactional),则txAttr为null,此时createTransactionIfNecessary将不会创建事务,直接执行目标方法。 - 确定事务管理器 :
determineTransactionManager会优先使用@Transactional的transactionManager(或value)属性指定的 Bean 名称,否则使用容器中默认的PlatformTransactionManager。 - 创建事务(如有) :
createTransactionIfNecessary内部调用tm.getTransaction(txAttr),得到TransactionStatus,并封装为TransactionInfo绑定到当前线程。如果txAttr为 null,则TransactionStatus为空,表示无事务。 - 执行目标 :
invocation.proceedWithInvocation()真实调用业务方法。 - 异常回滚 :在
catch块中调用completeTransactionAfterThrowing,该方法内部使用txAttr.rollbackOn(ex)判断异常是否应当触发回滚,然后调用tm.rollback(txInfo.getTransactionStatus())或tm.commit(txInfo.getTransactionStatus())。 - 正常提交 :若无异常,调用
commitTransactionAfterReturning,最终调用tm.commit。 - 清理 :
finally中调用cleanupTransactionInfo解绑线程绑定的TransactionInfo,并恢复挂起的事务资源。
3.4 createTransactionIfNecessary 细节
java
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr,
final String joinpointIdentification) {
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr); // 关键调用
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
prepareTransactionInfo 创建 TransactionInfo 并绑定到 TransactionAspectSupport 的 ThreadLocal<TransactionInfo> 中,同时将 TransactionSynchronizationManager 的资源同步到此事务信息中。
3.5 序列图:TransactionInterceptor 内部调用
主旨概括 :序列图展示了 invokeWithinTransaction 从获取事务属性到最终清理的全流程,体现了其作为事务拦截核心模板的角色。
逐层分解 :代理调用 invoke,进入 invokeWithinTransaction;首先读取事务属性,如果为非空则获取事务管理器并开启事务;执行业务方法;根据异常类型及回滚规则决定事务最终状态;最后清理线程绑定的资源。
设计原理 :Spring 将事务的开启、提交、回滚、资源清理封装在该模板方法中,子类或拦截器只需提供 TransactionAttributeSource 和 TransactionManager 即可复用全部逻辑。TransactionInfo 的线程绑定保证了嵌套事务传播时,外层能正确感知内层的状态。
工程联系与结论 :当事务传播行为为 REQUIRES_NEW 时,getTransaction 内部会挂起外层事务,挂起的信息保存在 TransactionInfo 中。cleanupTransactionInfo 在 finally 中负责恢复挂起的事务,这确保了即使内层异常,外层事务也能正确恢复并继续。
3.6 内联示例:观察拦截器调用链
利用 SLF4J 日志观察调用过程(需配置 DataSourceTransactionManager 的日志为 DEBUG 级别)。我们写一个 Service 并配置日志:
java
@Service
public class OrderService {
@Transactional
public void placeOrder() {
// 执行数据操作
System.out.println("Executing placeOrder...");
// 可以故意抛RuntimeException观察回滚
}
}
运行并观察日志输出:
ini
DEBUG o.s.j.d.DataSourceTransactionManager - Creating new transaction with name [com.example.OrderService.placeOrder]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.j.d.DataSourceTransactionManager - Acquired Connection [...]
DEBUG o.s.j.d.DataSourceTransactionManager - Switching JDBC Connection [...] to manual commit
...
DEBUG o.s.j.d.DataSourceTransactionManager - Initiating transaction commit
DEBUG o.s.j.d.DataSourceTransactionManager - Committing JDBC transaction on Connection [...]
此日志对应 TransactionInterceptor 调用的 getTransaction、commit 等阶段,清晰展示了事务生命周期。
四、事务管理器调用链:getTransaction、commit、rollback
4.1 AbstractPlatformTransactionManager 骨架
PlatformTransactionManager 定义了事务管理的三个核心方法:getTransaction(TransactionDefinition)、commit(TransactionStatus)、rollback(TransactionStatus)。AbstractPlatformTransactionManager 提供了这些方法的模板实现,并定义了 doGetTransaction、doCommit、doRollback 等抽象方法供具体数据源事务管理器实现(如 DataSourceTransactionManager)。
4.2 getTransaction:传播行为的处理中枢(结构总览)
getTransaction 是传播行为决策的入口,主要流程如下:
java
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 1. 获取当前事务对象
Object transaction = doGetTransaction();
// 2. 如果已存在事务,根据传播行为分支处理
if (isExistingTransaction(transaction)) {
return handleExistingTransaction(def, transaction, debugTx);
}
// 3. 无事务时的分支(根据传播行为创建、挂起或抛异常等)
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(...);
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
return startTransaction(def, transaction, debugTx, suspendedResources);
} catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// SUPPORTS, NOT_SUPPORTED, NEVER 等情况返回空事务状态
...
}
}
关键点 :isExistingTransaction 判定当前线程是否已绑定事务资源;如果存在,则调用 handleExistingTransaction 进行传播行为处理(该部分将在第五部分详细展开)。本节仅聚焦于 commit 与 rollback 的骨架。
4.3 commit 流程:嵌套与 rollbackOnly 处理
java
public final void commit(TransactionStatus status) throws TransactionException {
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
processCommit 内的关键步骤:
- 触发
TransactionSynchronization.beforeCommit/beforeCompletion - 根据事务状态决定:是释放保存点(嵌套),还是执行
doCommit(新事务) - 最后触发
afterCommit与afterCompletion
4.4 rollback 流程
java
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
status.rollbackToHeldSavepoint();
} else if (status.isNewTransaction()) {
doRollback(status);
} else {
status.setRollbackOnly(); // 参与外部事务,仅打标记
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
} finally {
cleanupAfterCompletion(status);
}
}
设计意图 :commit 和 rollback 均为模板方法,最终执行的动作由具体的事务管理器实现(例如 DataSourceTransactionManager 调用 Connection.commit() 或 Connection.rollback())。传播行为的影响已经由 getTransaction 阶段确定的事务对象和状态承载,commit/rollback 只需据此执行。
五、传播行为分支深入
在第四部分我们看到 getTransaction 将决策委托给 handleExistingTransaction。传播行为的差异在存在事务时最为显著。此部分独立拆解各种传播行为的源码分支及挂起/保存点机制。
5.1 handleExistingTransaction 源码拆解
java
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugTx) {
// 1. PROPAGATION_NEVER:已存在事务则抛异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// 2. PROPAGATION_NOT_SUPPORTED:挂起当前事务,以无事务方式执行
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, false, newSynchronization,
debugTx, suspendedResources);
}
// 3. PROPAGATION_REQUIRES_NEW:挂起当前事务,新建事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugTx, suspendedResources);
} catch (RuntimeException | Error ex) {
resume(transaction, suspendedResources);
throw ex;
}
}
// 4. PROPAGATION_NESTED:嵌套事务(需底层支持保存点)
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions");
}
if (useSavepointForNestedTransaction()) {
// 在现有事务中创建保存点
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugTx, null);
status.createAndHoldSavepoint();
return status;
} else {
// 若不使用保存点,则类似 REQUIRES_NEW
return startTransaction(definition, transaction, debugTx, null);
}
}
// 5. PROPAGATION_REQUIRED, SUPPORTS, MANDATORY:复用当前事务
if (isValidateExistingTransaction()) {
validateExistingTransaction(definition, transaction);
}
return prepareTransactionStatus(definition, transaction, false, false, debugTx, null);
}
逐段解读:
PROPAGATION_NEVER/NOT_SUPPORTED:一个抛异常,一个挂起事务。挂起通过suspend方法将当前连接资源和同步列表暂存到SuspendedResourcesHolder中,并解绑线程。PROPAGATION_REQUIRES_NEW:挂起现有事务后,调用startTransaction创建全新事务(通常是一个新的数据库连接)。挂起操作必须在 try 中执行,若创建失败需恢复原事务。PROPAGATION_NESTED:检查是否允许嵌套事务,并优先使用保存点(createAndHoldSavepoint),若数据库不支持保存点,则默认退化为REQUIRES_NEW或直接抛异常(取决于配置)。- 其余行为(
REQUIRED、SUPPORTS、MANDATORY):不会挂起或新建,而是验证(如隔离级别兼容性)后复用。
5.2 挂起事务与恢复的底层实现
java
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
List<TransactionSynchronization> suspendedSynchronizations =
TransactionSynchronizationManager.getSynchronizations();
// 遍历并暂停每个TransactionSynchronization,然后清理线程同步列表
for (TransactionSynchronization synchronization : suspendedSynchronizations) {
synchronization.suspend();
}
TransactionSynchronizationManager.clearSynchronization();
}
// 解绑并保存资源
Map<Object, Object> resources = TransactionSynchronizationManager.getResourceMap();
// ... 遍历 resources,对每个持有者执行 suspend,然后解绑
return new SuspendedResourcesHolder(resources, suspendedSynchronizations, ...);
}
挂起时:
- 调用每个已注册的
TransactionSynchronization.suspend(),给它们一个清理机会。 - 将当前线程绑定的所有资源(如
ConnectionHolder)从TransactionSynchronizationManager中移除,暂存在SuspendedResourcesHolder中。 - 恢复时 (
resume),重新调用bindResource绑定相应的资源,并将同步列表加回。
这种挂起/恢复机制是 REQUIRES_NEW 和 NOT_SUPPORTED 实现独立事务/非事务执行的基础。
5.3 三种核心传播行为对比图
主旨概括:该图突出在已有事务时,三种"新建"行为(REQUIRED、REQUIRES_NEW、NESTED)的本质区别:REQUIRED 不新建,而是复用;REQUIRES_NEW 挂起原有事务并新建独立事务;NESTED 在当前连接上创建保存点。
逐层分解 :REQUIRED 直接返回已存在的事务状态;REQUIRES_NEW 执行挂起流程后启动一个全新的物理事务(往往是新的连接);NESTED 仅创建保存点,不改变物理连接,内层回滚不影响外层提交。
设计原理 :Spring 通过挂起机制保证 REQUIRES_NEW 可以在同一线程内启动完全独立的事务,但要求连接池能够提供足够的连接。NESTED 则利用了 JDBC 3.0 的保存点特性,在单个连接内实现子事务部分回滚,开销更小。
工程联系与结论 :REQUIRES_NEW 与 NESTED 的选择直接影响数据库锁和连接资源。例如,使用 REQUIRES_NEW 可避免外层回滚影响内层审计日志,但需要额外的连接;使用 NESTED 则必须在同一个连接内,可能受数据库锁和隔离级别的限制。
5.4 内联示例:多种传播行为嵌套调用观察
下面示例演示 REQUIRED、REQUIRES_NEW、NESTED 三种传播行为的日志差异。
java
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional(propagation = Propagation.REQUIRED)
public void outerRequired() {
System.out.println("--- outer REQUIRED start ---");
innerService.innerRequiresNew(); // 内层REQUIRES_NEW
innerService.innerNested(); // 内层NESTED
System.out.println("--- outer REQUIRED end ---");
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerRequiresNew() {
System.out.println(" >>> inner REQUIRES_NEW executed");
}
@Transactional(propagation = Propagation.NESTED)
public void innerNested() {
System.out.println(" >>> inner NESTED executed");
}
}
在日志中可观察到:
- 外层事务
outerRequired开启一个事务。 - 调用
innerRequiresNew时,日志显示Suspending current transaction, creating new transaction,结束后Resuming suspended transaction。 - 调用
innerNested时,日志显示Creating savepoint,提交时则释放保存点。
可通过 DEBUG 级别日志实际运行验证。
六、回滚规则与回滚判断机制
6.1 回滚规则的来源:@Transactional 属性
@Transactional 的 rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName 这些属性在被解析时,会生成一组 RollbackRule 对象。
SpringTransactionAnnotationParser 解析 @Transactional 并填充 RuleBasedTransactionAttribute:
java
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
// 设置传播行为、隔离级别等...
// 解析回滚规则
List<RollbackRule> rollbackRules = new ArrayList<>();
for (Class<?> clazz : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(clazz));
}
for (String className : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(className));
}
for (Class<?> clazz : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(clazz));
}
for (String className : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(className));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
6.2 RollbackRule 继承体系与匹配逻辑
RollbackRule 是抽象类,有两个具体实现:
RollbackRuleAttribute:匹配的异常应触发回滚。NoRollbackRuleAttribute:匹配的异常不触发回滚。
匹配方法 getDepth(Throwable ex) 返回异常与规则中定义的异常类的"深度"(即继承层次差),深度越小越匹配。rollbackOn 方法综合判断:
java
public boolean rollbackOn(Throwable ex) {
RollbackRule winner = null;
int deepest = Integer.MAX_VALUE;
// 遍历所有规则,找出最匹配的(深度最小)
for (RollbackRule rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
// 如果没有自定义规则,走默认规则:RuntimeException或Error回滚
if (winner == null) {
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
默认行为(DefaultTransactionAttribute.rollbackOn):
java
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
6.3 回滚规则匹配流程图
主旨概括 :该流程图描述了从捕获异常到决定回滚或提交的判断路径:优先匹配自定义规则,无自定义规则时使用默认的 RuntimeException/Error 判断。
逐层分解 :rollbackOn 计算每个规则的匹配深度,选中最深匹配的规则;如果是 NoRollbackRule,即使异常属于默认回滚类型也不回滚;反之,如果是 RollbackRule 则触发回滚。
设计原理 :采用深度计算而非简单 isAssignableFrom 精确匹配,可正确处理父子异常关系。如 rollbackFor = BusinessException.class 时,其子类 PaymentException 也会回滚,除非有更精确的 noRollbackFor = PaymentException.class 覆盖。
工程联系与结论 :许多团队在事务切面上采用自定义元注解(如 @ServiceTx),并统一设置 rollbackFor = Exception.class,以避免 checked 异常不回滚的"陷阱"。理解该机制有助于在特定场景下精细控制(例如异步事件发送只在特定业务异常回滚后执行)。
6.4 内联示例:自定义回滚规则验证
java
class BusinessException extends RuntimeException {}
class PaymentException extends BusinessException {}
@Service
public class PaymentService {
@Transactional(rollbackFor = BusinessException.class,
noRollbackFor = PaymentException.class)
public void processPayment() throws PaymentException {
// 若抛BusinessException会回滚,抛PaymentException不回滚
throw new PaymentException("支付异常但不回滚");
}
}
编写测试,分别抛出 BusinessException 和 PaymentException,通过 SQL 日志或数据库状态验证回滚与否。
七、事务同步机制(TransactionSynchronization)
7.1 TransactionSynchronizationManager 的职责
TransactionSynchronizationManager 是事务同步的核心协调器,它在每个线程中维护两组状态:
- 资源映射
Map<Object, Object> resources:Key 为资源标识(通常是 DataSource),Value 为资源持有者(如ConnectionHolder)。通过bindResource和unbindResource管理。 - 同步回调列表
List<TransactionSynchronization> synchronizations:通过registerSynchronization注册,在事务各阶段触发。
7.2 TransactionSynchronization 回调时机
接口定义了以下回调:
beforeCommit(boolean readOnly):提交前调用(不适用于回滚)。beforeCompletion():事务完成前调用(提交或回滚前)。afterCommit():提交成功后调用。afterCompletion(int status):事务完成后(提交或回滚后),携带状态STATUS_COMMITTED、STATUS_ROLLED_BACK或STATUS_UNKNOWN。suspend()/resume():事务挂起/恢复时专用。
在 AbstractPlatformTransactionManager 的 processCommit 和 processRollback 中,可以看到明确的调用顺序:
提交场景:
triggerBeforeCommit(遍历 synchronizations,调用 beforeCommit)triggerBeforeCompletion- 实际
doCommit triggerAfterCommittriggerAfterCompletion(STATUS_COMMITTED)
回滚场景:
triggerBeforeCompletion- 实际
doRollback或设置回滚标记 triggerAfterCompletion(STATUS_ROLLED_BACK)
7.3 回调序列图
主旨概括:该序列图展示了事务同步回调在提交/回滚流程中的准确位置,为依赖事务后期操作的逻辑提供了可靠挂载点。
逐层分解 :提交前依次触发 beforeCommit 和 beforeCompletion,完成数据库操作后触发 afterCommit(仅提交成功)和 afterCompletion;回滚时仅触发 beforeCompletion 和 afterCompletion。
设计原理 :beforeCommit 与 afterCommit 的分离使得像发送消息、清除缓存等操作可以精确在事务提交前或后执行,避免数据不一致。afterCompletion 是最终的清理点,即使提交或回滚失败,这里也应该做防御式资源释放。
工程联系与结论 :在 afterCompletion 中绝对不要抛出异常,因为它会中断 cleanupAfterCompletion 的正常执行,导致数据库连接无法归还连接池,引发严重生产事故。
7.4 内联示例:注册事务同步实现缓存更新
java
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 业务操作
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 发送消息或更新缓存,仅当事务提交成功执行
System.out.println("事务提交成功,更新缓存");
}
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
System.out.println("事务回滚,执行补偿");
}
// 永远不要抛异常
}
});
}
}
八、生产事故排查专题
生产事故往往源于对机制理解的微小偏差。以下三个案例均来自真实场景的抽象。
案例1:REQUIRES_NEW 嵌套事务失效,内层回滚波及外层数据
事故现象 :订单服务 createOrder 调用积分服务 addPoints,积分服务标记为 REQUIRES_NEW。积分服务内部抛异常,期望只回滚积分更新,但生产中发现订单也被回滚。
排查思路:
- 检查日志中
DataSourceTransactionManager的事务创建和挂起操作。 - 观察
TransactionSynchronizationManager.getResource,发现内层事务复用了与外层相同的数据库连接(同一个ConnectionHolder)。 - 连接池配置为
HikariCP,最大连接数10,检查当时连接占用情况,发现连接池耗尽,内层等待外层连接释放,实际退化为同一个事务。
根因分析 :Spring 的 REQUIRES_NEW 要求挂起外层事务并获取新连接。如果连接池无空闲连接,且超时设置不合理,DataSourceTransactionManager 可能无法获得新连接,从而在某种配置或 BUG 下回退为复用。在本案例中,连接池大小设置过小,高并发下导致死锁或等待超时,内层方法实际未独立开启事务。
解决方案:合理配置连接池大小,确保至少可以容纳最大嵌套层数的连接数。同时,添加事务监控,针对 REQUIRES_NEW 场景检测实际连接是否为新建。
最佳实践 :配置连接池 maximumPoolSize ≥ 业务并发度 * 最大嵌套深度。对关键事务添加自定义 TransactionSynchronization 监听,验证事务边界。
案例2:checked 异常未触发回滚,对账发现数据不一致
事故现象 :文件导入服务在解析文件时抛出 IOException,业务逻辑规定此时应回滚全部导入数据,然而数据库中出现部分成功记录。
排查思路 :查看异常堆栈,确认异常为 IOException。发现方法上的 @Transactional 未指定 rollbackFor。日志显示事务正常提交。
根因分析 :Spring 默认只对 RuntimeException 和 Error 进行回滚,IOException 属于 checked 异常,所以被直接提交。源码 DefaultTransactionAttribute.rollbackOn 中明确排除了非运行时异常。
解决方案 :修改注解为 @Transactional(rollbackFor = Exception.class)。
最佳实践 :制定团队编码规范,要求所有 @Transactional 显式指定 rollbackFor = Exception.class,或封装自定义元注解(如 @ServiceTransactional)统一管理。使用 Checkstyle/PMD 等工具检查。
案例3:事务同步回调异常导致连接泄漏
事故现象:应用运行一段时间后,数据库连接池爆满,所有请求无法获取连接,系统假死。
排查思路 :线程 dump 显示大量线程阻塞在 HikariPool.getConnection()。查看连接池监控,活跃连接持续升高,无归还。审查最近代码变更,发现新增了一个 TransactionSynchronization.afterCompletion 实现,其中调用了外部 HTTP 服务,该服务偶尔超时抛 RuntimeException。
根因分析 :afterCompletion 中的 RuntimeException 导致 AbstractPlatformTransactionManager.cleanupAfterCompletion 没能执行,连接未被释放。连接池最终耗尽。
解决方案 :立即回滚代码,在 afterCompletion 中添加 try-catch,确保异常不被抛出。将 HTTP 调用移至异步线程或改为非核心逻辑。
最佳实践 :afterCompletion 必须防御式编程,包裹 try-catch 并记录日志。核心清理逻辑(如归还连接)不应依赖用户代码的正确性,但目前的 Spring 实现决定了我们必须负责。
九、面试高频专题
(以下内容独立于正文,专为面试场景设计)
-
@EnableTransactionManagement 注解做了什么?它注册了哪些核心 Bean?
标准回答 :它通过@Import导入TransactionManagementConfigurationSelector,根据模式加载ProxyTransactionManagementConfiguration,注册了TransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor三个核心 Bean,并启用基础设施级自动代理。
追问:TransactionAttributeSource的实现类有哪些?如何自定义?- 如果不用
@EnableTransactionManagement,手动开启事务代理需要哪些步骤? AdviceMode.ASPECTJ模式下的事务是如何织入的,与代理模式有何区别?
加分回答 :可结合TransactionManagementConfigUtils讲解 AspectJ 模式下的配置类路径。
-
TransactionInterceptor 如何实现 MethodInterceptor?它的 invoke 方法内部流程怎样?
标准回答 :它继承TransactionAspectSupport并实现MethodInterceptor,invoke方法调用父类的invokeWithinTransaction,在该方法中获取事务属性、确定事务管理器、开启事务、执行业务、决定提交或回滚、清理资源。
追问:InvocationCallback的设计好处是什么?- 如果目标方法没有
@Transactional,拦截器还执行吗? invokeWithinTransaction中completeTransactionAfterThrowing具体是如何判断回滚的?
加分回答 :可提及该方法还支持CallbackPreferringPlatformTransactionManager的特殊路径。
-
TransactionAttributeSource 和 TransactionAttribute 的作用是什么?
标准回答 :TransactionAttributeSource负责从方法/类上提取事务配置并生成TransactionAttribute对象,后者封装了传播行为、隔离级别、超时、回滚规则等。
追问:- 如何实现一个自定义的
TransactionAttributeSource? @Transactional注解的属性解析在哪个类完成?- 多数据源场景下,
TransactionAttribute如何帮助选择事务管理器?
加分回答 :AnnotationTransactionAttributeSource内部实现了基于@Transactional、@javax.transaction.Transactional等多种注解的解析。
- 如何实现一个自定义的
-
AbstractPlatformTransactionManager.getTransaction 方法处理了哪些传播行为?各自的逻辑差异是什么?
标准回答 :处理 REQUIRED、REQUIRES_NEW、NESTED、SUPPORTS、NOT_SUPPORTED、NEVER、MANDATORY。REQUIRED 复用或新建;REQUIRES_NEW 挂起并新建;NESTED 创建保存点;SUPPORTS 可运行在事务或非事务;NOT_SUPPORTED 挂起;NEVER 抛异常;MANDATORY 必须存在事务。
追问:isExistingTransaction的判断依据是什么?- 挂起事务具体做了哪些操作?
- 为什么
NESTED只能在 JDBC 环境中使用?
加分回答 :结合源码解释SuspendedResourcesHolder的结构和恢复流程。
-
PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 在源码中是如何实现的?挂起事务具体做了什么?
标准回答 :在getTransaction中,若存在事务,REQUIRED 直接返回;REQUIRES_NEW 调用suspend挂起当前事务(解绑资源和同步列表),然后startTransaction新建事务。挂起操作将原有ConnectionHolder和TransactionSynchronization暂存,以便后续恢复。
追问:- 挂起后外层的
TransactionSynchronization还会触发吗? - 如果连接池不够,
REQUIRES_NEW会怎样? REQUIRES_NEW内层提交后,外层还能看到内层修改吗?
加分回答 :视隔离级别而定,通常用REQUIRES_NEW是为了独立提交,需要确保连接隔离。
- 挂起后外层的
-
PROPAGATION_NESTED 与 REQUIRES_NEW 的区别是什么?NESTED 的底层实现依赖什么?
标准回答 :NESTED 使用数据库保存点实现,内层回滚只回到保存点,外层提交时统一提交;REQUIRES_NEW 使用独立连接和独立事务。
追问:- 哪些数据库/驱动支持保存点?
- NESTED 下,内层回滚后外层还能继续提交吗?
- 如果数据库不支持保存点,Spring 如何处理?
加分回答 :JdbcTransactionObjectSupport负责保存点管理,DataSourceTransactionManager覆盖了相应方法。
-
声明式事务的回滚规则是如何判断的?Spring 默认对哪些异常回滚?为什么?
标准回答 :TransactionAttribute.rollbackOn遍历RollbackRule列表,取最匹配者。默认实现只对RuntimeException和Error回滚。这源于 EJB CMT 设计,Spring 为兼容而沿用。
追问:- 如果异常链中的有回滚异常,会回滚吗?
- 自定义
RollbackRule的深度计算原理是什么? - 如何全局更改默认回滚行为?
加分回答 :可扩展DefaultTransactionAttribute覆盖rollbackOn,并注册自定义TransactionAttributeSource。
-
TransactionSynchronizationManager 的作用是什么?它如何绑定线程资源?
标准回答 :管理当前线程的事务资源和同步回调。bindResource将 DataSource→ConnectionHolder 存入ThreadLocal的 Map,getResource取出。
追问:TransactionSynchronizationManager的生命周期是否依赖 Spring 容器?- 多个数据源时如何绑定不同资源?
- 如何主动解除绑定?
加分回答:它是静态方法驱动的,不依赖容器,但事务管理器会正确调用。
-
TransactionSynchronization 的各个回调方法在事务生命周期中的执行顺序是什么?
标准回答 :提交场景:beforeCommit→beforeCompletion→ 实际doCommit→afterCommit→afterCompletion;回滚场景:beforeCompletion→ 实际回滚 →afterCompletion。
追问:beforeCommit中能修改数据吗?- 如果
afterCompletion抛异常会怎样? @TransactionalEventListener与它们的关系?
加分回答 :@TransactionalEventListener基于TransactionSynchronization实现,默认在afterCommit阶段触发。
-
事务拦截器的 Order 值是多少?如果用户自定义切面与事务切面冲突,如何控制顺序?
标准回答 :默认为Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)。可在@EnableTransactionManagement的order属性调整,或修改 Advisor 的 Order。
追问:- 如果用户切面 Order 也是
LOWEST_PRECEDENCE,顺序如何确定? - 为何通常需要让用户切面在外层?
- 若将事务切面顺序提前可能引发什么问题?
加分回答:顺序的决定影响异常捕获和资源状态,提前事务可能导致用户切面中数据操作不在同一事务内。
- 如果用户切面 Order 也是
-
在同一个类中,一个没有 @Transactional 的方法调用有 @Transactional 的方法,事务会生效吗?
标准回答 :不会,因为调用是通过this而非代理对象,绕过了 AOP 拦截。这涉及 self-invocation 失效,详细场景及解决方案将在后续《@Transactional 失效场景大全》中展开。
追问:- 如何通过 AopContext 解决?
- 使用 CGLIB 代理能解决吗?
- 结构上如何避免?
加分回答:本问题即为失效典型,下篇将详述。
-
多线程环境下,@Transactional 能传播事务吗?为什么?
标准回答 :不能,因为事务资源(Connection)和同步状态都通过ThreadLocal绑定,跨线程后无法共享。
追问:- 如何在多线程中实现类似事务传播?
- Spring 有提供任何跨线程的事务支持吗?
- 分布式事务与此有何关联?
加分回答 :DelegatingTransactionManager等也无法跨线程,分布式事务才是解决跨服务/线程的方案。
-
为什么某些 checked 异常不会触发回滚?这是 bug 还是有意设计?底层代码是如何实现的?
标准回答 :是有意设计,沿袭了 EJB 的传统。DefaultTransactionAttribute.rollbackOn仅对RuntimeException和Error返回 true。
追问:- 是否推荐修改默认行为为全部异常回滚?
- 如何处理
SQLException这类 checked 异常? - 能否通过其他方式(如 Bean 后处理器)全局覆盖?
加分回答 :可以自定义TransactionAttributeSource返回修改了rollbackOn的TransactionAttribute。
-
TransactionAspectSupport.invokeWithinTransaction 中的 invokeCallback 做了什么?目标方法的返回值如何被处理?
标准回答 :invokeCallback是一个函数式接口InvocationCallback,其唯一方法proceedWithInvocation调用了MethodInvocation.proceed()。返回值直接传递,事务拦截器不做修改。
追问:- 如果目标方法返回
void,如何处理? - 返回值是否会影响事务提交?
- 能否在拦截器中修改返回值?
加分回答 :可以包装回调,对返回值进行后处理,但仍推荐使用@Around切面。
- 如果目标方法返回
-
(系统设计题)设计一个分布式事务追踪平台,需要记录每个事务的开始、提交、回滚时间,并关联事务嵌套关系。利用 TransactionSynchronization + MDC 实现事务上下文的传递,并讨论在 REQUIRES_NEW 场景下如何正确处理事务父子关系。
标准回答 :利用TransactionSynchronization的beforeCommit/afterCompletion记录事件,同时通过TransactionSynchronizationManager的resource持有TransactionInfo。可以将跟踪 ID 放入 MDC,在事务挂起/恢复时也保存和恢复 MDC 值。
追问:- 如何生成全局唯一事务 ID?
- 在
REQUIRES_NEW挂起/恢复时,如何处理跟踪信息的传递? - 如何避免大量事件记录对性能的影响?
加分回答 :自定义TransactionSynchronization在suspend和resume中保存/恢复 MDC 上下文,使用SuspendedResourcesHolder传递跟踪信息;异步写入日志或发送到 Kafka,避免阻塞业务线程。
事务传播行为速查表
| 传播行为 | 描述 | 当前有事务 | 当前无事务 | 典型场景 | 源码分支位置 |
|---|---|---|---|---|---|
| REQUIRED | 默认,支持当前事务,无则新建 | 复用 | 新建 | 大多数业务方法 | getTransaction 无事务分支直接 startTransaction;有事务分支 handleExistingTransaction 中无特殊处理直接返回 |
| REQUIRES_NEW | 新建事务,挂起当前事务 | 挂起并新建 | 新建 | 独立日志、审计 | handleExistingTransaction → suspend → startTransaction |
| NESTED | 嵌套事务,使用保存点 | 创建保存点 | 新建 | 子任务失败不干扰主任务 | handleExistingTransaction → 检查 savepointAllowed → 创建保存点 |
| SUPPORTS | 支持当前事务,无则非事务运行 | 复用 | 非事务 | 可选的只读查询 | getTransaction 无分支返回空状态;有分支直接复用 |
| NOT_SUPPORTED | 非事务运行,挂起当前事务 | 挂起 | 非事务 | 不需要事务的耗时操作 | handleExistingTransaction → suspend |
| MANDATORY | 必须在事务内,无则抛异常 | 复用 | 抛异常 | 强制要求在事务内运行 | 无事务时 getTransaction 直接抛异常 |
| NEVER | 必须在非事务下运行,有事务抛异常 | 抛异常 | 非事务 | 不允许事务的原生 JDBC | handleExistingTransaction 直接抛异常 |
延伸阅读
- 《Expert One-on-One J2EE Development without EJB》Rod Johnson(第 8、9 章),声明式事务的思想源头
- 《Spring 揭秘》王福强,事务章节详细剖析
- Spring Framework 官方文档 "Transaction Management" (docs.spring.io/spring-fram...)
- 《Java Transaction Design Strategies》Mark Richards,深入比较事务模式
- Spring 源码分析系列博客:
TransactionInterceptor与AbstractPlatformTransactionManager解析(社区经典)
结语 :本文作为数据访问与事务深度系列的核心枢纽,将 AOP 的通用代理机制与 @Transactional 的具体实现精密咬合。从 TransactionInterceptor 的一行 invoke,到 AbstractPlatformTransactionManager 的挂起、保存点创建与同步回调,我们看到:所谓"声明式"背后,是一条严密、可扩展且经过十数年生产考验的代码链。理解其内部运作,不仅是为了通过面试,更是为了在生产系统中避免那些看似神秘的事务失效与资源泄漏。