专栏文章
专栏文章
Spring Framework 系列
1. Spring Framework 系列 #01:Spring 容器启动流程源码分析 2. Spring Framework 系列 #02:Spring 三级缓存与循环依赖 3. Spring Framework 系列 #03:Spring 事务管理 4. Spring Framework 系列 #04:Spring IoC 与 Bean 生命周期 5. Spring Framework 系列 #05:Spring AOP

Spring Framework 系列 #03:Spring 事务管理

发布于 2026-05-26 09:43 👁 9 次阅读
#源码解析#java#spring

本文从 Spring 事务抽象模型出发,深入 TransactionInterceptorAbstractPlatformTransactionManager 源码,梳理声明式事务的完整执行链路,并总结 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=falseConnection,Spring 直接复用它,只在外面包了一层状态位。

两层抽象对比:

层次 定位
公开 API TransactionStatus / DefaultTransactionStatus 状态描述,非事务实体
JDBC 内部 DataSourceTransactionObject package-private,仅持有 ConnectionHolder,不对外暴露
真正的事务载体 java.sql.Connection autoCommit=false 即开始事务

DataSourceTransactionObjectDataSourceTransactionManager 的包级私有内部类,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 需要的状态位(rollbackOnlysavepointAllowedreferenceCount),然后通过 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)解包为原始对象,确保 bindResourcegetResource 用同一个 key 能匹配上。

referenceCount 的作用:REQUIRED 传播时,内层方法加入外层事务,referenceCount++;内层退出时 referenceCount--,降为 0 才真正关闭连接,防止外层事务还在用时被提前归还连接池。

调用链路总览

spring tx chain


源码分析

1. AOP 代理的创建

Spring 启动时,InfrastructureAdvisorAutoProxyCreator 扫描所有 Bean,若方法或类上有 @Transactional,则创建代理:

# 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 时,TransactionSynchronizationManagerresources 为空 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());
    }
}

默认规则:RuntimeExceptionError 触发回滚,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

SuspendedResourcesHolderAbstractPlatformTransactionManager 持有为局部变量,跟着调用栈走,不放入 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 用单容器彻底消除了这个问题。


参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论