本文从 Spring 事务抽象模型出发,深入
TransactionInterceptor和AbstractPlatformTransactionManager源码,梳理声明式事务的完整执行链路,并总结 7 类高频踩坑场景及根因分析。
目录
| 章节 | 说明 |
|---|---|
| 核心概念 | 事务抽象、传播行为、隔离级别 |
| 整体架构 | 关键类与职责分工、为何没有 Transaction 实体、TransactionSynchronizationManager 内部结构 |
| 源码分析 | AOP 代理 → 拦截 → 开启/提交/回滚完整链路,含无事务时连接获取 |
| 传播行为深入 | 7 种传播行为的源码处理逻辑,含 ThreadLocal 状态时间线与 Savepoint 流程 |
| 常见坑 | 失效场景与根因 |
核心概念
事务三大抽象接口
| 接口 | 职责 |
|---|---|
PlatformTransactionManager |
顶层接口,定义 getTransaction / commit / rollback |
TransactionDefinition |
事务定义,描述传播行为、隔离级别、超时、是否只读 |
TransactionStatus |
运行时状态,判断是否新事务、是否有 savepoint、是否标记回滚 |
传播行为(Propagation)
| 传播行为 | 说明 |
|---|---|
REQUIRED(默认) |
有事务就加入,无则新建 |
REQUIRES_NEW |
挂起当前事务,强制新建独立事务 |
NESTED |
嵌套事务,基于 JDBC savepoint |
SUPPORTS |
有事务则加入,无则非事务执行 |
NOT_SUPPORTED |
挂起当前事务,强制非事务执行 |
MANDATORY |
必须在已有事务中,否则抛 IllegalTransactionStateException |
NEVER |
必须在无事务环境,否则抛异常 |
隔离级别(Isolation)
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
READ_UNCOMMITTED |
可能 | 可能 | 可能 |
READ_COMMITTED |
❌ | 可能 | 可能 |
REPEATABLE_READ |
❌ | ❌ | 可能 |
SERIALIZABLE |
❌ | ❌ | ❌ |
DEFAULT |
跟随数据库默认 |
MySQL InnoDB 默认
REPEATABLE_READ,且通过 MVCC + Gap Lock 在大多数场景解决了幻读问题。
声明式 vs 编程式
| 方式 | 优点 | 缺点 |
|---|---|---|
@Transactional(声明式) |
代码侵入性低,集中配置 | 无法细粒度控制,有 AOP 失效风险 |
TransactionTemplate(编程式) |
精确控制边界,无 AOP 限制 | 代码侵入性高 |
整体架构
关键类
| 类/接口 | 模块 | 职责 |
|---|---|---|
PlatformTransactionManager |
spring-tx | 顶层抽象接口 |
AbstractPlatformTransactionManager |
spring-tx | 模板方法,封装传播行为核心逻辑 |
DataSourceTransactionManager |
spring-jdbc | JDBC 事务管理器实现 |
TransactionInterceptor |
spring-tx | AOP 拦截器入口,继承 TransactionAspectSupport |
TransactionAspectSupport |
spring-tx | 核心事务逻辑,invokeWithinTransaction 是关键方法 |
TransactionAttributeSource |
spring-tx | 解析 @Transactional 注解属性 |
TransactionSynchronizationManager |
spring-tx | ThreadLocal 管理当前线程的 Connection 和事务资源 |
DefaultTransactionStatus |
spring-tx | 运行时事务状态持有者 |
BeanFactoryTransactionAttributeSourceAdvisor |
spring-tx | 自动注册的 Advisor,触发代理 Bean 创建 |
为何没有 Transaction 实体
Spring JDBC 事务管理没有抽象出独立的 Transaction 实体——事务的本质就是一个 autoCommit=false 的 Connection,Spring 直接复用它,只在外面包了一层状态位。
两层抽象对比:
| 层次 | 类 | 定位 |
|---|---|---|
| 公开 API | TransactionStatus / DefaultTransactionStatus |
状态描述,非事务实体 |
| JDBC 内部 | DataSourceTransactionObject |
package-private,仅持有 ConnectionHolder,不对外暴露 |
| 真正的事务载体 | java.sql.Connection |
autoCommit=false 即开始事务 |
DataSourceTransactionObject 是 DataSourceTransactionManager 的包级私有内部类,doGetTransaction() 返回它只是为了在 doBegin() / doCommit() / doRollback() 中传递状态,并不是一个领域意义上的"事务对象"。
与 JPA 的差异:
| Spring JDBC | Hibernate / JPA | |
|---|---|---|
| 是否有事务实体 | ❌ 无,直接操作 Connection | ✅ org.hibernate.Transaction / EntityTransaction |
| 开启事务 | connection.setAutoCommit(false) |
session.beginTransaction() |
| Spring 适配层 | DataSourceTransactionManager |
JpaTransactionManager |
结论:Spring 事务抽象的核心价值在于传播行为的编排逻辑(
AbstractPlatformTransactionManager),而不是在存储层引入新实体。对 JDBC 而言,ConnectionHolder只是给Connection加了 Spring 需要的状态位(rollbackOnly、savepointAllowed、referenceCount),然后通过 ThreadLocal 与当前线程绑定。
TransactionSynchronizationManager 内部结构
TransactionSynchronizationManager 用一组 ThreadLocal 存储当前线程的全部事务上下文:
// org.springframework.transaction.support.TransactionSynchronizationManager
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources"); // DataSource → ConnectionHolder
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");
resources ThreadLocal 的完整结构:
resources ThreadLocal
└── Map<Object, Object>
└── DataSource(key)→ ConnectionHolder(value)
├── connection 真实 JDBC Connection
├── transactionActive 是否在事务中
├── savepointAllowed 是否支持 Savepoint(NESTED 需要)
└── referenceCount 引用计数,防止嵌套时提前关闭连接
key 的解包:
bindResource(key, value)调用时,先执行TransactionSynchronizationUtils.unwrapResourceIfNecessary(key),将可能是代理的DataSource(如TransactionAwareDataSourceProxy)解包为原始对象,确保bindResource和getResource用同一个 key 能匹配上。
referenceCount的作用:REQUIRED 传播时,内层方法加入外层事务,referenceCount++;内层退出时referenceCount--,降为 0 才真正关闭连接,防止外层事务还在用时被提前归还连接池。
调用链路总览
源码分析
1. AOP 代理的创建
Spring 启动时,InfrastructureAdvisorAutoProxyCreator 扫描所有 Bean,若方法或类上有 @Transactional,则创建代理:
- 实现了接口 → JDK 动态代理
- 无接口,或 Spring Boot 2.x+ 默认 → CGLIB 代理
# Spring Boot 2.x 起默认强制 CGLIB
spring.aop.proxy-target-class=true
2. 拦截入口:TransactionInterceptor#invoke
// TransactionInterceptor.java
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ?
AopUtils.getTargetClass(invocation.getThis()) : null);
// 委托给父类核心方法
return invokeWithinTransaction(invocation.getMethod(), targetClass,
new CoroutinesInvocationCallback() { ... });
}
3. 核心方法:TransactionAspectSupport#invokeWithinTransaction
// TransactionAspectSupport.java(精简)
protected Object invokeWithinTransaction(Method method, Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 1. 解析 @Transactional 属性
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = tas.getTransactionAttribute(method, targetClass);
// 2. 获取事务管理器
final TransactionManager tm = determineTransactionManager(txAttr);
// 3. 开启(或加入)事务
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 4. 执行目标方法
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 5. 异常处理:按 rollbackOn 规则决定回滚还是提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
// 6. 正常提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
4. 开启事务:AbstractPlatformTransactionManager#getTransaction
// AbstractPlatformTransactionManager.java(精简)
@Override
public final TransactionStatus getTransaction(TransactionDefinition definition) {
Object transaction = doGetTransaction(); // 子类实现,获取当前线程事务对象
if (isExistingTransaction(transaction)) {
// 已有事务:根据传播行为决定加入/挂起/拒绝
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 无事务:根据传播行为决定是否新建
if (definition.getPropagationBehavior() == PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException("No existing transaction found...");
}
if (definition.getPropagationBehavior() == PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == PROPAGATION_NESTED) {
return startTransaction(definition, transaction, ...);
}
// SUPPORTS / NOT_SUPPORTED / NEVER → 非事务执行
return prepareTransactionStatus(definition, null, true, ...);
}
5. 绑定连接:DataSourceTransactionManager#doBegin
// DataSourceTransactionManager.java(精简)
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 从连接池获取连接
Connection con = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(con), true);
// 设置隔离级别
DataSourceUtils.prepareConnectionForTransaction(con, definition);
// 关闭自动提交 → 事务正式开始
con.setAutoCommit(false);
// 绑定到 ThreadLocal,同线程后续所有 SQL 共享此 Connection
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(
obtainDataSource(), txObject.getConnectionHolder());
}
}
关键:Spring 事务的传播依赖
TransactionSynchronizationManager的 ThreadLocal。同一线程的所有 DAO 操作取到同一个Connection,共享同一事务。跨线程则天然断开。
6. 无事务时的连接获取
JdbcTemplate 每次执行 SQL 都通过 DataSourceUtils.getConnection(dataSource) 取连接:
// DataSourceUtils#doGetConnection()
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && conHolder.hasConnection()) {
// 有活跃事务 → 复用 ThreadLocal 里的连接,共享同一事务
return conHolder.getConnection();
}
// 没有活跃事务 → 直接从连接池取新连接
Connection con = dataSource.getConnection();
// autoCommit 默认 true → 该 SQL 执行完立即提交,一条 SQL = 一个事务
结论:没有 @Transactional 时,TransactionSynchronizationManager 的 resources 为空 Map,形同虚设。每条 SQL 各自取连接、各自提交,事务隔离完全由数据库的 autoCommit 机制决定。
6. 回滚规则判断
// TransactionAspectSupport#completeTransactionAfterThrowing(精简)
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo.transactionAttribute != null &&
txInfo.transactionAttribute.rollbackOn(ex)) {
// 满足回滚规则 → rollback
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} else {
// 不满足(如 checked exception)→ commit!
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
默认规则:RuntimeException 和 Error 触发回滚,checked Exception 不回滚。
@Transactional(rollbackFor = Exception.class) // 所有异常都回滚
@Transactional(noRollbackFor = BusinessException.class) // 指定异常不回滚
传播行为深入
REQUIRED(默认)
外层事务存在 → 加入(共享同一 Connection 和事务状态)
外层事务不存在 → 新建
⚠️ 内层抛出异常时,AbstractPlatformTransactionManager 会将当前事务标记为 rollbackOnly。外层即使 catch 了异常,最终提交时也会抛出:
UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
REQUIRES_NEW
1. suspend():解绑当前线程 ThreadLocal 中的 ConnectionHolder,挂起外层事务
2. 新建独立 Connection,开启新事务
3. 内层完成(提交或回滚)完全独立于外层
4. resume():重新绑定外层 ConnectionHolder,恢复外层事务
ThreadLocal 状态时间线:
时刻 ThreadLocal resources SuspendedResourcesHolder
────────────────────────────────────────────────────────────────────────────
外层事务开始 DataSource → outerHolder null
调用内层方法 (空) {outerHolder, name, readOnly...} ← suspend()
内层事务执行 DataSource → innerHolder {outerHolder, ...}
内层事务提交 (空) {outerHolder, ...}
恢复外层事务 DataSource → outerHolder null ← resume()
外层事务提交 (空) null
SuspendedResourcesHolder由AbstractPlatformTransactionManager持有为局部变量,跟着调用栈走,不放入 ThreadLocal。这样 REQUIRES_NEW 多层嵌套时,每层都能独立恢复,不会互相覆盖。
常见场景:记录操作日志,无论主业务成功失败都要持久化。
NESTED
有外层事务:在当前 Connection 上创建 JDBC savepoint
内层回滚 → 回滚到 savepoint,外层事务继续
外层回滚 → savepoint 一并回滚
无外层事务:等价于 REQUIRED
Savepoint 三步流程:
① 创建 Savepoint(进入内层方法时)
// AbstractTransactionStatus#createAndHoldSavepoint()
Object savepoint = getSavepointManager().createSavepoint();
holdSavepoint(savepoint); // 存入 TransactionStatus,不动 ThreadLocal
// 底层:connection.setSavepoint("SAVEPOINT_1")
TransactionStatus 数据结构:
DefaultTransactionStatus {
savepoint: Savepoint(JDBC 对象,记录此刻数据库状态快照)
transaction: 指向外层 ConnectionHolder(同一个连接!)
}
② 内层正常完成 → 释放 Savepoint
// AbstractTransactionStatus#releaseHeldSavepoint()
connection.releaseSavepoint(savepoint);
// Savepoint 标记清除,操作永久归入外层事务
③ 内层抛异常 → 回滚至 Savepoint
// AbstractPlatformTransactionManager#processRollback()
if (status.hasSavepoint()) {
status.rollbackToHeldSavepoint();
// connection.rollback(savepoint)
// 只撤销 Savepoint 之后的操作,不关闭连接,外层事务继续
}
NESTED 与 REQUIRES_NEW 数据结构对比:
REQUIRES_NEW:
外层 ConnectionHolder → 暂存 SuspendedResourcesHolder
内层 ConnectionHolder → 绑入 ThreadLocal(新连接)
两个连接,两个独立事务
NESTED:
ThreadLocal 始终只有一个 ConnectionHolder(外层连接)
Savepoint 只是在该连接上打了一个"可回退标记"
内外共用同一事务,内层有独立回滚点
⚠️
NESTED依赖 JDBC savepoint。DataSourceTransactionManager支持;JpaTransactionManager不支持 savepoint,会退化为REQUIRED,行为与预期不符。
REQUIRES_NEW vs NESTED 对比
| 维度 | REQUIRES_NEW | NESTED |
|---|---|---|
| Connection | 新建独立 Connection | 复用同一 Connection |
| 外层回滚影响内层 | 无影响(内层已提交) | 内层随外层回滚 |
| 内层回滚影响外层 | 无影响 | 只回滚到 savepoint |
| 数据库锁 | 两个独立事务,可能死锁 | 同一事务,无锁竞争 |
常见坑
| # | 现象 | 根本原因 | 解决方案 |
|---|---|---|---|
| 1 | 同类内部方法互调事务失效 | Spring AOP 基于运行时代理(JDK/CGLIB),代理对象在容器中,this 指向原始对象绕过代理;AspectJ 织入(CTW/LTW)直接修改字节码,this 调用不失效 |
注入自身 Bean 或改用 TransactionTemplate;彻底解决可用 AspectJ LTW |
| 2 | private 方法上 @Transactional 无效 |
CGLIB 无法代理 private 方法(子类无法覆写);AspectJ 可织入 private 方法 | 改为 public;或用 AspectJ |
| 3 | checked exception 不回滚 | 默认只回滚 RuntimeException/Error |
加 rollbackFor = Exception.class |
| 4 | 多线程中事务失效 | TransactionSynchronizationManager 基于 ThreadLocal |
不要在事务方法内开新线程操作数据库 |
| 5 | @Async + @Transactional 同时使用失效 |
@Async 在新线程执行,脱离原线程事务上下文 |
分离:异步方法内部自行开启事务 |
| 6 | 传统 Spring MVC 双容器事务失效 | Service 被子容器重复扫描,子容器的 Bean 没有 AOP 代理 | 子容器 ComponentScan 严格排除 @Service |
| 7 | REQUIRES_NEW 死锁 |
外层持有行锁,内层新事务等待同一行锁 | 检查锁竞争,调整事务粒度或锁顺序 |
坑 1 详解:自调用失效
@Service
public class OrderService {
// ❌ this.createOrder() 直接调用原始对象,绕过 AOP 代理,事务无效
public void placeOrder() {
this.createOrder();
}
@Transactional
public void createOrder() { ... }
}
修复方案一:注入自身 Bean
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入的是 Spring 代理对象
public void placeOrder() {
self.createOrder(); // ✅ 通过代理调用,事务生效
}
@Transactional
public void createOrder() { ... }
}
修复方案二:编程式事务
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void placeOrder() {
transactionTemplate.execute(status -> {
createOrder(); // ✅ 在事务上下文中执行
return null;
});
}
public void createOrder() { ... }
}
修复方案三:AspectJ LTW(彻底解决,无需改业务代码)
AspectJ LTW(Load-Time Weaving)在类加载时直接修改字节码,切面逻辑织入到 createOrder() 方法体内部,this 调用也会触发事务,从根本上规避代理模型的限制。
启用方式:
<!-- pom.xml 加 aspectjweaver 依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
// 启动类加 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@SpringBootApplication
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class Application { ... }
# JVM 启动参数加 Agent
-javaagent:/path/to/aspectjweaver.jar
为什么 LTW 不失效: JDK/CGLIB 代理是"外壳"模式,
this绕过外壳直达原始对象;AspectJ 织入是"内嵌"模式,切面逻辑已经编译进createOrder()字节码里,this调用的方法本身就含有事务逻辑。
| 方案 | 是否需要改代码 | 是否彻底解决 | 适用场景 |
|---|---|---|---|
| 注入自身 Bean | 需要 | 是 | 少量方法,快速修复 |
TransactionTemplate |
需要 | 是 | 需要精细控制事务边界 |
| AspectJ LTW | 不需要 | 是 | 大规模自调用场景,接受 JVM Agent |
坑 3 详解:checked exception 不回滚
// ❌ IOException 是 checked exception,默认不触发回滚
// 抛出异常时数据已入库,事务却被提交
@Transactional
public void importData() throws IOException {
repository.save(data);
externalService.call(); // 抛出 IOException
}
// ✅ 明确声明回滚规则
@Transactional(rollbackFor = Exception.class)
public void importData() throws IOException { ... }
坑 6 详解:双容器事务失效(传统 Spring MVC)
Root ApplicationContext(父容器)
← 加载 Service、Repository
← AOP 代理在此创建 ✅
Servlet ApplicationContext(子容器)
← 子容器 ComponentScan 配置不当,重复扫描了 Service
← 子容器里的 Service Bean 没有 AOP 代理 ❌
← Controller 注入的是子容器的 Service(无代理)→ 事务失效
根本修复:子容器的 ComponentScan 只扫 @Controller,不扫 @Service:
<!-- Servlet ApplicationContext 配置 -->
<context:component-scan base-package="com.example">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
Spring Boot 用单容器彻底消除了这个问题。
参考资料
- Spring Framework Reference — Transaction Management
- 《Spring 技术内幕》— 第 4 章 Spring AOP 实现原理
评论 (0)
发表评论