Spring Framework 7.0.9 深度剖析-10-声明式事务
10.1 本章导读
声明式事务是 Spring 框架最具标志性的能力之一:业务代码上只需要一个 @Transactional 注解,方法前后的事务开启、提交、回滚、挂起、恢复、资源绑定与同步回调就全部由容器接管。这种"零侵入"的体验背后,是一整套建立在 AOP 代理、模板方法模式、ThreadLocal 资源绑定和策略模式之上的复杂机制。本章以 Spring Framework 7.0.9 仓库实际源码为准,逐层拆解这套机制的装配链路与运行时行为。
本章要回答的核心问题包括:
@EnableTransactionManagement到底往容器里注册了哪些 bean?它们之间如何协作形成"切点—通知—拦截器"的 AOP 三元组?- 一个标注了
@Transactional的服务方法被调用时,从代理对象进入到事务真正 commit,中间经过了哪些方法?每个方法承担什么职责? - 七种传播行为(
REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED)在AbstractPlatformTransactionManager.getTransaction中是如何被分支处理的?挂起与恢复具体做了什么? @Transactional(rollbackFor = ...)配置的回滚规则是如何在异常抛出后被匹配的?为什么默认只对RuntimeException和Error回滚,而对 checked 异常不回滚?- JDBC 的
Connection是如何被绑定到当前线程、又如何在事务结束后被归还连接池的?TransactionSynchronization的beforeCommit、afterCommit、afterCompletion分别在什么时机被触发? - 为什么"自调用"失效?为什么非 public 方法上的
@Transactional不生效?为什么 catch 掉异常后事务照样回滚?
本章涉及的源码主要位于 spring-tx 模块(spring-tx/src/main/java/org/springframework/transaction/ 及其子包 annotation、interceptor、support),以及 spring-jdbc 模块中的 DataSourceTransactionManager 与 DataSourceUtils。读者需要具备的前置知识包括:Spring AOP 的代理机制(JDK 动态代理与 CGLIB)、@Import 选择性装配的原理、模板方法设计模式,以及 JDBC Connection 的 autoCommit、setSavepoint、commit/rollback 语义。
读完本章,你将能够:不借助任何外部资料,仅凭断点就能把一个 @Transactional 方法的完整事务调用栈从头跟到尾;能够解释 REQUIRES_NEW 与 NESTED 在源码层面的本质区别;能够定位"事务不生效"类问题的根因;并能够基于 TransactionSynchronization 写出"事务提交后再发消息"这类生产级扩展。
10.2 核心概念
10.2.1 事务抽象的三个基石:TransactionManager、TransactionDefinition、TransactionStatus
Spring 事务抽象的设计哲学是:把"事务管理器"、"事务属性定义"、"事务运行状态"三个维度解耦,分别用三个接口表示。这种解耦使得同一个 PlatformTransactionManager(例如 DataSourceTransactionManager)可以服务于任何传播行为、任何隔离级别的事务定义;而声明式事务解析出的 TransactionAttribute 与编程式事务使用的 TransactionDefinition 是同一套体系。
TransactionManager 是一个纯标记接口,位于 spring-tx/src/main/java/org/springframework/transaction/TransactionManager.java。7.0.9 中它的完整内容如下:
public interface TransactionManager {
}
这个接口自 5.2 引入,目的是把命令式(imperative)的 PlatformTransactionManager 与响应式(reactive)的 ReactiveTransactionManager 纳入同一个类型体系,方便在配置类中按类型注入。它本身不声明任何方法,仅仅是一个"这是事务管理器"的标记。
PlatformTransactionManager 继承自 TransactionManager,是命令式事务的核心接口,位于 spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java。它只声明了三个方法:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
三个方法各司其职:
getTransaction(TransactionDefinition def):根据传播行为"返回当前活动事务或新建一个事务",返回的TransactionStatus代表当前事务的运行状态。隔离级别、超时等参数只对新建事务生效,参与已有事务时被忽略——这一点接口注释明确说明。commit(TransactionStatus status):提交给定事务。接口注释特别强调:如果事务被标记为 rollback-only,commit 内部会转而执行回滚;如果当前事务是参与外部事务的(非新建),commit 会被"省略"以保证外层事务统一提交;如果挂起了之前的事务,commit 完成后要恢复之前的事务。rollback(TransactionStatus status):回滚。同样,参与外部事务时只是把外部事务标记为 rollback-only,而不是真正回滚物理连接。
值得注意的是接口注释里的一条硬性约束:"Do not call rollback on a transaction if commit threw an exception."——commit 抛异常时事务已经被清理完毕,再调 rollback 会抛 IllegalTransactionStateException。这条约束在 AbstractPlatformTransactionManager 的 commit 方法中通过 status.isCompleted() 检查落地。
TransactionDefinition 描述"一个事务长什么样",位于 spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java。它定义了七个传播行为常量和五个隔离级别常量,以及超时、只读、名称等属性。7.0.9 中传播行为常量依次为:
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
隔离级别常量与 java.sql.Connection 中的隔离级别常量数值一一对应:
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
TransactionDefinition 还通过 default 方法给出了属性默认值:getPropagationBehavior() 默认返回 PROPAGATION_REQUIRED,getIsolationLevel() 默认返回 ISOLATION_DEFAULT,getTimeout() 默认返回 TIMEOUT_DEFAULT(-1),isReadOnly() 默认 false,getName() 默认 null。这种用接口 default 方法提供默认值的做法,让实现类只需覆盖有差异的属性。
TransactionStatus 代表"一个事务运行时的句柄",位于 spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java。它继承了 TransactionExecution(提供 isNewTransaction()、isRollbackOnly()、setRollbackOnly() 等)、SavepointManager(提供 createSavepoint()、rollbackToSavepoint(Object)、releaseSavepoint(Object))以及 Flushable。7.0.9 中它额外声明了两个 default 方法:
default boolean hasSavepoint() {
return false;
}
@Override
default void flush() {
}
hasSavepoint() 用于诊断当前事务是否携带一个 savepoint(即是否以 PROPAGATION_NESTED 开启的嵌套事务);flush() 用于把底层会话(如 Hibernate Session)刷新到数据库,是给 ORM 集成用的提示性钩子。
这三个接口构成了 Spring 事务抽象的"三元组":定义层(Definition)说"要什么样的事务",管理器层(Manager)说"如何开启/提交/回滚",状态层(Status)说"当前事务进行到哪一步、能不能只读、有没有 savepoint"。理解了这三者的分工,后面所有的装配与运行机制都是围绕它们展开的。
10.2.2 TransactionAttribute:从注解到事务属性的桥梁
TransactionDefinition 描述通用事务属性,但声明式事务还需要回答一个额外问题:"方法抛异常时要不要回滚?"。这个能力由 TransactionAttribute 接口扩展,它位于 spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java,在 TransactionDefinition 基础上增加了一个方法:
public interface TransactionAttribute extends TransactionDefinition {
boolean rollbackOn(Throwable ex);
}
rollbackOn(Throwable ex) 判断给定异常是否应当触发回滚。它的默认实现逻辑在 DefaultTransactionAttribute 中:
// spring-tx/.../interceptor/DefaultTransactionAttribute.java 第 180-182 行
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
这就是"默认只对运行时异常和 Error 回滚、对 checked 异常不回滚"这一广为流传规则的源码出处。但 @Transactional 注解的 rollbackFor/noRollbackFor 属性会改变这个行为——它们被解析成 RuleBasedTransactionAttribute,后者重写了 rollbackOn 方法,按规则列表匹配异常类型。
RuleBasedTransactionAttribute 位于 spring-tx/src/main/java/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.java,它维护一个 List<RollbackRuleAttribute> rollbackRules,每个规则要么是正向回滚规则(RollbackRuleAttribute),要么是反向不回滚规则(NoRollbackRuleAttribute,后者继承自前者)。其 rollbackOn 方法是整个回滚判定的核心:
@Override
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
// User superclass behavior (rollback on unchecked) if no rule matches.
if (winner == null) {
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
逐段解读:
- 遍历所有规则,调用
rule.getDepth(ex)计算该规则与抛出异常的"继承距离"。getDepth返回 -1 表示不匹配,返回 0 表示异常类型与规则类型完全相同,返回正数表示沿父类链向上几层才匹配。 - 在所有匹配的规则中,选择
depth最小的那个(即"最贴近异常类型"的规则)作为 winner。这意味着:如果同时配置了rollbackFor = Exception.class和noRollbackFor = IOException.class,那么抛FileNotFoundException(是IOException子类)时,IOException规则 depth=1、Exception规则 depth=2,更近的IOException胜出——而它是NoRollbackRuleAttribute,所以不回滚。 - 如果没有任何规则匹配(winner == null),退回父类
DefaultTransactionAttribute.rollbackOn的默认行为:只对RuntimeException和Error回滚。 - 如果 winner 是
NoRollbackRuleAttribute(反向规则),返回false(不回滚);否则返回true(回滚)。
RollbackRuleAttribute.getDepth 的实现(同文件第 170-186 行)展示了类型安全匹配与名字模式匹配两条路径:
private int getDepth(Class<?> exceptionType, int depth) {
if (this.exceptionType != null) {
if (this.exceptionType.equals(exceptionType)) {
// Found it!
return depth;
}
}
else if (exceptionType.getName().contains(this.exceptionPattern)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionType == Throwable.class) {
return -1;
}
return getDepth(exceptionType.getSuperclass(), depth + 1);
}
如果规则是用 Class 指定的(rollbackFor = IOException.class),走 exceptionType.equals 精确类型比较;如果是用字符串模式指定的(rollbackForClassName = "IOException"),走 exceptionType.getName().contains(pattern) 子串匹配——后者有命名陷阱,注解 javadoc 明确警告 "Exception" 会匹配几乎所有异常类名。这条递归沿 getSuperclass() 向上追溯到 Throwable.class 为止。
10.2.3 TransactionSynchronizationManager:线程级事务上下文
声明式事务的一个核心工程难题是:业务代码(如 JdbcTemplate)如何知道"当前线程正处在一个事务中、应该复用同一个 Connection"?答案是 TransactionSynchronizationManager,位于 spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java。它是一个纯静态工具类,内部维护六个 ThreadLocal 字段:
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
六个 ThreadLocal 各司其职:
resources:键是资源工厂(如DataSource对象),值是持有的资源句柄(如ConnectionHolder)。事务管理器在doBegin时把新获取的ConnectionHolder绑定到当前线程,业务代码通过DataSourceUtils.getConnection(dataSource)取出同一个Connection,从而保证事务内所有 SQL 走同一个物理连接。synchronizations:当前线程注册的事务同步回调集合(LinkedHashSet)。initSynchronization()开启,clearSynchronization()关闭。currentTransactionName/currentTransactionReadOnly/currentTransactionIsolationLevel:暴露当前事务的名称、只读标志、隔离级别,供 ORM 层做优化决策(例如 Hibernate 根据只读标志把 Session 的 FlushMode 设为 MANUAL)。actualTransactionActive:标记当前线程是否真的关联了一个物理事务(而非仅仅是PROPAGATION_SUPPORTS下的"空事务同步")。
TransactionSynchronizationManager 的关键方法包括 bindResource(key, value)、unbindResource(key)、getResource(key)、initSynchronization()、registerSynchronization(sync)、getSynchronizations()、clearSynchronization(),以及一组 set/getCurrentTransactionXxx()。它是整个事务体系的"线程级黑板"——事务管理器写,数据访问层读。
10.2.4 AbstractPlatformTransactionManager:模板方法模式的典范
具体的事务管理器(如 DataSourceTransactionManager)并不自己处理传播行为、挂起恢复、回滚-only 判定这些通用流程,而是把这些骨架逻辑上提到 AbstractPlatformTransactionManager,后者位于 spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java。它通过模板方法把"变"的部分交给子类:
doGetTransaction():返回一个代表当前事务状态的对象(子类实现,通常会去TransactionSynchronizationManager查当前线程是否已绑定资源)。isExistingTransaction(Object transaction):判断这个事务对象是否代表一个已经存在的事务(DataSourceTransactionManager的实现是检查ConnectionHolder.transactionActive标志)。doBegin(transaction, definition):真正开启一个物理事务(获取连接、关闭 autoCommit、绑定资源)。doSuspend(transaction)/doResume(transaction, suspendedResources):挂起/恢复资源。doCommit(status)/doRollback(status):真正提交/回滚物理事务。doSetRollbackOnly(status):参与外部事务时,把外部事务标记为 rollback-only。doCleanupAfterCompletion(transaction):事务结束后清理资源(解绑连接、恢复 autoCommit、归还连接池)。
而"根据传播行为决定是新建、参与还是挂起"、"commit 前检查 rollback-only"、"触发 beforeCommit/afterCommit/afterCompletion 回调"、"挂起时保存 ThreadLocal 现场、恢复时还原"这些通用逻辑,全部由 AbstractPlatformTransactionManager 用 final 方法实现,子类无法覆写。这是模板方法模式在 Spring 中最经典的应用之一。
本章后续的源码剖析,将沿着"装配链路 → 拦截器入口 → 事务管理器骨架 → 子类 doXxx 实现 → 资源绑定与同步回调"这条主线展开。
10.3 源码剖析(一):声明式装配链路
10.3.1 @EnableTransactionManagement 与 Import 选择器
一切始于配置类上的 @EnableTransactionManagement。该注解位于 spring-tx/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java,它的关键元注解是 @Import(TransactionManagementConfigurationSelector.class):
@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;
RollbackOn rollbackOn() default RollbackOn.RUNTIME_EXCEPTIONS;
}
四个属性各自承担:proxyTargetClass 控制是否强制使用 CGLIB 代理(默认 false,即优先 JDK 动态代理);mode 选择 PROXY 还是 ASPECTJ 织入模式;order 控制事务 advisor 在多个通知中的执行顺序;rollbackOn(6.2 引入,7.0.9 保留)控制无自定义回滚规则时的默认回滚策略,默认 RUNTIME_EXCEPTIONS(仅运行时异常),可切到 ALL_EXCEPTIONS(包括 checked 异常)。
@Import 引入的 TransactionManagementConfigurationSelector 继承自 AdviceModeImportSelector<EnableTransactionManagement>,位于同包下。它的 selectImports 方法根据 mode 属性选择要导入的配置类:
@Override
protected String[] selectImports(AdviceMode adviceMode) {
return switch (adviceMode) {
case PROXY -> new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ -> new String[] {determineTransactionAspectClass()};
};
}
PROXY 模式下导入两个类:AutoProxyRegistrar(向容器注册一个 InfrastructureAdvisorAutoProxyCreator,这是 Spring AOP 实现"自动为匹配 bean 创建代理"的基础设施后处理器)和 ProxyTransactionManagementConfiguration(真正注册事务 advisor 的配置类)。ASPECTJ 模式则走 spring-aspects 模块的 AspectJ 切面配置,本章不展开。
10.3.2 ProxyTransactionManagementConfiguration:注册三个基础设施 bean
ProxyTransactionManagementConfiguration 位于 spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java,是一个 @Configuration(proxyBeanMethods = false) 的基础设施配置类。它只显式声明了两个 @Bean 方法:
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@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;
}
这里出现了三个关键角色:
BeanFactoryTransactionAttributeSourceAdvisor:事务 Advisor。它继承自AbstractBeanFactoryPointcutAdvisor,内部持有一个TransactionAttributeSourcePointcut作为切点,以及一个TransactionInterceptor作为通知(advice)。它的 bean 名固定为org.springframework.transaction.config.internalTransactionAdvisor(由TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME常量定义)。TransactionInterceptor:事务通知,实现 AOP Alliance 的MethodInterceptor接口。它是一个方法拦截器,在代理方法被调用时介入。TransactionAttributeSource:事务属性来源。注意它本身没有在这个配置类里声明@Bean——它由父类AbstractTransactionManagementConfiguration提供(该父类还负责创建默认的AnnotationTransactionAttributeSource并处理@EnableTransactionManagement(rollbackOn = ALL_EXCEPTIONS)时注入默认回滚规则)。
transactionInterceptor 方法把 transactionAttributeSource 注入拦截器,并通过 this.txManager 注入默认事务管理器(txManager 字段来自父类,通过 TransactionManagementConfigurer 回调或按类型查找获得)。
10.3.3 切点 TransactionAttributeSourcePointcut:如何判断一个方法需要事务
BeanFactoryTransactionAttributeSourceAdvisor 内部持有的切点是 TransactionAttributeSourcePointcut,位于 spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java。它继承 StaticMethodMatcherPointcut,意味着方法匹配结果对每个方法只计算一次并缓存(这也是性能关键):
@Override
public boolean matches(Method method, Class<?> targetClass) {
return (this.transactionAttributeSource == null ||
this.transactionAttributeSource.hasTransactionAttribute(method, targetClass));
}
匹配逻辑直接委托给 TransactionAttributeSource.hasTransactionAttribute(method, targetClass)——对于注解驱动场景,这个方法最终会走到 AnnotationTransactionAttributeSource,检查方法或类上是否存在 @Transactional 注解。
除了方法级匹配,它还设置了一个 ClassFilter,在类级别先做一轮粗筛:
private final class TransactionAttributeSourceClassFilter implements ClassFilter {
@Override
public boolean matches(Class<?> clazz) {
if (TransactionalProxy.class.isAssignableFrom(clazz) ||
TransactionManager.class.isAssignableFrom(clazz) ||
PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
return false;
}
return (transactionAttributeSource == null || transactionAttributeSource.isCandidateClass(clazz));
}
// ...
}
这个 ClassFilter 做了两件事:第一,排除那些本身就是事务基础设施的类(事务代理、事务管理器、异常转换器),避免对基础设施类自身做事务代理(否则会无限递归);第二,调用 transactionAttributeSource.isCandidateClass(clazz) 快速判断目标类是否"可能带事务注解"——AnnotationTransactionAttributeSource 会用 AnnotationUtils.isCandidateClass 做一次扫描,对完全没有 @Transactional 注解的类直接返回 false,避免后续对每个方法都做昂贵的注解查找。这是一个重要的性能优化:容器中绝大多数 bean 都不是事务 bean,粗筛在类级别就把它们排除了。
10.3.4 装配完成后的 AOP 三元组
至此,声明式事务的装配链路完成。容器中最终存在三个基础设施 bean:
internalTransactionAdvisor:一个BeanFactoryTransactionAttributeSourceAdvisor,内部组合了TransactionAttributeSourcePointcut(切点)和TransactionInterceptor(通知)。transactionInterceptor:单例的事务拦截器,持有AnnotationTransactionAttributeSource和默认PlatformTransactionManager。annotationTransactionAttributeSource:注解解析器,持有SpringTransactionAnnotationParser(以及可选的 JTA/EJB 注解解析器)。
运行时,InfrastructureAdvisorAutoProxyCreator(由 AutoProxyRegistrar 注册)在每个 bean 初始化后检查:这个 bean 的类是否被 transactionAttributeSourcePointcut 匹配?如果匹配,就为它创建代理(JDK 动态代理或 CGLIB),并把 transactionInterceptor 织入调用链。之后所有通过代理对象调用 @Transactional 方法的请求,都会先进入 TransactionInterceptor.invoke。
下一节我们就从 TransactionInterceptor.invoke 开始,追踪一次事务方法调用的完整链路。
10.4 源码剖析(二):TransactionInterceptor.invoke 与 invokeWithinTransaction
10.4.1 TransactionInterceptor.invoke:AOP 入口
TransactionInterceptor 位于 spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java。它本身很薄,只实现 AOP Alliance 的 MethodInterceptor:
@Override
public @Nullable Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public @Nullable Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public void onRollback(Throwable failure, TransactionExecution execution) {
MethodRollbackEvent event = new MethodRollbackEvent(invocation, failure, execution);
logger.trace(event, failure);
if (applicationEventPublisher != null) {
try {
applicationEventPublisher.publishEvent(event);
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to publish " + event, ex);
}
}
}
}
});
}
这段代码做了三件事:
- 通过
AopUtils.getTargetClass(invocation.getThis())拿到目标类(注意不是代理类本身,而是被代理的原始类)。这一点很关键——@Transactional可能标注在实现类上而非接口上,必须用目标类去查找注解。 - 把
invocation.proceed()包装成一个InvocationCallback传给父类TransactionAspectSupport.invokeWithinTransaction。这种回调抽象让TransactionAspectSupport既能服务于 AOP 拦截器,也能服务于 AspectJ 切面。 onRollback是 7.0.3 引入的默认回调,在判定需要回滚时发布一个MethodRollbackEvent应用事件,供监听器感知"哪个方法因为什么异常回滚了"。
10.4.2 invokeWithinTransaction:事务方法的主流程
真正的逻辑在父类 TransactionAspectSupport.invokeWithinTransaction,位于 spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java 第 333-473 行。这是整个声明式事务的"主舞台",我们逐段拆解。
第一段:取事务属性、确定事务管理器。
protected @Nullable Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr, targetClass);
tas.getTransactionAttribute(method, targetClass):通过切点阶段已经匹配过的TransactionAttributeSource,拿到这个方法对应的TransactionAttribute。如果返回 null,说明这个方法实际上没有事务属性(虽然切点匹配了,但注解解析在某些边界情况下可能返回 null),后面会走"非事务"路径。determineTransactionManager(txAttr, targetClass):决定用哪个事务管理器。
determineTransactionManager 的解析优先级(第 489-534 行)值得详细看:
protected @Nullable TransactionManager determineTransactionManager(
@Nullable TransactionAttribute txAttr, @Nullable Class<?> targetClass) {
TransactionManager tm = determineTransactionManager(txAttr);
if (tm != null) {
return tm;
}
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
else if (targetClass != null) {
// Consider type-level qualifier annotations for transaction manager selection
String typeQualifier = BeanFactoryAnnotationUtils.getQualifierValue(targetClass);
if (StringUtils.hasText(typeQualifier)) {
try {
return determineQualifiedTransactionManager(this.beanFactory, typeQualifier);
}
catch (NoSuchBeanDefinitionException ex) {
// Consider type qualifier as optional, proceed with regular resolution below.
}
}
}
if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
else {
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
解析顺序依次是:(1) 子类硬编码设置的默认 tm;(2) @Transactional("xxx") 中指定的 qualifier;(3) 目标类上 @Qualifier 标注的 qualifier(6.2 引入);(4) transactionManagerBeanName 指定的名字;(5) 按类型从容器取唯一的 TransactionManager bean,并缓存到 transactionManagerCache(一个 ConcurrentReferenceHashMap)。这套优先级保证了:多数据源场景下,方法级 @Transactional("orderTxManager") 能精确指定用哪个事务管理器;单数据源场景下,什么都不配也能自动按类型找到。
第二段:反应式事务分支(本章不展开)。
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) {
// ... ReactiveTransactionSupport 处理 Mono/Flux 返回类型
}
如果事务管理器是 ReactiveTransactionManager 且方法返回类型是响应式类型,走 Reactor 上下文传播的响应式事务路径。本章聚焦命令式事务,跳过这段。
第三段:命令式事务主路径。
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, invocation, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// ... Future/Vavr 返回值额外检查 ...
commitTransactionAfterReturning(txInfo);
return retVal;
}
这是最常见的路径(txAttr != null 且 tm 不是回调偏好型),结构非常清晰:
createTransactionIfNecessary:开启事务(或参与现有事务),返回一个TransactionInfo上下文对象。invocation.proceedWithInvocation():执行业务方法(目标 bean 的真实方法)。- 业务方法正常返回 →
commitTransactionAfterReturning提交事务。 - 业务方法抛异常 →
completeTransactionAfterThrowing根据回滚规则决定提交还是回滚,然后重新抛出原异常(注意throw ex在 catch 块里)。 - 无论成功失败,
finally里cleanupTransactionInfo恢复 ThreadLocal 现场。
这个结构里有两个关键细节:
第一,cleanupTransactionInfo 在 finally 中执行,而 commitTransactionAfterReturning 在 try-catch-finally 之后才执行。这意味着 TransactionInfo 的 ThreadLocal 栈恢复发生在 commit 之前——commit 阶段已经不依赖 transactionInfoHolder 了,commit 所需的所有信息都在 txInfo 对象本身里。
第二,异常路径中 completeTransactionAfterThrowing 之后必须 throw ex 把原异常抛出去。这就是为什么"事务回滚了但调用方依然能看到业务异常"——Spring 不会吞异常,只是在异常穿过拦截器之前先完成回滚动作。
第四段:返回值的额外检查(Future / Vavr Try)。
if (retVal != null && txAttr != null) {
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null) {
if (retVal instanceof Future<?> future && future.isDone()) {
try {
future.get();
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
Assert.state(cause != null, "Cause must not be null");
if (txAttr.rollbackOn(cause)) {
invocation.onRollback(cause, status);
status.setRollbackOnly();
}
}
// ...
}
else if (VAVR_PRESENT && VavrDelegate.isVavrTry(retVal)) {
retVal = VavrDelegate.evaluateTryFailure(invocation, retVal, txAttr, status);
}
}
}
这一段处理两种特殊返回值:如果方法返回一个已经完成的 Future,取出其 ExecutionException 的 cause,按回滚规则标记 rollback-only;如果返回 Vavr 的 Try 类型,失败时同样按规则标记。这是为异步/函数式风格方法提供的补偿逻辑——这类方法不会把异常直接抛出来,而是包装在返回值里,Spring 需要在提交前检查返回值是否携带了应当回滚的失败。
第五段:commit。
commitTransactionAfterReturning(txInfo);
return retVal;
commitTransactionAfterReturning 的实现(第 682-689 行)很简单:
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
直接调用 PlatformTransactionManager.commit(status)。注意这里没有 try-catch——commit 抛的异常会直接传给调用方。
10.4.3 createTransactionIfNecessary:开启事务并绑定 ThreadLocal
createTransactionIfNecessary(第 612-638 行)做了两件事:给事务起名字,然后调用 tm.getTransaction:
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
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);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
第一块:如果 txAttr.getName() 为 null(注解上没显式指定 transactionManager 之外的名称),用一个匿名 DelegatingTransactionAttribute 包装原属性,把 getName() 返回值改成 joinpointIdentification——也就是"全限定类名.方法名"(由 methodIdentification 通过 ClassUtils.getQualifiedMethodName 生成)。这就是事务监控里看到的事务名为什么默认是 com.example.FooService.bar 的原因。
第二块:调用 tm.getTransaction(txAttr)。这一步进入 AbstractPlatformTransactionManager.getTransaction,是下一节的主角。
第三块:prepareTransactionInfo 把 tm、txAttr、status 组装成一个 TransactionInfo 对象,并调用 txInfo.bindToThread()。bindToThread 的实现(第 808-813 行)揭示了 TransactionInfo 的 ThreadLocal 栈机制:
private void bindToThread() {
// Expose current TransactionStatus, preserving any existing TransactionStatus
// for restoration after this transaction is complete.
this.oldTransactionInfo = transactionInfoHolder.get();
transactionInfoHolder.set(this);
}
它把当前已有的 TransactionInfo 存到 oldTransactionInfo 字段,再把自己设为当前线程的 transactionInfoHolder。这样方法嵌套调用时(外层 @Transactional 方法调内层 @Transactional 方法),形成一条链表:内层方法完成后,restoreThreadLocalStatus 把 oldTransactionInfo 恢复回去,ThreadLocal 栈正确出栈。这就是 TransactionAspectSupport.currentTransactionStatus() 能拿到"当前方法"事务状态的原理。
10.5 源码剖析(三):AbstractPlatformTransactionManager 的传播行为骨架
10.5.1 getTransaction:入口与"无现有事务"分支
AbstractPlatformTransactionManager.getTransaction 是 final 方法,位于 spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java 第 372-421 行。这是所有传播行为决策的总入口:
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, false, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
逐段解读:
doGetTransaction():模板方法,子类返回一个事务对象。对于DataSourceTransactionManager,这个对象里会携带从TransactionSynchronizationManager.getResource(dataSource)查到的ConnectionHolder(可能为 null)。isExistingTransaction(transaction):判断当前线程是否已经在一个活动事务中。DataSourceTransactionManager的实现是检查ConnectionHolder != null && connectionHolder.isTransactionActive()——即ConnectionHolder.transactionActive标志位是否为 true。这个标志在doBegin中被设为 true,在cleanupAfterCompletion中被 clear。- 已有事务 →
handleExistingTransaction:进入下一节要讲的分支逻辑。 - 无现有事务时,先校验超时(不能小于 -1),然后按传播行为分三类:
MANDATORY(2):没有事务就直接抛IllegalTransactionStateException。这是硬性约束——"必须在事务中运行"。REQUIRED(0)/REQUIRES_NEW(3)/NESTED(6):先suspend(null)(此时没有事务可挂起,实际上只是清理可能存在的残留同步状态),然后startTransaction真正开启新事务。注意NESTED在"无现有事务"时与REQUIRED行为完全一致——嵌套语义只在"有现有事务"时才体现为 savepoint。- 其余(
SUPPORTS=1 /NOT_SUPPORTED=4 /NEVER=5):进入 else 分支,创建一个"空事务状态"(transaction 对象为 null,newTransaction=true),不开启物理事务,但根据transactionSynchronization配置决定是否初始化同步。如果配置了非默认隔离级别,这里会打一条 warn 日志——因为没有新事务,隔离级别无处施加。
10.5.2 handleExistingTransaction:七种传播行为的分支决策
handleExistingTransaction 位于第 426-519 行,是传播行为决策的核心。它在"当前已存在活动事务"的前提下被调用:
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, false, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, false, false, true, debugEnabled, null);
this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status));
try {
status.createAndHoldSavepoint();
}
catch (RuntimeException | Error ex) {
this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex));
throw ex;
}
this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null));
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
return startTransaction(definition, transaction, true, debugEnabled, null);
}
}
// PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY:
// regular participation in existing transaction.
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
DefaultTransactionDefinition.getIsolationLevelName(currentIsolationLevel) :
"(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
这段代码按 if 顺序依次处理五种传播行为,最后 fall-through 到 REQUIRED/SUPPORTS/MANDATORY 的共用参与逻辑。逐个解读:
PROPAGATION_NEVER(5):直接抛异常。语义是"当前存在事务反而错了"——设计意图是确保方法在无事务环境下运行。
PROPAGATION_NOT_SUPPORTED(4):挂起当前事务,然后以"非事务"方式执行。注意 prepareTransactionStatus(definition, null, false, ...) 中 transaction 对象传 null、newTransaction=false——即不开启新事务,业务代码在无事务状态下运行(每次 SQL 自动提交)。被挂起的事务资源存在 suspendedResources 里,等这个方法返回后在 cleanupAfterCompletion 中恢复。
PROPAGATION_REQUIRES_NEW(3):先 suspend(transaction) 挂起当前事务,然后 startTransaction(...) 开启一个全新的物理事务。这是真正的"新事务"——它有自己独立的数据库连接、独立的 autoCommit、独立的提交/回滚边界。如果新事务开启失败(beginEx),调用 resumeAfterBeginException 恢复原事务再抛出异常——保证原事务不因新事务开启失败而丢失。
PROPAGATION_NESTED(6):先检查 isNestedTransactionAllowed()(DataSourceTransactionManager 构造函数里默认设为 true,JTA 事务管理器默认 false)。然后 useSavepointForNestedTransaction() 默认返回 true,走 savepoint 路径:构造一个 DefaultTransactionStatus,其中 newTransaction=false、nested=true,然后调用 status.createAndHoldSavepoint()——这会委托给底层 SavepointManager(即 JDBC Connection.setSavepoint())创建一个保存点。关键:嵌套事务不挂起外层事务、不开启新物理连接,而是在同一个连接上打一个 savepoint 标记。 内层方法回滚时回滚到这个 savepoint 而非整个事务;内层方法提交时只是释放 savepoint。如果 useSavepointForNestedTransaction() 返回 false(某些 JTA 场景),则走 startTransaction(..., nested=true, ...) 真正嵌套 begin。
REQUIRED(0)/ SUPPORTS(1)/ MANDATORY(2)fall-through:三者共用这段"参与现有事务"逻辑。注意代码里没有对 MANDATORY 在已有事务时做任何特殊判断——因为在 getTransaction 入口处,"无事务 + MANDATORY"已经抛异常了,能走到这里说明已有事务,MANDATORY 自然合法。
这段 fall-through 里还有一个可选校验开关 isValidateExistingTransaction()(默认 false):如果开启,会检查内层方法声明的隔离级别是否与外层事务一致、内层非只读但外层只读是否冲突,不一致就抛异常。默认关闭是为了宽松兼容——内层方法声明的隔离级别在参与外层事务时本来就被忽略,不报错只是"静默忽略"。
最后 newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER),然后 prepareTransactionStatus(definition, transaction, false, newSynchronization, ...)——注意这里 newTransaction=false,表示这个 status 不是新事务,commit 时不会真正调 doCommit。
10.5.3 startTransaction 与 prepareSynchronization:开启新事务后的 ThreadLocal 暴露
startTransaction(第 524-541 行)负责真正开启一个新事务:
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean nested, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, nested, debugEnabled, suspendedResources);
this.transactionExecutionListeners.forEach(listener -> listener.beforeBegin(status));
try {
doBegin(transaction, definition);
}
catch (RuntimeException | Error ex) {
this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, ex));
throw ex;
}
prepareSynchronization(status, definition);
this.transactionExecutionListeners.forEach(listener -> listener.afterBegin(status, null));
return status;
}
流程:构造 status(newTransaction=true)→ 触发监听器 beforeBegin → 调用子类 doBegin(真正获取连接、关 autoCommit、绑定资源)→ prepareSynchronization 把事务属性暴露到 ThreadLocal → 触发监听器 afterBegin。
prepareSynchronization(第 575-585 行)是 ThreadLocal 暴露的关键:
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
只有 status.isNewSynchronization() 为 true 时才执行。这个标志在 newTransactionStatus 中通过 newSynchronization && !TransactionSynchronizationManager.isSynchronizationActive() 计算——即"我想要新同步,且当前线程还没有活动同步"。这意味着:内层 REQUIRED 参与外层事务时,外层已经 initSynchronization 过了,内层不会重复初始化;而 REQUIRES_NEW 挂起外层时外层同步被 clearSynchronization 清掉了,新事务会 initSynchronization 建立自己的同步集合。
prepareSynchronization 做了五件事:设置 actualTransactionActive、设置隔离级别、设置只读标志、设置事务名、initSynchronization() 创建空的同步集合。这五个 ThreadLocal 就是 TransactionSynchronizationManager 那六个字段中的五个(resources 由子类 doBegin 自己绑定)。
10.6 源码剖析(四):挂起、恢复、提交与回滚
10.6.1 suspend:保存 ThreadLocal 现场与解绑资源
suspend 方法位于 AbstractPlatformTransactionManager 第 614-648 行,是 REQUIRES_NEW 与 NOT_SUPPORTED 能够"先暂停外层事务"的核心:
protected final @Nullable SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
if (transaction != null) {
suspendedResources = doSuspend(transaction);
}
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
catch (RuntimeException | Error ex) {
// doSuspend failed - original transaction is still active...
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
}
else if (transaction != null) {
// Transaction active but no synchronization active.
Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);
}
else {
// Neither transaction nor synchronization active.
return null;
}
}
逐段解读:
- 如果当前有活动同步:先
doSuspendSynchronization()——取出所有已注册的同步回调列表,逐个调用其suspend()方法,然后TransactionSynchronizationManager.clearSynchronization()清空当前线程的同步集合。这个列表被保存下来,恢复时要逐个resume()并重新注册。 - 解绑物理资源:如果 transaction 对象非 null,调用子类
doSuspend(transaction)。对DataSourceTransactionManager而言,这一步就是TransactionSynchronizationManager.unbindResource(dataSource)——把当前线程绑定的ConnectionHolder解绑,让新事务可以绑定自己的ConnectionHolder。解绑出来的 holder 作为suspendedResources存进 holder 对象。 - 保存事务属性快照:把当前事务名、只读标志、隔离级别、actualTransactionActive 四个 ThreadLocal 的当前值读出来,然后清空 ThreadLocal(设为 null/false)。这些值一起装进
SuspendedResourcesHolder。 - 异常安全:如果
doSuspend失败,调用doResumeSynchronization(suspendedSynchronizations)把同步恢复回去再抛异常——避免外层事务的同步状态因挂起失败而丢失。 - 两条简化路径:如果没有同步但有事务(transaction != null),只做
doSuspend解绑资源,不处理 ThreadLocal 属性;如果两者都没有,直接返回 null。
suspendedResourcesHolder 是一个静态内部类(第 1332-1361 行),它持有:suspendedResources(物理资源,如被解绑的 ConnectionHolder)、suspendedSynchronizations(被挂起的同步回调列表)、name/readOnly/isolationLevel/wasActive(四个 ThreadLocal 属性的快照)。
10.6.2 resume:恢复被挂起的事务
resume 位于第 660-677 行,与 suspend 对称:
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)
throws TransactionException {
if (resourcesHolder != null) {
Object suspendedResources = resourcesHolder.suspendedResources;
if (suspendedResources != null) {
doResume(transaction, suspendedResources);
}
List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
if (suspendedSynchronizations != null) {
TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
doResumeSynchronization(suspendedSynchronizations);
}
}
}
恢复顺序:先 doResume 重新绑定物理资源(DataSourceTransactionManager.doResume 就是 TransactionSynchronizationManager.bindResource(dataSource, suspendedResources)),然后恢复四个 ThreadLocal 属性,最后 doResumeSynchronization——它先 initSynchronization() 重建同步集合,再逐个调用同步回调的 resume() 方法并 registerSynchronization 重新注册。
这套挂起/恢复机制是 REQUIRES_NEW 能工作的关键:内层新事务在自己的事务边界内,ThreadLocal 上看到的是新事务的连接和属性;内层完成后,外层事务的连接和属性被原样恢复,外层业务代码继续使用原来的 Connection。
10.6.3 commit:rollback-only 检查与 processCommit
commit 方法位于第 733-758 行:
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
三道闸门:
isCompleted():事务已经完成过(commit 或 rollback 已执行)再调 commit,直接抛异常。这就是接口注释说的"commit 失败后不要再调 rollback"的防护。isLocalRollbackOnly():业务代码通过TransactionStatus.setRollbackOnly()显式要求回滚。这是"我不想抛异常但想让事务回滚"的编程式手段。命中则走processRollback。isGlobalRollbackOnly():事务被"全局"标记为回滚——典型场景是:内层REQUIRED参与外层事务时抛了异常,processRollback对内层 status 调用了doSetRollbackOnly,把外层事务的 Connection 标记为 rollback-only。外层方法此时正常返回、调用 commit,但发现全局 rollback-only 已被设置,于是走processRollback(defStatus, true)——第二个参数unexpected=true,意味着这是"意外回滚",最终会抛UnexpectedRollbackException。
shouldCommitOnGlobalRollbackOnly() 默认返回 false(局部资源事务如 JDBC 不支持在 rollback-only 状态下继续 commit),JTA 事务管理器会覆写返回 true,让 UserTransaction.commit() 自己去检测 RollbackException。
10.6.4 processCommit:回调时序与真正 commit
processCommit 位于第 766-848 行,是提交流程的完整编排:
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
boolean commitListenerInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));
commitListenerInvoked = true;
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
this.transactionExecutionListeners.forEach(listener -> listener.beforeCommit(status));
commitListenerInvoked = true;
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
throw ex;
}
catch (TransactionException ex) {
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
if (commitListenerInvoked) {
this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, ex));
}
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
if (commitListenerInvoked) {
this.transactionExecutionListeners.forEach(listener -> listener.afterCommit(status, null));
}
}
}
finally {
cleanupAfterCompletion(status);
}
}
这段代码很长,但结构清晰。关键时序:
prepareForCommit:子类钩子(默认空实现)。triggerBeforeCommit(status.isReadOnly()):调用所有注册的TransactionSynchronization.beforeCommit(readOnly)。这是"数据库真正 commit 之前"的最后机会,ORM 层在这里 flush SQL 到数据库。注意此回调抛异常会导致事务回滚。triggerBeforeCompletion:调用beforeCompletion(),无论后续 commit 还是 rollback 都会执行。- 分三种情况做真正的提交动作:
hasSavepoint():嵌套事务,调用status.releaseHeldSavepoint()释放 savepoint(不真正提交,只是让 savepoint 之后的变更保留在外层事务里)。isNewTransaction():新事务,调用子类doCommit(status)——对 JDBC 就是connection.commit()。- 既无 savepoint 也不是新事务(参与外层事务):什么都不做,只在
failEarlyOnGlobalRollbackOnly时检查一下全局 rollback-only。
unexpectedRollback检查:如果发现全局 rollback-only 但 commit 没抛异常(理论上 JTA 会抛 RollbackException,局部资源事务不会),手动抛UnexpectedRollbackException。- 异常处理分支:捕获
UnexpectedRollbackException时按回滚状态触发 afterCompletion;捕获TransactionException时按配置决定是否补做 rollback;捕获其他 RuntimeException/Error 时先确保 beforeCompletion 被调用,再doRollbackOnCommitException。 triggerAfterCommit:在 finally 块之前调用afterCommit()——这是"事务已经成功提交"之后的回调,发消息、发邮件这类操作应该放这里。注意它和triggerAfterCompletion(STATUS_COMMITTED)在同一个 finally 里,保证 afterCompletion 一定执行。- 最外层 finally
cleanupAfterCompletion:无论成功失败都清理资源。
triggerAfterCommit 的实现(第 1003-1007 行)只有一行:
private void triggerAfterCommit(DefaultTransactionStatus status) {
if (status.isNewSynchronization()) {
TransactionSynchronizationUtils.triggerAfterCommit();
}
}
注意 status.isNewSynchronization() 这个守卫——只有当前事务范围自己开启的同步才在这里触发 afterCommit。内层 REQUIRED 参与外层事务时,afterCommit 由外层事务的提交流程统一触发,内层不重复触发。
10.6.5 processRollback:回滚流程
processRollback 位于第 874-944 行,与 processCommit 对称:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
boolean rollbackListenerInvoked = false;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
rollbackListenerInvoked = true;
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
rollbackListenerInvoked = true;
doRollback(status);
}
else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
// ... 异常处理 ...
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// ...
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
三种分支:
hasSavepoint():嵌套事务回滚到 savepoint(rollbackToHeldSavepoint),外层事务继续。isNewTransaction():新事务,调用doRollback——JDBC 就是connection.rollback()。- 参与外层事务:关键决策点。如果
isLocalRollbackOnly()(业务代码显式 setRollbackOnly)或isGlobalRollbackOnParticipationFailure()(默认 true,内层失败就标记全局回滚),调用doSetRollbackOnly(status)——对 JDBC 就是connectionHolder.setRollbackOnly(),把外层 Connection 标记为 rollback-only,等外层真正 commit 时会走全局 rollback-only 分支转成回滚。如果globalRollbackOnParticipationFailure=false,则什么都不做,让外层调用者自己决定。
这就是"内层 REQUIRED 方法抛异常、外层 REQUIRED 方法正常返回"时,为什么外层最终会抛 UnexpectedRollbackException 的完整链路:内层 processRollback → doSetRollbackOnly 标记外层 Connection rollback-only → 内层方法正常返回(异常被外层 catch 了)→ 外层 commit → commit 方法检测到 isGlobalRollbackOnly() → processRollback(unexpected=true) → 最终抛 UnexpectedRollbackException。
10.7 源码剖析(五):事务同步回调与资源清理
10.7.1 cleanupAfterCompletion:收尾总入口
cleanupAfterCompletion 位于 AbstractPlatformTransactionManager 第 1055-1070 行,在 processCommit 和 processRollback 的 finally 块中被调用:
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
三个步骤:
status.setCompleted():标记事务已完成,后续再调 commit/rollback 会在入口处抛IllegalTransactionStateException。TransactionSynchronizationManager.clear():如果当前事务开启了新同步,清空所有五个事务属性 ThreadLocal 和同步集合。注意这里调的是clear()而不是clearSynchronization()——clear()会同时清掉currentTransactionName、currentTransactionReadOnly、currentTransactionIsolationLevel、actualTransactionActive和synchronizations五个 ThreadLocal。doCleanupAfterCompletion(transaction):如果是新事务,调用子类清理物理资源(解绑连接、恢复 autoCommit、归还连接池)。- 恢复挂起事务:如果这个 status 携带了
suspendedResources(即当前是内层REQUIRES_NEW/NOT_SUPPORTED方法),调用resume恢复外层事务。
10.7.2 TransactionSynchronization 回调时序
TransactionSynchronization 接口位于 spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronization.java。它定义了七个回调方法,按事务生命周期顺序排列:
default void suspend() {}
default void resume() {}
default void flush() {}
default void savepoint(Object savepoint) {}
default void savepointRollback(Object savepoint) {}
default void beforeCommit(boolean readOnly) {}
default void beforeCompletion() {}
default void afterCommit() {}
default void afterCompletion(int status) {}
结合 processCommit 和 processRollback 的源码,完整回调时序如下:
提交成功路径(新事务):
prepareForCommit(子类钩子,无同步回调)beforeCommit(readOnly)—— 所有同步回调beforeCompletion()—— 所有同步回调doCommit(status)—— 真正物理提交afterCommit()—— 所有同步回调(在 commit 成功之后、资源清理之前)afterCompletion(STATUS_COMMITTED)—— 所有同步回调cleanupAfterCompletion
回滚路径(新事务):
beforeCompletion()doRollback(status)—— 真正物理回滚afterCompletion(STATUS_ROLLED_BACK)cleanupAfterCompletion
注意回滚路径没有 beforeCommit 和 afterCommit——beforeCommit 顾名思义只在"准备提交"时触发,回滚时不触发。
这套时序的设计意图:
beforeCommit(readOnly=true/false):给 ORM 层最后一次 flush 机会,把缓存的 SQL 发到数据库。readOnly 参数告诉它"这是只读事务,可以跳过脏检查"。此回调抛异常会导致回滚。beforeCompletion():无论提交还是回滚都会调用,用于资源清理。此回调异常只记录日志不传播。afterCommit():事务已经成功提交,数据库变更已持久化。这里适合做"提交成功后才能做"的操作——比如发消息通知、发邮件。但 javadoc 警告:此时事务资源可能还没清理完,这里调用的 DAO 操作仍会"参与"原事务,所以如果需要在 afterCommit 里再开一个新事务,必须用REQUIRES_NEW。afterCompletion(status):事务彻底结束(提交或回滚),用于清理资源。status 参数是STATUS_COMMITTED(0)/STATUS_ROLLED_BACK(1)/STATUS_UNKNOWN(2)。
suspend() 和 resume() 回调在 AbstractPlatformTransactionManager.doSuspendSynchronization 和 doResumeSynchronization 中被调用——挂起外层事务时逐个调 synchronization.suspend(),恢复时调 resume() 并重新注册。
sync 回调的执行顺序由 OrderComparator 排序实现(见 TransactionSynchronizationManager.getSynchronizations 第 351-371 行):注册时用 LinkedHashSet 保持插入顺序,取快照时才按 Ordered 接口排序。这意味着 Spring 自身注册的系统同步(如 DataSourceUtils 的连接管理同步)可以通过较低 order 值排在前面执行。
10.7.3 DataSourceTransactionManager.doBegin:连接获取与事务绑定
现在我们进入子类实现。DataSourceTransactionManager 位于 spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java。它的构造函数默认开启嵌套事务支持:
public DataSourceTransactionManager() {
setNestedTransactionAllowed(true);
}
doGetTransaction 与 isExistingTransaction(第 247-260 行):
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}
doGetTransaction 从 TransactionSynchronizationManager.getResource(dataSource) 查当前线程是否已绑定 ConnectionHolder。如果外层事务已经绑定过,这里能查到;如果是第一次进入,查到 null。
isExistingTransaction 的判定条件是:holder 存在且 holder.isTransactionActive() 为 true。注意仅仅"绑定了 holder"还不够——ConnectionHolder 在非事务状态下也可能被绑定(比如 JTA 事务中懒绑定连接),必须额外检查 transactionActive 标志。这个标志在 doBegin 末尾被设为 true。
doBegin(第 263-321 行)是真正开启 JDBC 事务的地方:
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
if (definition.isReadOnly()) {
checkDefaultReadOnly(newCon);
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,
definition.getIsolationLevel(),
(definition.isReadOnly() && !isDefaultReadOnly()));
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary.
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
逐段解读:
- 获取新连接:如果当前没有 holder,或者现有 holder 已经"与事务同步"(说明它属于另一个事务上下文,需要新连接),从 DataSource 获取一个新 Connection,包装成
ConnectionHolder,标记newConnectionHolder=true。 - 标记 holder 已同步:
setSynchronizedWithTransaction(true)——这个标志是上面doBegin入口判断"现有 holder 是否属于当前事务"的依据。 - 准备连接:
DataSourceUtils.prepareConnectionForTransaction(con, isolationLevel, readOnly)设置隔离级别和只读标志,并返回之前的隔离级别(供清理时恢复)。 - 关闭 autoCommit:如果连接当前是 autoCommit=true,切换到手动提交,并记录
mustRestoreAutoCommit=true(清理时恢复)。这是 JDBC 事务的本质——setAutoCommit(false)之后,所有 SQL 都在一个事务里,直到显式commit()或rollback()。 prepareTransactionalConnection:子类钩子。DataSourceTransactionManager的默认实现是:如果enforceReadOnly=true且定义是只读事务,执行SET TRANSACTION READ ONLYSQL。- 标记事务活跃:
holder.setTransactionActive(true)——这就是isExistingTransaction检查的那个标志。 - 超时设置:
holder.setTimeoutInSeconds(timeout)——把超时秒数记在 holder 上,后续DataSourceUtils.applyTransactionTimeout会把它应用到每个 Statement 的 queryTimeout。 - 绑定到线程:如果是新建的 holder,
TransactionSynchronizationManager.bindResource(dataSource, holder)——从此当前线程上这个 DataSource 就对应这个 ConnectionHolder,JdbcTemplate 通过DataSourceUtils.getConnection取到同一个 Connection。 - 异常清理:如果任何一步失败且 holder 是新建的,释放连接并清空 holder。
10.7.4 doCommit / doRollback / doCleanupAfterCompletion
doCommit 和 doRollback 非常直白(第 336-363 行):
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
// ...
try {
con.rollback();
}
catch (SQLException ex) {
throw translateException("JDBC rollback", ex);
}
}
就是直接调 connection.commit() / connection.rollback(),SQLException 被翻译成 Spring 的数据访问异常体系。
doSetRollbackOnly(第 366-373 行)参与外层事务时被调用:
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
txObject.setRollbackOnly();
}
即 connectionHolder.setRollbackOnly(true)——在外层事务的 ConnectionHolder 上打标记,不真正回滚连接。
doCleanupAfterCompletion(第 376-406 行)是资源清理的关键:
@Override
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// Remove the connection holder from the thread, if exposed.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
// Reset connection.
Connection con = txObject.getConnectionHolder().getConnection();
try {
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
DataSourceUtils.resetConnectionAfterTransaction(con,
txObject.getPreviousIsolationLevel(),
(txObject.isReadOnly() && !isDefaultReadOnly()));
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
}
DataSourceUtils.releaseConnection(con, this.dataSource);
}
txObject.getConnectionHolder().clear();
}
清理顺序:(1) 如果是新建 holder,从 ThreadLocal 解绑;(2) 如果之前改过 autoCommit,恢复成 true;(3) 通过 resetConnectionAfterTransaction 恢复隔离级别和只读标志到事务前的值;(4) 如果是新建 holder,releaseConnection 把连接归还连接池;(5) holder.clear() 清空 holder 状态(包括 transactionActive、rollbackOnly、savepoint 等标志)。
注意第 (4) 步只对"新建 holder"归还连接——如果是参与外层事务(holder 是外层的、不是本事务新建的),连接不能归还,要留给外层事务继续用。这是 newConnectionHolder 标志的核心作用。
10.8 源码剖析(六):@Transactional 注解解析
10.8.1 @Transactional 注解属性
@Transactional 注解位于 spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java,可标注在类或方法上。7.0.9 中它的完整属性列表:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
要点:
value与transactionManager通过@AliasFor互为别名,@Transactional("orderTxManager")等价于@Transactional(transactionManager = "orderTxManager")。这个值会被解析成TransactionAttribute.getQualifier(),在determineTransactionManager中用于按 qualifier 查找事务管理器。propagation/isolation是枚举类型(Propagation/Isolation),枚举的value()方法返回对应的TransactionDefinition.PROPAGATION_XXX/ISOLATION_XXX常量值。timeout是秒数,默认TIMEOUT_DEFAULT(-1)。timeoutString(5.3 引入)支持占位符解析(如"${tx.timeout:30}")。readOnly是提示性标志,不强制。rollbackFor/noRollbackFor接受异常类型数组;rollbackForClassName/noRollbackForClassName接受类名模式字符串。label(5.3 引入)是事务标签,可被特定事务管理器解释。@Inherited元注解:类级@Transactional会被类的子类继承(但注解 javadoc 明确说"不向上继承祖先类的注解"——继承只向下生效)。
10.8.2 SpringTransactionAnnotationParser:把注解转成 RuleBasedTransactionAttribute
注解解析由 SpringTransactionAnnotationParser 完成,位于 spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java。核心方法是 parseTransactionAnnotation(AnnotationAttributes attributes):
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
String timeoutString = attributes.getString("timeoutString");
Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0,
"Specify 'timeout' or 'timeoutString', not both");
rbta.setTimeoutString(timeoutString);
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
rbta.setLabels(Set.of(attributes.getStringArray("label")));
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
这段代码是注解到 TransactionAttribute 的完整映射:
- new 一个
RuleBasedTransactionAttribute。 - 把 propagation 枚举的
value()(int)设进去;isolation 同理。 - timeout 从 Number 取出 int;同时校验
timeout与timeoutString不能同时指定。 - readOnly、qualifier(即
value属性)、labels 直接设置。 - 四个回滚规则数组分别遍历:
rollbackFor类型 →new RollbackRuleAttribute(Class);rollbackForClassName字符串 →new RollbackRuleAttribute(String);noRollbackFor→new NoRollbackRuleAttribute(Class);noRollbackForClassName→new NoRollbackRuleAttribute(String)。全部加入 list。 rbta.setRollbackRules(rollbackRules),返回。
注意这个方法用 AnnotatedElementUtils.findMergedAnnotationAttributes 而不是直接 element.getAnnotation(Transactional.class)(见 parseTransactionAnnotation(AnnotatedElement element) 第 55-64 行):
@Override
public @Nullable TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
findMergedAnnotationAttributes 是 Spring 元注解合并工具——它能处理 @Transactional 被组合到自定义注解上的情况(例如 @Target(ElementType.METHOD) @Retention(RUNTIME) @Transactional(rollbackFor = Exception.class) public @interface MyTx {},标注 @MyTx 的方法也会被识别),以及 @AliasFor 属性别名的合并。这就是为什么 Spring 的注解支持"组合注解"能力。
10.8.3 AnnotationTransactionAttributeSource:方法级与类级合并
AnnotationTransactionAttributeSource 位于 spring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java。它继承 AbstractFallbackTransactionAttributeSource,后者实现了"方法优先、类兜底"的查找逻辑与缓存。
构造函数(第 85-99 行)按 classpath 是否存在 JTA/EJB 注解决定要加载哪些解析器:
public AnnotationTransactionAttributeSource() {
if (JTA_PRESENT || EJB_3_PRESENT) {
this.annotationParsers = CollectionUtils.newLinkedHashSet(3);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (JTA_PRESENT) {
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (EJB_3_PRESENT) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
默认只装 SpringTransactionAnnotationParser;classpath 上有 jakarta.transaction.Transactional 时额外装 JTA 解析器,有 EJB TransactionAttribute 时额外装 EJB 解析器。这意味着同一个方法上同时标 Spring @Transactional 和 JTA @Transactional 时,Spring 解析器先跑、先命中就返回(determineTransactionAttribute 遍历解析器,第一个返回非 null 的胜出)。
determineTransactionAttribute(第 197-205 行附近):
protected @Nullable TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
它被父类 AbstractFallbackTransactionAttributeSource 的 getTransactionAttribute(method, targetClass) 调用。父类的查找顺序是(见 AbstractFallbackTransactionAttributeSource):先在方法上找(findTransactionAttribute(method)),找不到再在方法所在类上找(findTransactionAttribute(method.getDeclaringClass())),还找不到再在目标类及其接口上找。这个"方法级覆盖类级"的合并规则意味着:类上标了 @Transactional(readOnly = true),某个方法上标了 @Transactional(readOnly = false) 时,该方法用自己的属性,其他方法用类级属性。
父类还做了缓存:TransactionAttributeSource 的 getTransactionAttribute 结果对每个 Method 只计算一次,缓存在 ConcurrentReferenceHashMap 中,避免每次方法调用都反射查注解。这就是为什么切点 TransactionAttributeSourcePointcut.matches 虽然要调 hasTransactionAttribute,但实际运行时性能开销很小。
10.8.4 completeTransactionAfterThrowing:异常后的提交/回滚决策
回到 TransactionAspectSupport。业务方法抛异常时,invokeWithinTransaction 的 catch 块调用 completeTransactionAfterThrowing,位于第 697-737 行:
protected void completeTransactionAfterThrowing(
@Nullable TransactionInfo txInfo, InvocationCallback invocation, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
invocation.onRollback(ex, txInfo.getTransactionStatus());
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
决策点只有一行:txInfo.transactionAttribute.rollbackOn(ex)。
- true:调用
invocation.onRollback(发布 MethodRollbackEvent),然后tm.rollback(status)。注意这里 catch 了 rollback 阶段自己抛出的异常——如果回滚本身失败,会记录日志并把原业务异常通过initApplicationException关联到新异常上,再抛出新异常。这就是"应用异常被回滚异常覆盖"场景的处理。 - false:不回滚,而是
tm.commit(status)。注释说"Will still roll back if TransactionStatus.isRollbackOnly() is true"——即使这里走 commit 分支,AbstractPlatformTransactionManager.commit内部会检查isLocalRollbackOnly()和isGlobalRollbackOnly(),如果发现全局 rollback-only,依然会转成回滚并抛UnexpectedRollbackException。
这就是"checked exception 默认不回滚"的完整链路:业务方法抛 IOException → RuleBasedTransactionAttribute.rollbackOn(ex) 中没有匹配规则 → 退回 super.rollbackOn(ex) → ex instanceof RuntimeException || ex instanceof Error 为 false → 返回 false → 走 commit 分支 → AbstractPlatformTransactionManager.commit 检查 rollback-only 标志(没有被设置)→ processCommit 正常提交。
10.8.5 DataSourceUtils.getConnection:业务代码如何拿到事务连接
业务代码(JdbcTemplate)获取连接走的是 DataSourceUtils.getConnection(dataSource),位于 spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java 第 79 行。它的核心逻辑在 doGetConnection:先查 TransactionSynchronizationManager.getResource(dataSource),如果当前线程已绑定 ConnectionHolder(即事务开启时 doBegin 绑定的),直接返回 holder 里的 Connection;如果没有绑定,才从 DataSource 新建连接。
这个"先查 ThreadLocal、没有再新建"的两步查找,是声明式事务能让业务代码无感知地复用事务连接的关键:事务内所有 DAO 操作都调 DataSourceUtils.getConnection,拿到的是同一个 Connection;事务结束后 holder 被解绑,下一次 getConnection 又会新建连接(自动提交模式)。
对应的 releaseConnection 则是反向操作:如果连接来自事务绑定(holder 标记了事务同步),不真正归还连接池,只是递减引用计数;如果连接是方法内临时新建的(无事务),才真正 connection.close() 归还连接池。这套机制保证了事务内连接被正确持有到事务结束,事务外连接被及时归还。
10.9 运行机制:一次 @Transactional 调用的完整时序
10.9.1 标准 REQUIRED 场景:从代理到提交
假设有一个服务:
@Service
public class OrderService {
@Autowired private JdbcTemplate jdbc;
@Transactional
public void createOrder(Order order) {
jdbc.update("INSERT INTO orders ...");
stockService.deduct(order.getSku(), order.getCount());
}
}
通过 Spring 容器拿到的 orderService 实际上是一个代理对象(JDK 动态代理或 CGLIB)。调用 orderService.createOrder(order) 时,完整时序如下:
调用方
│
▼
OrderService$$SpringProxy.createOrder(order)
│
▼
TransactionInterceptor.invoke(MethodInvocation)
│ targetClass = OrderService
▼
TransactionAspectSupport.invokeWithinTransaction(method, OrderService, callback)
│
├─ tas.getTransactionAttribute(method, OrderService)
│ → AnnotationTransactionAttributeSource 查注解(缓存命中)
│ → RuleBasedTransactionAttribute{propagation=REQUIRED, ...}
│
├─ determineTransactionManager(txAttr, OrderService)
│ → 无 qualifier → 按类型取 DataSourceTransactionManager
│
├─ createTransactionIfNecessary(ptm, txAttr, "com.example.OrderService.createOrder")
│ │
│ ▼
│ AbstractPlatformTransactionManager.getTransaction(txAttr)
│ │
│ ├─ doGetTransaction()
│ │ → TransactionSynchronizationManager.getResource(dataSource) = null
│ │ → txObject{connectionHolder=null}
│ │
│ ├─ isExistingTransaction(txObject) = false
│ │
│ ├─ propagation=REQUIRED → suspend(null) → startTransaction(...)
│ │ │
│ │ ├─ doBegin(txObject, def)
│ │ │ → dataSource.getConnection() // 从连接池取物理连接
│ │ │ → con.setAutoCommit(false)
│ │ │ → holder.setTransactionActive(true)
│ │ │ → TransactionSynchronizationManager.bindResource(dataSource, holder)
│ │ │
│ │ └─ prepareSynchronization(status, def)
│ │ → setCurrentTransactionName("com.example.OrderService.createOrder")
│ │ → setActualTransactionActive(true)
│ │ → initSynchronization() // 创建空同步集合
│ │
│ └─ return DefaultTransactionStatus{newTransaction=true, ...}
│
├─ invocation.proceedWithInvocation()
│ │
│ ▼
│ 目标对象 OrderService.createOrder(order) 真正执行
│ │
│ ├─ jdbc.update("INSERT INTO orders ...")
│ │ → DataSourceUtils.getConnection(dataSource)
│ │ → 查 ThreadLocal 命中 holder → 返回同一个 Connection
│ │ → SQL 在手动提交连接上执行,未提交
│ │
│ └─ stockService.deduct(...) // 若该方法也 @Transactional
│ → 代理 → TransactionInterceptor.invoke → invokeWithinTransaction
│ → getTransaction → isExistingTransaction=true(holder.transactionActive)
│ → propagation=REQUIRED → handleExistingTransaction fall-through
│ → prepareTransactionStatus(def, txObject, newTransaction=false, ...)
│ → 参与外层事务,不新建连接
│ → deduct 方法在同一个 Connection 上执行 SQL
│ → 正常返回 → commitTransactionAfterReturning
│ → tm.commit(status) → status.isNewTransaction()=false
│ → processCommit 中"参与外层事务"分支,什么都不做
│ → cleanupAfterCompletion:status.isNewSynchronization()=false,不清 ThreadLocal
│ → 内层方法返回
│
├─ cleanupTransactionInfo(txInfo) // finally:恢复 ThreadLocal TransactionInfo 栈
│
├─ commitTransactionAfterReturning(txInfo)
│ │
│ ▼
│ AbstractPlatformTransactionManager.commit(status)
│ │
│ ├─ isCompleted()=false
│ ├─ isLocalRollbackOnly()=false
│ ├─ isGlobalRollbackOnly()=false
│ └─ processCommit(status)
│ │
│ ├─ triggerBeforeCommit(readOnly=false)
│ │ → 调用所有注册同步的 beforeCommit(JPA 在这里 flush)
│ ├─ triggerBeforeCompletion()
│ ├─ status.isNewTransaction()=true → doCommit(status)
│ │ → connection.commit() // 真正提交数据库事务
│ ├─ triggerAfterCommit()
│ │ → 调用所有注册同步的 afterCommit(发消息等)
│ ├─ triggerAfterCompletion(STATUS_COMMITTED)
│ │ → 调用所有注册同步的 afterCompletion
│ └─ cleanupAfterCompletion(status)
│ ├─ status.setCompleted()
│ ├─ TransactionSynchronizationManager.clear() // 清 ThreadLocal
│ ├─ doCleanupAfterCompletion(txObject)
│ │ → unbindResource(dataSource)
│ │ → con.setAutoCommit(true)
│ │ → resetConnectionAfterTransaction(恢复隔离级别)
│ │ → DataSourceUtils.releaseConnection(con, dataSource) // 归还连接池
│ │ → holder.clear()
│ └─ (无挂起资源,不 resume)
│
└─ return retVal
这条时序线展示了几个关键事实:
- ThreadLocal 是整个机制的枢纽:doBegin 把 ConnectionHolder 绑定到 ThreadLocal,业务代码通过 DataSourceUtils 查到它,结束后 cleanup 解绑。没有这个 ThreadLocal,事务内多次 DAO 操作就无法复用同一个 Connection。
- 内层 REQUIRED 方法不新建连接:
isExistingTransaction返回 true,handleExistingTransaction 走 fall-through,newTransaction=false。内层 commit 时 processCommit 里hasSavepoint()=false、isNewTransaction()=false,什么都不做;cleanup 时isNewTransaction()=false,不调 doCleanupAfterCompletion(连接不能释放),isNewSynchronization()=false,不清 ThreadLocal。所有收尾工作留给最外层事务。 - afterCommit 在 connection.commit() 之后、cleanup 之前触发:这意味着在 afterCommit 回调里,数据库已经持久化,但连接还没归还——此时做数据访问仍会复用这个连接(如 javadoc 警告)。
10.9.2 REQUIRES_NEW 场景:挂起与恢复
假设 stockService.deduct 标注了 @Transactional(propagation = Propagation.REQUIRES_NEW),则内层调用时的时序变化:
外层 OrderService.createOrder 已开启事务:
ThreadLocal: resources[dataSource]=holderA, actualTransactionActive=true,
synchronizations=Set{sync1,...}
进入 stockService.deduct:
│
▼
getTransaction(txAttr{REQUIRES_NEW})
├─ doGetTransaction → 查到 holderA(当前线程绑定)
├─ isExistingTransaction(holderA.transactionActive=true) = true
└─ handleExistingTransaction
└─ propagation=REQUIRES_NEW
│
├─ suspend(transaction)
│ ├─ doSuspendSynchronization()
│ │ → 取出 sync1... 调 sync1.suspend()
│ │ → clearSynchronization() // 当前线程同步集合被清空
│ ├─ doSuspend(transaction)
│ │ → unbindResource(dataSource) // 解绑 holderA
│ │ → holderA 被取出但未关闭
│ ├─ 保存 name/readOnly/isolation/wasActive 到 SuspendedResourcesHolder
│ └─ holderA 被包进 suspendedResources
│
└─ startTransaction(...)
├─ doBegin(txObject, def)
│ → dataSource.getConnection() // 从连接池取**新连接** holderB
│ → conB.setAutoCommit(false)
│ → holderB.setTransactionActive(true)
│ → bindResource(dataSource, holderB) // ThreadLocal 现在绑 holderB
└─ prepareSynchronization → initSynchronization() // 新同步集合
此时线程上的状态:resources[dataSource]=holderB(新连接),同步集合是空的(外层同步被挂起保存)。deduct 方法的所有 SQL 走 holderB 连接,与外层 holderA 完全独立。
deduct 方法完成时:
processCommit → doCommit(status) // connectionB.commit()
cleanupAfterCompletion:
├─ TransactionSynchronizationManager.clear() // 清 holderB 的 ThreadLocal
├─ doCleanupAfterCompletion → unbindResource(dataSource=holderB)
│ → conB.setAutoCommit(true)
│ → releaseConnection(conB) // holderB 连接归还连接池
└─ status.getSuspendedResources() != null → resume(transaction, suspendedHolder)
├─ doResume(transaction, holderA)
│ → bindResource(dataSource, holderA) // holderA 重新绑回线程
├─ 恢复 name/readOnly/isolation/wasActive 四个 ThreadLocal
└─ doResumeSynchronization(suspendedSynchronizations)
→ initSynchronization()
→ 逐个 sync.resume() + registerSynchronization(sync)
恢复后,线程状态回到外层事务开始 deduct 调用前的样子:resources[dataSource]=holderA,外层同步集合完整。外层 createOrder 方法继续执行后续逻辑,最后外层 commit 提交 holderA 的事务。
关键设计点:REQUIRES_NEW 用了两个独立的数据库连接、两个独立的事务边界。内层 deduct 成功提交后,外层 createOrder 后续如果失败回滚,deduct 的提交不受影响——这就是"日志记录方法用 REQUIRES_NEW,即使主事务回滚日志也能持久化"的原理。代价是两个连接池连接并发占用,长事务里频繁 REQUIRES_NEW 容易耗尽连接池。
10.9.3 NESTED 场景:savepoint 状态流转
假设 stockService.deduct 标注 @Transactional(propagation = Propagation.NESTED),在外层 createOrder 事务内调用:
外层已绑定 holderA,transactionActive=true
进入 deduct:
getTransaction → isExistingTransaction=true
handleExistingTransaction → propagation=NESTED
├─ isNestedTransactionAllowed()=true(DataSourceTransactionManager 默认)
├─ useSavepointForNestedTransaction()=true
├─ newTransactionStatus(def, transaction, newTransaction=false,
│ newSynchronization=false, nested=true, ...)
└─ status.createAndHoldSavepoint()
→ 委托 SavepointManager(即 JdbcTransactionObjectSupport)
→ connectionA.setSavepoint("SVPG_xxx") // 在 holderA 连接上打保存点
注意 NESTED 不挂起外层事务、不新建连接。它只是在 holderA 的同一个 Connection 上打了一个 JDBC Savepoint。线程上 resources[dataSource] 仍然是 holderA,没有变化。
deduct 方法如果正常返回:
commit(status)
→ status.hasSavepoint()=true
→ processCommit 中走 hasSavepoint 分支
→ status.releaseHeldSavepoint() // connectionA.releaseSavepoint(savepoint)
→ 不做 connection.commit()
→ cleanup:status.isNewTransaction()=false,不解绑连接
deduct 方法如果抛异常且回滚规则命中:
rollback(status)
→ processRollback 中 status.hasSavepoint()=true
→ status.rollbackToHeldSavepoint() // connectionA.rollback(savepoint)
→ 回滚到 savepoint 之后、savepoint 之前的 SQL 仍然有效
→ 不做 connection.rollback()
NESTED 与 REQUIRES_NEW 的本质区别:REQUIRES_NEW 是两个独立物理事务,各有各的连接、各有各的 commit;NESTED 是同一个物理事务里的一个保存点,内层回滚只撤销到 savepoint,外层事务继续,外层最终 commit 时 savepoint 之后的变更一起提交。NESTED 内层 commit 不等于物理提交,只是释放 savepoint 标记——如果外层后续回滚,内层"提交"的变更也会一起回滚。
10.9.4 状态机视角:TransactionSynchronizationManager 的 ThreadLocal 流转
把 ThreadLocal 状态机化,可以画出一个完整的状态转换图。初始状态(线程未进入任何事务):
resources= 空 map(或未初始化)synchronizations= nullcurrentTransactionName= nullactualTransactionActive= null
进入 REQUIRED 方法(无现有事务)后:
resources[dataSource]= holder(doBegin 绑定)synchronizations= 空 LinkedHashSet(initSynchronization)currentTransactionName= "com.example.OrderService.createOrder"actualTransactionActive= TRUE
方法执行中注册了一个自定义同步 mySync:
synchronizations= {mySync}
方法返回、commit 完成后 cleanup:
synchronizations= removed(clear())- 五个属性 ThreadLocal 全部 removed
resources[dataSource]= removed(unbindResource)
回到初始状态。这个状态机的正确性依赖于"每个 initSynchronization 必有对应 clearSynchronization"、"每个 bindResource 必有对应 unbindResource"的严格配对——AbstractPlatformTransactionManager 通过 try-finally 结构保证了这一点。
10.10 扩展用法与最佳实践
10.10.1 编程式事务:TransactionTemplate
声明式事务虽然方便,但有两类场景不适用:(1) 事务边界非常细、只包裹几行代码;(2) 需要在事务内根据条件决定提交还是回滚。这时候 Spring 提供了编程式事务模板 TransactionTemplate,位于 spring-tx/src/main/java/org/springframework/transaction/support/TransactionTemplate.java。
它的 execute 方法核心逻辑(第 130-152 行):
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager cpptm) {
return cpptm.execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
与声明式路径对比:TransactionInterceptor.invokeWithinTransaction 处理的是"方法抛异常时按 rollbackOn 规则决定回滚还是提交",而 TransactionTemplate 走更简单的语义——action.doInTransaction(status) 抛任何 RuntimeException 或 Error 都回滚,正常返回就 commit。它不处理 checked 异常的回滚规则(checked 异常会被包成 UndeclaredThrowableException 再回滚)。
典型用法:
transactionTemplate.execute(status -> {
jdbc.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
jdbc.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
return null;
});
TransactionTemplate 本身继承 DefaultTransactionDefinition,所以可以在构造时设置传播行为、隔离级别、超时:
TransactionTemplate tt = new TransactionTemplate(transactionManager);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tt.setTimeout(30);
编程式事务与声明式事务混用是安全的:两者底层都走同一个 PlatformTransactionManager.getTransaction/commit/rollback 和同一套 ThreadLocal 资源绑定。外层 @Transactional 方法里注入 TransactionTemplate 执行一段 REQUIRES_NEW 逻辑,与直接写一个 @Transactional(REQUIRES_NEW) 的内部方法效果等价——前提是通过代理调用(编程式模板不经过代理,所以不存在自调用问题)。
10.10.2 事务同步:提交后发消息
一个经典需求:"订单创建成功后,发一条 MQ 消息通知库存系统。"如果直接在业务方法里发消息,消息可能在事务提交前就发出去了——如果事务后续回滚,消息已经发了,库存系统却没看到订单,造成数据不一致。
正确做法是注册一个 TransactionSynchronization,在 afterCommit 里发消息:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
mqProducer.send("order.created", orderEvent);
}
});
回顾 processCommit 的时序:doCommit(status) 成功执行后才调用 triggerAfterCommit。这意味着 afterCommit 回调运行时,数据库事务已经持久化——此时发消息,消息消费方一定能读到已提交的订单数据。
如果需要在事务回滚时也做处理(比如记录失败日志),可以实现 afterCompletion(int status):
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
mqProducer.send("order.created", event);
}
@Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
log.warn("事务回滚,订单 {} 未创建", orderId);
}
}
});
注意 afterCommit 回调里如果要再访问数据库,javadoc 警告:此时事务资源可能还没清理完,数据访问仍会"参与"原事务(而原事务已经 commit 了,后续操作没有 commit 机会)。所以 afterCommit 里需要写库的操作,必须用 REQUIRES_NEW 开新事务。
Spring 4.2 之后提供了更声明式的等价做法:@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 注解的事件监听器,它内部就是注册了一个只在 afterCommit 阶段触发的同步回调。底层机制与手动 registerSynchronization 完全一致。
10.10.3 只读事务的语义与正确用法
@Transactional(readOnly = true) 在 7.0.9 中的语义需要精确理解:
- 它不是强制约束。
DataSourceTransactionManager.doBegin默认只调用DataSourceUtils.prepareConnectionForTransaction(con, isolationLevel, readOnly)中的con.setReadOnly(true)——这是 JDBC 驱动的提示性标志,告诉驱动"这个连接不会写数据",驱动可能据此做优化(如 Oracle 启用只读模式、PostgreSQL 跳过 WAL)。但应用代码在只读事务里执行INSERT,JDBC 驱动默认不会报错(除非显式开启enforceReadOnly=true,那会执行SET TRANSACTION READ ONLYSQL,强制数据库拒绝写操作)。 - 它会暴露到 ThreadLocal:
prepareSynchronization把currentTransactionReadOnly设为 true。ORM 层(Hibernate/JPA)读到这个标志后会把 Session 的 FlushMode 设为 MANUAL——这才是只读事务最实际的优化:避免 ORM 在 commit 前 flush 时做脏检查、生成不必要的 UPDATE SQL。 - 它会传给 beforeCommit 回调:
triggerBeforeCommit(status.isReadOnly())把只读标志传给同步回调,ORM 层据此跳过 flush。
最佳实践:查询类服务方法标 readOnly = true,能减少 ORM 脏检查开销、让连接池更容易优化。但不要把它当作"写操作会报错"的安全护栏——那是 enforceReadOnly=true 或数据库权限的职责。
10.10.4 大事务与超时
@Transactional(timeout = 30) 的实现机制:doBegin 时 holder.setTimeoutInSeconds(30),这个值存在 ConnectionHolder 上。后续每次通过 JdbcTemplate 创建 Statement 时,DataSourceUtils.applyTransactionTimeout(stmt, dataSource) 会把 holder 里的超时值设置为 stmt.setQueryTimeout(30)——即每条 SQL 的查询超时时间,而不是整个事务的超时。
这意味着 Spring 的事务超时实际上是"语句级超时":如果事务里第一条 SQL 跑了 29 秒、第二条 SQL 又跑了 29 秒,总耗时 58 秒也不会触发事务超时——因为每条语句都没超过 30 秒。真正的"整个事务超时"需要数据库或连接池层配合(如 PostgreSQL 的 idle_in_transaction_session_timeout)。
大事务风险:长事务长时间持有数据库连接和行锁,会导致锁等待堆积、连接池耗尽。常见根因是事务方法里调用了外部 HTTP 接口、发了 RPC、做了大循环计算——这些操作不应在事务内。原则是:事务方法里只做数据库操作,远程调用放到事务方法外(或用 afterCommit 回调)。
10.10.5 多事务管理器的配置
当容器里存在多个 PlatformTransactionManager bean 时,@Transactional 不指定 qualifier 会导致 NoUniqueBeanDefinitionException(beanFactory.getBean(TransactionManager.class) 按类型取会失败)。解决方式:
- 在
@Transactional("orderTxManager")或@Transactional(transactionManager = "orderTxManager")中显式指定 bean 名。 - 或者实现
TransactionManagementConfigurer接口,通过annotationDrivenTransactionManager()指定默认事务管理器。 - 或者给其中一个事务管理器 bean 加
@Primary。
回顾 determineTransactionManager 的解析顺序:方法级 qualifier > 类级 @Qualifier > transactionManagerBeanName > 按类型取。方法级指定优先级最高,适合在多数据源场景下精确路由。
10.11 常见问题与坑
10.11.1 自调用失效:为什么 this.method() 上的 @Transactional 不生效
这是声明式事务最经典的坑。看下面的代码:
@Service
public class OrderService {
@Autowired private JdbcTemplate jdbc;
public void createOrder(Order order) {
jdbc.update("INSERT INTO orders ...");
this.deduct(order); // ← 自调用
}
@Transactional
public void deduct(Order order) {
jdbc.update("UPDATE stock SET ...");
}
}
createOrder 没有 @Transactional,它内部调用 this.deduct(order)。你期望 deduct 方法被事务包裹,但实际上 deduct 没有事务行为。
源码级解释:声明式事务基于 AOP 代理。容器里的 orderService bean 是一个代理对象,外部调用 orderService.createOrder() 时,调用进入代理 → TransactionInterceptor.invoke(虽然 createOrder 没有 @Transactional,advisor 不匹配,但代理对象本身的方法分发机制仍在)→ 最终调用目标对象的 createOrder 方法。
关键在 createOrder 内部那行 this.deduct(order):这里的 this 是目标对象本身,不是代理对象。this.deduct() 是一次普通的 Java 方法调用,不经过代理、不经过 TransactionInterceptor、不查 TransactionAttributeSource、不调 getTransaction。代理只拦截"从代理对象外部进入的调用",对象内部的方法互调完全绕过了代理链。
回顾装配链路:BeanFactoryTransactionAttributeSourceAdvisor 的切点 TransactionAttributeSourcePointcut.matches 在 bean 创建时判断是否需要给这个 bean 创建代理。代理创建了,但代理对象的 deduct 方法只是"调用拦截器链再调目标 deduct"——目标对象自己调自己的 deduct,根本不走代理入口。
解决方案:
- 注入自己:
@Autowired private OrderService self;然后调self.deduct(order)——通过代理对象调用。 - AopContext.currentProxy():在
exposeProxy = true的配置下,((OrderService) AopContext.currentProxy()).deduct(order)。 - 把 deduct 方法移到另一个 bean:外部 bean 的方法调用自然经过代理。
- 改用 ASPECTJ 模式:
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)通过字节码织入,自调用也能被拦截——但需要 spring-aspects 依赖和织入配置。
10.11.2 非 public 方法上的 @Transactional 不生效
AnnotationTransactionAttributeSource 默认 publicMethodsOnly = true。回顾它的构造函数和 setPublicMethodsOnly 方法:只有 public 方法上的 @Transactional 才会被识别(PROXY 模式下)。这是因为 JDK 动态代理只能代理接口方法,CGLIB 代理虽然能代理类方法,但 Spring 在注解解析阶段就过滤了非 public 方法——这是历史遗留设计,避免在非 public 方法上意外开启事务。
源码层面,AbstractFallbackTransactionAttributeSource 或其相关查找逻辑中,对非 public 方法在 PROXY 模式下直接返回 null(无事务属性),切点匹配失败,代理拦截器不会介入。
如果你一定要在 protected/private 方法上用事务,需要切换到 ASPECTJ 织入模式(publicMethodsOnly=false 才有意义),或者把事务方法改成 public。
10.11.3 异常被 catch 吞掉,事务却还是回滚了
这是另一个高频困惑场景:
@Transactional
public void createOrder(Order order) {
jdbc.update("INSERT INTO orders ...");
try {
stockService.deduct(order); // 内层 REQUIRED 方法抛 RuntimeException
} catch (RuntimeException e) {
log.error("扣库存失败", e);
}
}
很多人以为:我 catch 了异常、没往外抛,外层事务应该正常提交。但实际运行结果是外层事务回滚,并抛 UnexpectedRollbackException。
源码级解释:回顾 handleExistingTransaction 中 REQUIRED 参与外层事务时,内层方法抛异常走 completeTransactionAfterThrowing → rollbackOn(ex)=true → tm.rollback(innerStatus) → processRollback。在 processRollback 的"参与外层事务"分支:
else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
// ...
doSetRollbackOnly(status);
}
}
}
isGlobalRollbackOnParticipationFailure() 默认 true,所以内层 processRollback 会调用 doSetRollbackOnly——对 JDBC 就是 connectionHolder.setRollbackOnly(true)。这一步不真正回滚连接,只是在外层事务的 ConnectionHolder 上打了一个 rollback-only 标记。
内层方法的异常被外层 catch 后,外层 createOrder 方法正常走完,返回,commitTransactionAfterReturning 调 tm.commit(outerStatus)。进入 AbstractPlatformTransactionManager.commit:
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
// ...
processRollback(defStatus, true);
return;
}
isGlobalRollbackOnly() 读的就是 ConnectionHolder 上的 rollback-only 标记——它是 true,所以 commit 转成 processRollback(unexpected=true),真正 connection.rollback(),然后抛出 UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only")。
这就是"catch 了异常事务照样回滚"的完整机制:内层 REQUIRED 失败会把整个外层事务标记为 rollback-only,外层即使 catch 了异常,最终 commit 时也会被检测到并转成回滚。这是 Spring 的默认设计——globalRollbackOnParticipationFailure=true,保证"一个参与方失败,整个事务必须回滚",避免部分提交。
如果希望内层失败后外层能继续,有几种选择:
- 内层方法用
REQUIRES_NEW(独立事务,失败不影响外层)。 - 内层方法用
NESTED(savepoint,内层失败回滚到 savepoint,外层可继续)。 - 把
globalRollbackOnParticipationFailure设为 false(不推荐,文档警告 Hibernate Session 等资源在失败后可能无法继续)。
10.11.4 checked exception 默认不回滚
这个问题在 10.8.4 节已经从源码讲过:DefaultTransactionAttribute.rollbackOn(ex) 只对 RuntimeException 和 Error 返回 true。业务方法抛 IOException 这类 checked 异常时,completeTransactionAfterThrowing 走 commit 分支,事务正常提交——这通常不是业务期望的行为。
解决方式:在注解上显式配置 @Transactional(rollbackFor = Exception.class) 或 rollbackFor = IOException.class。rollbackFor 会被解析成 RuleBasedTransactionAttribute 中的 RollbackRuleAttribute 列表,rollbackOn 方法遍历时找到匹配规则,优先于父类默认行为。
Spring 6.2 起提供了全局开关:@EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS),让无自定义规则的事务默认对所有异常(包括 checked)回滚。这通过 AnnotationTransactionAttributeSource.addDefaultRollbackRule(RollbackRuleAttribute.ROLLBACK_ON_ALL_EXCEPTIONS) 实现——ROLLBACK_ON_ALL_EXCEPTIONS 是 RollbackRuleAttribute 的静态常量(new RollbackRuleAttribute(Exception.class)),它会被追加到每个 RuleBasedTransactionAttribute 的规则列表头部。
10.11.5 REQUIRES_NEW 与 NESTED 的区别(源码级总结)
| 维度 | REQUIRES_NEW | NESTED |
|---|---|---|
| 物理连接 | 新建独立连接,挂起外层连接 | 复用外层连接 |
| 事务边界 | 两个独立物理事务 | 一个物理事务 + savepoint |
| 内层 commit | 真正 connection.commit(),变更持久化 | releaseSavepoint,不真正提交 |
| 内层 rollback | connection.rollback(),内层完全回滚 | rollbackToSavepoint,只撤销到 savepoint |
| 外层失败回滚 | 内层已提交不受影响 | 内层"已提交"的变更随外层一起回滚 |
| ThreadLocal | suspend 外层资源和同步,新建自己的 | 不挂起,复用外层同步集合 |
| 适用场景 | 日志记录、独立审计,与主事务成败无关 | 部分失败可回退、外层继续 |
源码区别在 handleExistingTransaction 中一目了然:REQUIRES_NEW 走 suspend(transaction) + startTransaction(doBegin 获取新连接);NESTED 走 status.createAndHoldSavepoint()(在现有连接上打 savepoint),不调用 doBegin。
10.12 本章小结
本章以 Spring Framework 7.0.9 实际源码为准,完整剖析了声明式事务从装配到运行的全链路。核心脉络可以浓缩为以下几点:
装配侧:@EnableTransactionManagement 通过 @Import(TransactionManagementConfigurationSelector.class) 选择性导入 ProxyTransactionManagementConfiguration,后者注册三个基础设施 bean——BeanFactoryTransactionAttributeSourceAdvisor(组合切点 TransactionAttributeSourcePointcut 与通知 TransactionInterceptor)、TransactionInterceptor(持有 AnnotationTransactionAttributeSource 与默认 PlatformTransactionManager)、以及 AnnotationTransactionAttributeSource(委托 SpringTransactionAnnotationParser 解析 @Transactional 注解)。InfrastructureAdvisorAutoProxyCreator 在 bean 初始化后为匹配的类创建代理。
运行侧主链路:代理方法调用 → TransactionInterceptor.invoke → TransactionAspectSupport.invokeWithinTransaction → createTransactionIfNecessary 调 PlatformTransactionManager.getTransaction → AbstractPlatformTransactionManager.getTransaction 根据 isExistingTransaction 与传播行为分支处理 → 子类 doBegin 获取连接、关 autoCommit、绑定 ConnectionHolder 到 ThreadLocal → 业务方法通过 DataSourceUtils.getConnection 复用同一连接 → 正常返回走 commitTransactionAfterReturning → processCommit 触发 beforeCommit/beforeCompletion 同步回调、调 doCommit、再触发 afterCommit/afterCompletion → cleanupAfterCompletion 解绑资源、恢复 autoCommit、归还连接池。异常路径走 completeTransactionAfterThrowing,由 RuleBasedTransactionAttribute.rollbackOn(ex) 按规则列表(最近匹配规则优先,无规则退回 RuntimeException/Error 默认)决定回滚还是提交,然后原异常继续抛出。
传播行为决策:handleExistingTransaction 按固定 if 顺序分支:NEVER 抛异常、NOT_SUPPORTED 挂起后空事务执行、REQUIRES_NEW 挂起后开新物理事务、NESTED 在现有连接上打 savepoint、REQUIRED/SUPPORTS/MANDATORY 参与现有事务。挂起/恢复通过 SuspendedResourcesHolder 保存 ThreadLocal 属性快照与物理资源,在 cleanupAfterCompletion 中恢复。
资源绑定:TransactionSynchronizationManager 用六个 ThreadLocal 维护线程级事务上下文——resources(ConnectionHolder 绑定)、synchronizations(回调集合)、以及事务名/只读/隔离级别/actualTransactionActive 四个暴露属性。TransactionSynchronization 的回调时序是:beforeCommit → beforeCompletion → doCommit/doRollback → afterCommit(仅提交成功)→ afterCompletion(必调)。
子类扩展点:AbstractPlatformTransactionManager 用模板方法把"变"的部分交给子类——doGetTransaction/isExistingTransaction/doBegin/doSuspend/doResume/doCommit/doRollback/doSetRollbackOnly/doCleanupAfterCompletion。DataSourceTransactionManager 是最典型的实现,doBegin 的核心是 dataSource.getConnection() + setAutoCommit(false) + bindResource,doCleanupAfterCompletion 的核心是 unbind + 恢复 autoCommit + releaseConnection 归还连接池。
理解了这条链路,再看任何"事务不生效"类问题都能定位到具体环节:自调用是没进代理、非 public 是注解解析阶段被过滤、checked 异常不回滚是 rollbackOn 默认规则、catch 后仍回滚是 doSetRollbackOnly 标记了全局 rollback-only、REQUIRES_NEW 不生效是没挂起成功或连接池耗尽。