Spring 事务管理抽象:PlatformTransactionManager 与 TransactionDefinition

2 阅读50分钟

概述

衔接前文段落

在前文《数据访问异常体系:从 SQLException 到 DataAccessException》与《JdbcTemplate 的设计与模板方法模式》中,我们系统性地解构了 Spring 如何优雅地处理 JDBC 操作的繁琐与异常不一致问题。我们特别强调了一个关键细节:JdbcTemplate 通过 DataSourceUtils.getConnection() 获取数据库连接,而非直接从 DataSource 获取。这一设计使得 JdbcTemplate 能够“无感”地参与到 Spring 管理的事务中——当有事务存在时,DataSourceUtils 神奇地返回了当前线程绑定的那个连接。这个“魔法”背后的总指挥,便是本文要深入剖析的 Spring 事务抽象层。JdbcTemplate 是优秀的事务“参与者”,但它从不主动开启或终结事务。划定事务边界、决定提交或回滚的“决策者”,正是 PlatformTransactionManager。本文将揭开这个事务管理器的神秘面纱,分析它如何将 JDBC、Hibernate、JTA 等异构技术的事务处理,统一为简洁一致的编程模型,从而为声明式事务(@Transactional)提供坚实的底层运行支柱。

总结性引言

事务管理是企业应用开发中数据一致性的基石,也是最容易出错且最难抽象的环节之一。直接使用 JDBC 事务,开发者需要手动调用 connection.setAutoCommit(false)commit()rollback(),并在错综复杂的异常处理逻辑中确保资源正确释放。更棘手的是,不同数据访问技术(JDBC、Hibernate、MyBatis)和分布式事务框架(JTA)拥有完全迥异的 API 集。如果每切换一种技术都需要重写事务管理代码,系统的可维护性和可移植性将荡然无存。

Spring 事务管理的解耦之道在于定义了一套纯粹、技术无关的抽象。这套抽象的核心是三位一体的接口体系:PlatformTransactionManager 负责划定事务边界(开始、提交、回滚);TransactionDefinition 用于描述事务的元数据属性(传播行为、隔离级别、超时等);TransactionStatus 则代表一个正在运行的事务的句柄,用于在运行时控制事务状态。它们背后的支撑是 TransactionSynchronizationManager,一个利用 ThreadLocal 魔法将数据库连接、Hibernate Session 等资源与当前线程事务进行绑定的核心调度器。这些抽象通过模板方法模式策略模式的精妙组合,将事务管理的通用流程(获取事务、提交、回滚)与具体数据访问技术(JDBC、JTA)的实现细节彻底分离。开发者只需通过配置切换不同的 PlatformTransactionManager 实现,业务代码即可在不同的事务技术之间无缝迁移。本文将带您深入这套抽象的内部世界,剖析“挂起”和“恢复”在传播行为背后的实现机制,追踪一个事务从诞生到消亡的完整生命周期,让您彻底洞悉 Spring 实现技术无关性事务管理的工程智慧。

核心要点

  • 统一事务接口PlatformTransactionManager 及其实现类,以策略模式屏蔽了不同持久化技术的事务 API 差异。
  • 传播行为的底层机制PROPAGATION_REQUIREDREQUIRES_NEWNESTED 等行为的实现,依赖于对当前事务的“挂起”与“恢复”操作,以及数据库 Savepoint 的创建。
  • 线程级事务同步TransactionSynchronizationManager 利用多个 ThreadLocal 变量,将事务资源(如连接)、事务状态和同步回调牢牢绑定在当前执行线程上,这是声明式事务和 JdbcTemplate 无缝集成的基础。
  • 模板方法骨架AbstractPlatformTransactionManager 通过 getTransactioncommitrollback 等模板方法定义了不变的事务处理流程,并将关键的可变步骤(如 doBegindoCommit)延迟给子类实现。
  • JdbcTemplate 的无感集成DataSourceUtils 作为桥梁,在 JdbcTemplate 请求连接时,智能地检查 TransactionSynchronizationManager 是否存在线程绑定的连接,从而实现了“有事务则参与,无事务则新建”的自动协同。
  • 设计模式精粹:模板方法、策略、线程局部存储等模式共同构成了 Spring 事务抽象的设计基石。

文章组织架构图

flowchart TD
    subgraph S1 ["认知基础"]
        n1["1. Spring 事务抽象总览:统一的事务管理模型"]
        n2["2. TransactionDefinition:传播行为、隔离级别与属性定义"]
    end

    subgraph S2 ["核心架构"]
        n3["3. PlatformTransactionManager 的体系结构"]
        n4["4. 事务同步与资源绑定:TransactionSynchronizationManager 的 ThreadLocal 设计"]
        n5["5. AbstractPlatformTransactionManager 的模板方法骨架"]
        n6["6. 传播行为的底层实现(以 DataSourceTransactionManager 为例)"]
    end

    subgraph S3 ["协同与扩展"]
        n7["7. JdbcTemplate 事务参与的完整链路"]
        n8["8. 事务同步回调(TransactionSynchronization)及其应用"]
        n9["9. 事务管理器配置与扩展点"]
    end

    subgraph S4 ["实战闭环"]
        n10["10. 生产事故排查专题"]
        n11["11. 面试高频专题"]
    end

    n1 --> n2 --> n3 --> n4 --> n5 --> n6 --> n7 --> n8 --> n9 --> n10 --> n11

    classDef topic fill:#f8f9fa,stroke:#333,stroke-width:2px,rx:5,color:#333;
    class S1,S2,S3,S4 topic;

架构图说明

  • 总览说明:全文严格遵循“抽象总览 → 核心定义 → 架构骨架 → 机制实现 → 协同工作 → 实战排错”的逻辑递进。从第 1 模块的宏观抽象模型出发,深入第 2 模块的事务属性定义,再进入第 3、4、5 模块的架构核心,剖析接口体系、线程资源管理和模板方法骨架。第 6 模块深入最复杂的传播行为实现,第 7、8 模块揭示事务与 JdbcTemplate 的协同及回调机制。第 9 模块简述扩展点后,第 10、11 模块聚焦于生产实践的避坑与面试能力提升,形成完整的知识闭环。
  • 逐模块说明
    • 模块 1-2:建立 Spring 事务抽象的基本概念,阐述其诞生的背景与核心组成部分,并详细解析事务的属性定义,尤其是七种传播行为。
    • 模块 3-5:进入事务管理器的核心架构。剖析 PlatformTransactionManager 的接口体系、TransactionSynchronizationManager 的线程绑定设计,以及 AbstractPlatformTransactionManager 如何通过模板方法模式统一事务处理流程。
    • 模块 6:聚焦最复杂的机制——以 DataSourceTransactionManager 为例,深入源码分析 PROPAGATION_REQUIREDREQUIRES_NEWNESTED 等传播行为的底层实现。
    • 模块 7-8:揭示 JdbcTemplate 如何自动参与 Spring 事务的完整链路,并介绍事务同步回调机制及其应用场景。
    • 模块 9:简述 PlatformTransactionManager 的自定义与 TransactionAwareDataSourceProxy 等扩展点。
    • 模块 10-11:通过真实的生产事故案例和精心设计的面试题,将理论知识转化为排错与表达能力。
  • 关键结论Spring 事务管理的精髓在于,通过 TransactionSynchronizationManager 的线程绑定机制实现了资源与事务的生命周期同步,并通过 AbstractPlatformTransactionManager 的模板方法骨架将具体技术的事务操作委托给子类,从而以一套统一的 API 实现了事务管理的技术无关性。

1. Spring 事务抽象总览:统一的事务管理模型

在深入源码之前,我们首先从问题域出发,理解 Spring 为何要设计这样一套事务抽象,以及它试图解决的痛点是什么。

1.1 原生事务 API 的混乱之治

在没有统一抽象的情况下,处理事务是一件极其痛苦的事情。开发者需要直接面对不同技术栈的底层事务 API:

1. 原生 JDBC 事务

Connection conn = null;
try {
    conn = dataSource.getConnection();
    conn.setAutoCommit(false); // 开启事务
    // ... 执行 JDBC 操作 ...
    conn.commit(); // 提交事务
} catch (SQLException e) {
    if (conn != null) {
        try {
            conn.rollback(); // 回滚事务
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
    throw new RuntimeException(e);
} finally {
    if (conn != null) {
        try {
            conn.close(); // 释放连接
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

这段样板代码充斥着手动的资源管理和异常处理,不仅冗余,而且容易出错(例如,忘记关闭连接或在 finally 块中再次抛出异常覆盖原始异常)。

2. Hibernate 事务

Session session = null;
Transaction tx = null;
try {
    session = sessionFactory.openSession();
    tx = session.beginTransaction(); // 开启事务
    // ... 执行 Hibernate 操作 ...
    tx.commit(); // 提交事务
} catch (RuntimeException e) {
    if (tx != null) {
        tx.rollback(); // 回滚事务
    }
    throw e;
} finally {
    if (session != null) {
        session.close();
    }
}

虽然相比 JDBC 有所简化,但 Hibernate 的事务 API 是 org.hibernate.Transaction,与 JDBC 的 java.sql.Connection 完全不同。

3. JTA 分布式事务

UserTransaction utx = null;
try {
    InitialContext ctx = new InitialContext();
    utx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
    utx.begin(); // 开启分布式事务
    // ... 操作多个资源(数据库、消息队列)...
    utx.commit(); // 提交
} catch (Exception e) {
    if (utx != null) {
        utx.rollback();
    }
    throw new RuntimeException(e);
}

JTA 的事务管理又完全是另一套 API,依赖于 JNDI 查找和 javax.transaction.UserTransaction 接口。

这种碎片化的 API 现状意味着,如果业务代码直接依赖上述任何一种技术,当需要从单一数据库扩展到多数据源,或者从 JDBC 迁移到 Hibernate 时,所有事务管理代码都必须重写。这无疑是一场灾难。

1.2 Spring 的解耦之道:三位一体的抽象

Spring 的解决方案是定义一套顶层抽象,将事务管理的“做什么”(开启、提交、回滚)与“怎么做”(JDBC 还是 Hibernate)彻底分离。这套抽象由三个核心接口组成,它们共同构成了 Spring 事务管理的“铁三角”。

接口/类核心职责比喻
PlatformTransactionManager事务管理器的策略接口,定义了创建、提交和回滚事务的基本操作。事务的执行官,负责具体执行事务的边界操作。
TransactionDefinition事务的定义,携带了事务的元数据属性,如传播行为、隔离级别、超时、只读等。事务的策划书,规定了这次事务的属性。
TransactionStatus代表一个活跃事务的当前状态,提供了检查事务状态、设置回滚点等运行时控制能力。事务的进度追踪器,记录了事务运行过程中的各种状态。

它们之间的关系可以这样理解:一个核心事务管理器的配置,外加一个附带事务属性的请求,最终生成了一个代表该事务的状态对象。

classDiagram
    class PlatformTransactionManager {
        <<interface>>
        +getTransaction(TransactionDefinition) TransactionStatus
        +commit(TransactionStatus)
        +rollback(TransactionStatus)
    }
    
    class TransactionDefinition {
        <<interface>>
        +getPropagationBehavior() int
        +getIsolationLevel() int
        +getTimeout() int
        +isReadOnly() boolean
        +getName() String
    }
    
    class TransactionStatus {
        <<interface>>
        +isNewTransaction() boolean
        +hasSavepoint() boolean
        +setRollbackOnly()
        +isRollbackOnly() boolean
        +isCompleted() boolean
    }

    PlatformTransactionManager ..> TransactionStatus : creates
    PlatformTransactionManager ..> TransactionDefinition : uses
    TransactionStatus ..> TransactionDefinition : associated with

图表说明:

  • 核心实体:图中展示了 Spring 事务抽象的三大核心接口:PlatformTransactionManagerTransactionDefinitionTransactionStatus。它们是整个事务体系的基石。
  • 关系与职责
    • PlatformTransactionManager 是事务管理的入口,它接收一个 TransactionDefinition(事务定义)作为参数,并据此创建一个 TransactionStatus(事务状态)对象。
    • TransactionStatus 对象在事务的生命周期内被 PlatformTransactionManagercommitrollback 方法所使用,以控制事务的最终结局。
    • TransactionStatus 间接关联着创建它时使用的 TransactionDefinition,例如,你可以通过它查询当前事务是否是只读的。
  • 关键结论:这种“三位一体”的设计,将事务的配置、执行和运行时状态完美解耦。TransactionDefinition 负责描述“要一个什么样的事务”,PlatformTransactionManager 负责“怎么去管理这个事务”,而 TransactionStatus 则负责“这个事务现在怎么样了”。这使得事务管理流程可以通过接口进行标准化,而具体的技术实现细节则被隐藏在接口之后。

通过这套抽象,开发者就无需再关心底层的 JDBC Connection 或 Hibernate Session,他们只需要与这三个接口打交道即可。这正是 Spring 事务管理模型实现技术无关性的第一层,也是最重要的一层。

2. TransactionDefinition:传播行为、隔离级别与属性定义

TransactionDefinition 接口是事务的“DNA”,它定义了事务的五个核心属性。理解这些属性,尤其是传播行为,是掌握 Spring 事务管理的关键。

2.1 传播行为(Propagation Behavior)

传播行为定义了当事务方法被另一个事务方法调用时,事务应该如何传播。它是 Spring 事务管理中最复杂也最强大的特性之一。Spring 共定义了七种传播行为,均在 TransactionDefinition 接口中以常量形式存在。

常量名称描述
PROPAGATION_REQUIRED0默认行为。如果当前存在事务,则加入该事务;如果没有,则创建一个新事务。这是最常用的选择。
PROPAGATION_SUPPORTS1如果当前存在事务,则加入该事务;如果没有,则以非事务方式执行。
PROPAGATION_MANDATORY2强制要求当前必须存在事务,如果不存在则抛出异常。
PROPAGATION_REQUIRES_NEW3无论当前是否存在事务,都创建一个新事务。如果当前存在事务,则将当前事务挂起。
PROPAGATION_NOT_SUPPORTED4以非事务方式执行。如果当前存在事务,则将当前事务挂起。
PROPAGATION_NEVER5以非事务方式执行,并强制要求当前不能存在事务,如果存在则抛出异常。
PROPAGATION_NESTED6如果当前存在事务,则在嵌套事务内执行(通过保存点实现);如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

其中,PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED 是开发中最常用的三种,也是实现最复杂的逻辑所在,我们将在第 6 模块深入它们的底层实现。

2.2 隔离级别(Isolation Level)

隔离级别定义了一个事务可能受其他并发事务活动影响的程度,用于解决脏读、不可重复读、幻读等并发问题。

常量名称描述
ISOLATION_DEFAULT-1使用底层数据存储的默认隔离级别。对于大多数数据库,通常是 READ_COMMITTED
ISOLATION_READ_UNCOMMITTED1读取未提交数据(脏读),基本不使用。
ISOLATION_READ_COMMITTED2读取已提交数据,防止脏读。这是大多数主流数据库的默认级别(如 Oracle)。
ISOLATION_REPEATABLE_READ4可重复读,防止脏读和不可重复读(MySQL InnoDB 的默认级别)。
ISOLATION_SERIALIZABLE8串行化,防止所有并发问题,但性能开销最大。

这些常量值直接对应于 JDBC 标准中的隔离级别常量,Spring 仅扮演“翻译”的角色,将定义传递给底层 Connection

2.3 其他属性与默认实现

除了传播行为和隔离级别,TransactionDefinition 还定义了:

  • 超时(Timeout):事务在自动回滚之前可以运行的最长时间(秒)。
  • 只读(Read-Only):一个优化提示,告知底层数据访问层该事务只包含读取操作。在 Hibernate 中,这可以禁用脏检查,从而获得性能提升。
  • 名称(Name):事务的名称,主要用于日志和调试,通常默认为调用方法全名。

Spring 提供了 DefaultTransactionDefinition 这个基础实现,其默认值为: PROPAGATION_REQUIREDISOLATION_DEFAULTTIMEOUT_DEFAULT(-1,表示永不超时)、非只读。我们编程式创建事务或使用 @Transactional 注解时,未显式指定的属性都会继承这些默认值。

// org.springframework.transaction.support.DefaultTransactionDefinition
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
    private int propagationBehavior = PROPAGATION_REQUIRED; // 默认传播行为
    private int isolationLevel = ISOLATION_DEFAULT;        // 默认隔离级别
    private int timeout = TIMEOUT_DEFAULT;                 // 默认超时
    private boolean readOnly = false;                      // 默认非只读
    // ...
}

通过灵活组合这些属性,开发者可以精准地控制事务的边界和行为,而这一切都完全独立于具体的事务技术实现。

3. PlatformTransactionManager 的体系结构

PlatformTransactionManager 是整个事务抽象的核心,它是策略模式的典范。它为上层(如 AOP 拦截器)提供统一的事务操作接口,而它的不同实现则封装了与特定技术打交道的事务策略。

3.1 核心接口:三个标准操作

PlatformTransactionManager 接口极其精简,只有三个方法,清晰地定义了事务生命周期中必须的操作。

// org.springframework.transaction.PlatformTransactionManager
public interface PlatformTransactionManager {

    /**
     * 根据给定的事务定义,获取一个当前活跃或新创建的事务。
     * @param definition 描述新事务属性的 TransactionDefinition 实例。
     * @return 代表新事务或当前事务的 TransactionStatus 对象。
     * @throws TransactionException 当事务系统发生不可恢复的故障时抛出。
     */
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    /**
     * 根据给定事务的状态提交事务。如果事务仅被标记为回滚,则执行回滚。
     * @param status 由 getTransaction 返回的 TransactionStatus 对象。
     * @throws TransactionException 当提交或回滚发生错误时抛出。
     */
    void commit(TransactionStatus status) throws TransactionException;

    /**
     * 根据给定事务的状态回滚事务。
     * @param status 由 getTransaction 返回的 TransactionStatus 对象。
     * @throws TransactionException 当回滚发生错误时抛出。
     */
    void rollback(TransactionStatus status) throws TransactionException;
}

源码解读:

  • 职责单一:接口只定义了如何开启一个事务(getTransaction)、如何完成一个事务(commitrollback)。它完全不关心事务是如何被创建的(由 AOP 编程式触发还是编程式触发),也不关心事务属性从何而来(注解还是代码定义),只关心事务本身的处理。
  • getTransaction 的双重语义:这个方法名看似是“获取一个事务”,但它实际上承担了处理传播行为的逻辑。在某些传播行为下(如 REQUIRED),它可能返回当前已存在的事务,而不是创建一个新的。这为声明式事务的无缝叠加调用提供了可能。
  • commit 的智能处理:请注意注释中的“如果事务仅被标记为回滚,则执行回滚”。这意味着一个被调用的方法可以通过 TransactionStatus.setRollbackOnly() 将事务标记为不可提交,即使调用方决定提交,最终也会触发回滚。这是 Spring 事务协作中的一个重要机制。
  • 统一的 TransactionException:所有方法抛出的都是 TransactionException 这一抽象异常,它将底层技术的特定异常(如 SQLException)包装起来,与我们在第 1 篇《数据访问异常体系》中分析的 DataAccessException 设计理念一脉相承。

3.2 实现类的谱系:适配不同持久化技术

Spring 为 PlatformTransactionManager 接口提供了丰富的实现,以适配不同的持久化技术。这些实现都通过继承模板基类 AbstractPlatformTransactionManager 来实现公共流程的重用,只需专注于特定技术的“钩子”实现。

classDiagram
    class PlatformTransactionManager {
        <<interface>>
        +getTransaction(TransactionDefinition)
        +commit(TransactionStatus)
        +rollback(TransactionStatus)
    }
    
    class AbstractPlatformTransactionManager {
        <<abstract>>
        +getTransaction(TransactionDefinition)
        +commit(TransactionStatus)
        +rollback(TransactionStatus)
        #doGetTransaction() TransactionStatus
        #doBegin(Object, TransactionDefinition)
        #doCommit(DefaultTransactionStatus)
        #doRollback(DefaultTransactionStatus)
        #doSuspend(Object) Object
        #doResume(Object, Object)
        #doCleanupAfterCompletion(Object)
    }
    
    class DataSourceTransactionManager {
        -DataSource dataSource
        +doBegin(Object, TransactionDefinition)
        +doCommit(DefaultTransactionStatus)
        +doRollback(DefaultTransactionStatus)
        +doSuspend(Object) Object
        +doResume(Object, Object)
    }
    
    class JtaTransactionManager {
        +doBegin(Object, TransactionDefinition)
        +doCommit(DefaultTransactionStatus)
        +doRollback(DefaultTransactionStatus)
    }
    
    class HibernateTransactionManager {
        +doBegin(Object, TransactionDefinition)
        +doCommit(DefaultTransactionStatus)
        +doRollback(DefaultTransactionStatus)
        +doSuspend(Object) Object
        +doResume(Object, Object)
    }

    PlatformTransactionManager <|.. AbstractPlatformTransactionManager
    AbstractPlatformTransactionManager <|-- DataSourceTransactionManager
    AbstractPlatformTransactionManager <|-- JtaTransactionManager
    AbstractPlatformTransactionManager <|-- HibernateTransactionManager

图表说明:

  • 体系结构:图展示了 PlatformTransactionManager 接口、其抽象骨架类 AbstractPlatformTransactionManager,以及三个具有代表性的具体实现之间的关系。
  • AbstractPlatformTransactionManager 的枢纽作用:作为模板方法模式的核心,AbstractPlatformTransactionManager 实现了 getTransactioncommitrollback 的通用流程(如传播行为处理、同步回调等),并将特定于技术的操作(如真正在 JDBC Connection 上调用 commit)定义为 protected abstract 的钩子方法,如 doBegindoCommit 等,交由子类实现。
  • 具体实现的选择
    • DataSourceTransactionManager:为单个 DataSource 提供最基础的 JDBC 事务管理。通过 ConnectionsetAutoCommitcommitrollback 来控制事务。
    • JtaTransactionManager:委托给 JTA 实现分布式事务管理,通常在应用服务器环境下使用。
    • HibernateTransactionManager:专门为 Hibernate 的 SessionFactory 设计,基于 org.hibernate.Transaction 管理事务。
  • 设计意图:这个体系结构完美诠释了“针对接口编程”和“开闭原则”。上层调用者(如事务 AOP 拦截器)只需要依赖 PlatformTransactionManager 接口;而扩展一个新的持久化技术时,只需要继承 AbstractPlatformTransactionManager 并实现对应的钩子方法,无需修改任何现有代码。

回到我们前文的核心,DataSourceTransactionManager 将是本文后续分析的焦点,因为它是连接 JdbcTemplate 与 Spring 事务管理的纽带。

4. 事务同步与资源绑定:TransactionSynchronizationManager 的 ThreadLocal 设计

如果说 PlatformTransactionManager 是事务管理的“大脑”,那么 TransactionSynchronizationManager 就是“中枢神经系统”。它是一个完全静态的辅助类,通过一组 ThreadLocal 变量,将事务资源和状态牢牢地绑定在当前执行的线程上,为 Spring 事务的技术无关性提供了最底层的支撑。

4.1 核心 ThreadLocal 变量

TransactionSynchronizationManager 维护了多个关键的 ThreadLocal 变量,它们各司其职。

// org.springframework.transaction.support.TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {

    // 1. 资源绑定:将 DataSource 等资源标识符 映射到 其对应的资源持有者(如 ConnectionHolder)
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    // 2. 事务同步回调:存储所有为当前事务注册的回调接口
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");

    // 3. 当前事务名称
    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");

    // 4. 当前事务只读状态
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");

    // 5. 当前事务隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");

    // 6. 当前事务是否活跃
    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");
            
    // ... 省略 getter/setter 方法
}

源码解读:

  • resources — 核心中的核心:它是一个 Map,Key 是某个资源的唯一标识符(通常是 DataSource 对象本身),Value 是该资源的“持有者”(例如 ConnectionHolder,其中封装了实际的 JDBC Connection)。正是这个 Map,实现了“同一个事务使用同一个连接”这一关键保证。
  • synchronizations — 扩展点基石:存储了一组 TransactionSynchronization 回调接口。开发者可以在事务的不同阶段(提交前、提交后、完成等)插入自定义逻辑。@TransactionalEventListener 的底层机制就依赖于此。
  • 状态线程安全:所有变量都是 ThreadLocal 的,天然保证了线程安全性。在同一个线程内,任何地方(JdbcTemplate、AOP 拦截器、自定义 Service)都可以通过 TransactionSynchronizationManager 获取到当前线程事务的上下文信息,而无需层层传递。

4.2 资源绑定的核心操作:bindResource 与 unbindResource

事务开始时,DataSourceTransactionManager 会将获取到的 JDBC Connection 绑定到 TransactionSynchronizationManager.resources 上。我们来看绑定和解绑的源码。

// org.springframework.transaction.support.TransactionSynchronizationManager

public static void bindResource(Object key, Object value) throws IllegalStateException {
    // 1. 获取实际的资源 Key,例如解开代理等
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    
    // 2. 获取当前线程的资源 Map,如果不存在则初始化一个
    Map<Object, Object> map = resources.get();
    // 如果当前线程还没有 Map,则创建一个 HashMap 并放入 ThreadLocal
    if (map == null) {
        map = new HashMap<>();
        resources.set(map);
    }
    
    // 3. 检查是否已经有资源绑定,防止事务资源的冲突
    Object oldValue = map.put(actualKey, value);
    if (oldValue != null) {
        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
                actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
}

public static void unbindResource(Object key) throws IllegalStateException {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Map<Object, Object> map = resources.get();
    if (map == null || map.remove(actualKey) == null) {
        throw new IllegalStateException(
                "No value for key [" + actualKey + "] bound to thread");
    }
    // ... 移除后如果 Map 为空,清理 ThreadLocal
}

源码解读:

  • ThreadLocal 的懒加载resources 对应的 Map 并非一开始就创建,而是在第一次 bindResource 调用时按需创建(if (map == null) { map = new HashMap<>(); })。这是一种资源优化的实践。
  • 唯一性保证bindResource 在放入新的资源之前,会检查是否已存在绑定。一个线程对同一个 DataSource 在一个事务中只能绑定一个连接。这强制保证了事务内连接的唯一性。
  • DataSourceUtils 的配合:在模块 7 我们会看到,DataSourceUtils.getConnection 正是通过调用 TransactionSynchronizationManager.getResource(dataSource) 来尝试获取这个“已绑定”的连接,从而实现对当前事务的“感知”。

4.3 ThreadLocal 的设计意图:透明的事务上下文传播

为什么使用 ThreadLocal?其根本目的在于在特定线程的任意深度调用栈中,透明地共享事务上下文。当一个 Service 方法通过 AOP 开启事务时,事务信息被绑定到当前线程。此后,该线程上任何被调用的 DAO 方法、JdbcTemplate 操作,都可以通过 TransactionSynchronizationManager “无感”地获取到当前线程的事务和连接,而无需通过参数传递。这完美地解决了跨层调用时上下文传递的难题,也为声明式事务和 JdbcTemplate 的无缝协作提供了技术前提。

这个机制也与前文《JdbcTemplate 的设计与模板方法模式》中强调的DataSourceUtils.getConnection的行为紧密关联。JdbcTemplate 自己不管理事务,它只是一个“使用者”,它通过 DataSourceUtils 来获取连接,而 DataSourceUtils 又去 TransactionSynchronizationManager 这个“调度中心”查询是否有现成的连接,从而决定是“参与”现有事务还是“创建”一个新的无事务连接。

sequenceDiagram
    participant AOP as 事务AOP拦截器
    participant TSM as TransactionSynchronizationManager
    participant DSM as DataSourceTransactionManager
    participant DS as DataSource

    AOP->>+DSM: getTransaction(def)
    DSM->>DSM: doGetTransaction()<br>创建TransactionStatus
    DSM->>DSM: doBegin(status, def)
    DSM->>+DS: getConnection()
    DS-->>-DSM: return new Connection (JDBC Conn)
    Note over DSM: con.setAutoCommit(false)
    DSM->>+TSM: bindResource(ds, ConnectionHolder(conn))
    TSM-->>-DSM: ThreadLocal.set(map{ds->holder})
    DSM-->>-AOP: return TransactionStatus
    
    Note over AOP: 开始执行业务方法链<br>... -> JdbcTemplate.execute(...)
    
    JDBCTemplate->>+DS: DataSourceUtils.getConnection(ds)
    DS->>+TSM: getResource(ds)
    TSM-->>-DS: return ConnectionHolder(conn)
    DS-->>-JDBCTemplate: return conn (属于当前事务)
    Note over JDBCTemplate: 执行SQL操作

图表说明:

  • 参与者:事务 AOP 拦截器(事务AOP拦截器)、事务管理器(DSM)、同步管理器(TSM)、数据源(DS)和 JdbcTemplate
  • 生命线:清晰地展示了两个阶段。阶段1:事务开启时,DataSourceTransactionManagerDataSource 获取一个 Connection,设置 autoCommitfalse 后,将其封装到 ConnectionHolder 并注册到 TransactionSynchronizationManagerThreadLocal Map 中。阶段2:当后续的 JdbcTemplate 执行 execute 时,通过 DataSourceUtilsTransactionSynchronizationManager 查询是否存在绑定连接,并直接获取到阶段1中绑定的同一个连接。
  • 关键交互:箭头 bindResourcegetResource 是连接复用的核心。正是这两个操作,确保了在同一个线程的事务边界内,所有 JdbcTemplate 操作使用的都是同一个数据库连接。
  • 关键结论:这张图直观地揭示了 Spring 事务同步与资源绑定的核心机制。TransactionSynchronizationManager 充当了线程级的“资源注册中心”,事务管理器负责“注册”资源,而 DataSourceUtils(代表 JdbcTemplate)负责“发现并使用”资源,从而以优雅的、非侵入的方式实现了事务的传播与管理。

5. AbstractPlatformTransactionManager 的模板方法骨架

AbstractPlatformTransactionManager 是 Spring 事务管理的基石,它使用模板方法模式定义了事务处理的通用、不可变的主流程,并将可变部分延迟给子类实现。理解这个骨架,就读懂了 Spring 事务处理的核心流程。

5.1 getTransaction():事务获取的模板流程

getTransaction 方法是所有一切的入口,它完成了传播行为处理和事务创建的完整逻辑。

// org.springframework.transaction.support.AbstractPlatformTransactionManager
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
        throws TransactionException {

    // 1. 获取事务对象。这是一个钩子方法,由子类实现,
    // 用于获取特定于技术的事务对象(对于JDBC是ConnectionHolder)。
    Object transaction = doGetTransaction();

    // ... 缓存debug日志 ...

    if (definition == null) {
        // 使用默认的事务定义
        definition = new DefaultTransactionDefinition();
    }

    // 2. 检查当前线程是否已经存在一个活跃事务
    if (isExistingTransaction(transaction)) {
        // 3. 如果存在,则根据传播行为处理与现有事务的关系
        // 这是传播行为实现的核心
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }

    // 4. 检查事务定义中的超时设置是否合法
    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }

    // 5. 传播行为为PROPAGATION_MANDATORY,但未发现现有事务,抛出异常
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
                "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    
    // 6. 处理PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW等需要创建新事务的行为
    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
            definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // 挂起空事务占位符(用于处理挂起/恢复的一致性)
        SuspendedResourcesHolder suspendedResources = suspend(null);
        try {
            // 7. 启动一个新事务
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        } catch (RuntimeException | Error ex) {
            // 失败时,恢复之前挂起的空事务
            resume(null, suspendedResources);
            throw ex;
        }
    }
    
    // 8. 处理PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
    else {
        // ... 创建“空”事务状态,即以非事务方式运行 ...
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
    }
}

源码深度解读:

  • final 方法getTransaction 被声明为 final,这强制了任何子类都不能修改这个模板流程,从而保证了事务处理行为的一致性。这正是模板方法模式的精髓。
  • 步骤 1:doGetTransaction():这是一个钩子方法。对于 DataSourceTransactionManager,它返回一个 DataSourceTransactionObject,其中持有从 TransactionSynchronizationManager 获取的当前线程绑定的 ConnectionHolder(可能为 null)。
  • 步骤 2:isExistingTransaction(transaction):第二个钩子方法。DataSourceTransactionManager 的实现是检查 DataSourceTransactionObject 中的 ConnectionHolder 是否存在且其内部事务是否活跃。
  • 步骤 3:handleExistingTransaction(…):传播行为的核心处理逻辑,我们将在模块 6 中详细剖析。
  • 步骤 6-7:新建事务:对于 REQUIREDREQUIRES_NEWNESTED 这些需要新事务的传播行为,此处的处理非常精巧。suspend(null) 挂起了一个空事务,这是为了利用 Spring 的挂起/恢复机制,确保即使在新建事务失败时,也能通过 resume(null, suspendedResources) 恢复到初始状态。这体现了代码逻辑的严密性。
  • 关联前文知识:你在前文学习的模板方法模式在这里得到了最顶级的应用。整个复杂的事务获取流程被固定在一个骨架方法中,而关键的“如何获取事务对象”、“如何判断事务存在”等细节,则完全由具体的技术实现来决定。

5.2 commit() 与 rollback():事务终结的模板流程

getTransaction 类似,commitrollback 也是模板方法,它们处理了回调、异常、资源清理等通用逻辑。

commit 方法的骨架逻辑:

  1. 判断状态:检查 TransactionStatus 是否已完成。若已完成,抛出异常。
  2. 检查回滚标记:通过 status.isRollbackOnly() 检查事务是否被标记为“必须回滚”。若是,则转而调用 rollback 方法。
  3. 触发 beforeCommit 回调:遍历 TransactionSynchronizationManager 中注册的 TransactionSynchronization,调用其 beforeCommit(onReadOnly) 方法。
  4. 执行具体提交:调用钩子方法 doCommit(status),由子类(如 DataSourceTransactionManager)执行真正的 connection.commit()
  5. 触发 afterCommit 回调:提交成功后,调用注册的 TransactionSynchronizationafterCommit() 方法。
  6. 触发 beforeCompletion / afterCompletion 回调:无论成功与否,都会调用这些回调,执行最终清理工作。
  7. 最终清理:调用 doCleanupAfterCompletion(status.getTransaction()) 钩子方法,由子类清理资源(如关闭 JDBC Connection),并从 TransactionSynchronizationManager 中解除所有绑定。

在一个异常发生后,rollback 方法会触发。它的流程与 commit 类似,但会调用 doRollback 钩子,并根据异常类型决定是否对调用者透明。

5.3 留给子类的钩子方法(Hook Methods)

这些是子类必须实现的核心方法,它们构成了模板方法模式中的“可变部分”。

钩子方法DataSourceTransactionManager中的职责
doGetTransaction()创建一个 DataSourceTransactionObject,并从 TransactionSynchronizationManager 获取当前绑定的 ConnectionHolder
isExistingTransaction(Object txObj)检查 DataSourceTransactionObject 中的 ConnectionHolder 是否有效且存在活跃事务。
doBegin(Object txObj, TransactionDefinition def)1. 获取新的 JDBC Connection
2. con.setAutoCommit(false)
3. 将连接封装成 ConnectionHolder 并通过 bindResource 绑定到线程。
doCommit(DefaultTransactionStatus status)通过 status.getTransaction() 获取 ConnectionHolder,调用 con.commit()
doRollback(DefaultTransactionStatus status)通过 status.getTransaction() 获取 ConnectionHolder,调用 con.rollback()
doSuspend(Object txObj)1. 解除 DataSource 的资源绑定。
2. 返回一个持有被解绑连接和状态的 SuspendedResourcesHolder
doResume(Object txObj, Object suspendedRes)suspendedRes 中的资源重新绑定到当前线程。
doCleanupAfterCompletion(Object txObj)清理 ConnectionHolder,如果连接不与 Hibernate 等ORM共享,则关闭连接。

可以看到,AbstractPlatformTransactionManager 已经将所有脏活累活(传播行为判断、同步管理、状态跟踪)都做完了,具体的 DataSourceTransactionManager 只需专注于如何“在 JDBC Connection 上”完成这些动作。这种分离使得整个框架既强大又容易扩展。

6. 传播行为的底层实现(以 DataSourceTransactionManager 为例)

传播行为是 Spring 事务管理中最具挑战性的部分。现在,我们结合 AbstractPlatformTransactionManager 的模板骨架和 DataSourceTransactionManager 的钩子实现,深入剖析三种核心传播行为的底层机制。

6.1 PROPAGATION_REQUIRED:忠实跟随者

这是默认且最常用的传播行为。其核心逻辑是:“你需要事务吗?我来为你创建;你已经有了吗?那我和你一起用。”

AbstractPlatformTransactionManager.getTransaction() 中,逻辑非常清晰:

  1. 调用 doGetTransaction(),尝试获取线程绑定的事务对象。
  2. 调用 isExistingTransaction(transaction)。如果返回 true,则进入 handleExistingTransaction
  3. handleExistingTransaction 中,对于 PROPAGATION_REQUIRED,代码直接 return,复用当前事务。
// org.springframework.transaction.support.AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled)
        throws TransactionException {

    // ... 其他传播行为 ...

    // PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY
    // 等行为都是直接参与当前事务
    return prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
}

如果 isExistingTransaction 返回 false,则流程会走到模块 5.1 中分析的步骤 6-7,调用 startTransaction -> doBegin。在 DataSourceTransactionManager.doBegin 中,一个新连接被获取、autoCommit 被关闭,并绑定到当前线程。

// org.springframework.jdbc.datasource.DataSourceTransactionManager
@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();
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        // *** 关键操作:关闭自动提交,开启事务 ***
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            con.setAutoCommit(false);
        }
        
        // ... 准备只读和超时设置 ...

        // *** 关键操作:将连接Holder绑定到当前线程 ***
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
        }

        // ... 
    } catch (Throwable ex) {
        // 异常时释放连接
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.unbindResource(obtainDataSource());
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}

源码解读:

  • 首次进入 doBegin 时,txObject 没有 ConnectionHolder。因此,它会从 DataSource 获取一个新连接,并设置 setAutoCommit(false)
  • 此后,任何进入该事务的 PROPAGATION_REQUIRED 方法,都会在 isExistingTransaction 中发现线程已绑定了 Connection,从而复用该连接。它们共享同一个提交或回滚操作。

6.2 PROPAGATION_REQUIRES_NEW:特立独行者

REQUIRES_NEW 总是要求一个全新的、独立的事务。其逻辑远比 REQUIRED 复杂,关键在于“挂起”和“恢复”机制。

handleExistingTransaction 方法中,当检测到传播行为是 PROPAGATION_REQUIRES_NEW 时:

// org.springframework.transaction.support.AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(...) {
    // ...
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        // 1. 挂起当前事务
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            // 2. 创建一个全新的事务
            return startTransaction(definition, transaction, debugEnabled, suspendedResources);
        } catch (RuntimeException | Error beginEx) {
            // 3. 如果创建失败,则恢复先前挂起的事务
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }
    // ...
}

suspend 方法会调用 doSuspend 钩子。对于 DataSourceTransactionManager

// org.springframework.jdbc.datasource.DataSourceTransactionManager
@Override
protected Object doSuspend(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // 1. 移除 ConnectionHolder 对当前线程的绑定
    txObject.setConnectionHolder(null);
    // 2. 从 TransactionSynchronizationManager 中解绑并获取原始的 ConnectionHolder
    return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

源码解读:

  • suspend 操作本质上是将当前线程与事务资源解绑。TransactionSynchronizationManager.unbindResource 会从 ThreadLocal 中移除该 DataSource 对应的 ConnectionHolder,并将其返回。
  • 这个返回的 ConnectionHolder 被包装进一个 SuspendedResourcesHolder 对象中,并保存在新事务的 TransactionStatus 里。此时,当前线程处于无事务状态。

随后,startTransaction 被调用,它会像开启一个全新事务一样,调用 doBegin,从 DataSource 获取一个新的 JDBC Connection,并绑定到线程。这样,内层方法就运行在一个完全独立的新连接和事务上。

当内层事务提交或回滚后,AbstractPlatformTransactionManager.cleanupAfterCompletion 会调用 resume 方法,最终调用 doResume

// org.springframework.jdbc.datasource.DataSourceTransactionManager
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
    // 1. 将之前挂起的资源重新绑定到当前线程
    TransactionSynchronizationManager.bindResource(
            obtainDataSource(), suspendedResources);
}

源码解读:

  • doResume 操作是 doSuspend 的逆过程,它将之前解绑的 ConnectionHolder 重新放回 TransactionSynchronizationManagerThreadLocal 中,从而“恢复”外层事务的上下文,后续的外层方法可以继续使用它们的事务连接。

这个“挂起-恢复”机制是 REQUIRES_NEW 实现完全独立事务的关键,确保了内外层事务在数据库连接和提交/回滚操作上的物理隔离。

sequenceDiagram
    participant OuterService as 外部事务方法
    participant TxManager as AbstractPlatformTransactionManager
    participant TSM as TransactionSynchronizationManager
    participant InnerService as 内部需要新事务的方法
    participant DS as DataSource

    Note over OuterService: 已在 PROPAGATION_REQUIRED 事务中运行<br>con1 已绑定到 TSM

    OuterService->>+TxManager: getTransaction(PROPAGATION_REQUIRES_NEW)
    TxManager->>TxManager: isExistingTransaction() -> true
    TxManager->>TxManager: handleExistingTransaction() -> PROPAGATION_REQUIRES_NEW分支
    
    Note over TxManager: 步骤1: 挂起当前事务
    TxManager->>+TSM: doSuspend()
    TSM->>TSM: unbindResource(ds) 获取 con1 的 Holder
    TSM-->>-TxManager: 返回 SuspendedResourcesHolder(con1)
    Note over TSM: 此时线程无任何事务绑定

    Note over TxManager: 步骤2: 开始新事务
    TxManager->>TxManager: startTransaction() -> doBegin()
    TxManager->>+DS: getConnection()
    DS-->>-TxManager: Return new Connection con2
    TxManager->>DS: con2.setAutoCommit(false)
    TxManager->>+TSM: bindResource(ds, ConnectionHolder(con2))
    TSM-->>-TxManager: 
    Note over TSM: 线程现在绑定的是 con2
    
    InnerService-->>TxManager: 执行业务逻辑(使用con2)

    Note over TxManager: 内层事务提交逻辑
    TxManager->>DS: con2.commit()
    TxManager->>TxManager: cleanupAfterCompletion() -> doCleanup()
    TxManager->>DS: close con2
    TxManager->>+TSM: unbindResource(ds) 解绑 con2
    
    Note over TxManager: 步骤3: 恢复挂起的事务
    TxManager->>+TSM: doResume(SuspendedResourcesHolder(con1))
    TSM->>TSM: bindResource(ds, ConnectionHolder(con1))
    TSM-->>-TxManager: 
    Note over TSM: 线程恢复绑定 con1
    
    TxManager-->>-OuterService: 返回内层事务Status
    Note over OuterService: 继续使用 con1 进行操作

图表说明:

  • 参与者:展示了外层方法(外部事务方法)、事务管理器(TxManager)、事务同步管理器(TSM)、数据源(DS)以及触发 REQUIRES_NEW 的内层方法(内部需要新事务的方法)。
  • 交互流程:序列图清晰地描绘了三个核心阶段:① 挂起,将外层事务的连接 con1 从线程解绑并保存。② 新建,获取新连接 con2,设置 autoCommit(false) 并绑定到线程,内层方法使用 con2 执行并提交、关闭。③ 恢复,将先前保存的 con1 重新绑定回线程,外层方法可以无缝继续使用其事务。
  • 关键机制suspend/resume 是整个流程的灵魂。它通过在 ThreadLocal 上“先解绑、后重绑”的操作,实现了两个物理上完全独立的事务在同一线程上的串行运行。
  • 关键结论:这张图生动地解释了为什么 REQUIRES_NEW 内层事务的提交和回滚完全不影响外层事务。因为它们使用的是不同的数据库连接,其生命周期完全由各自的 begin-commit/rollback-close 操作块管理,互不干扰。

6.3 PROPAGATION_NESTED:与世周旋者

PROPAGATION_NESTED 提供了一种介于 REQUIREDREQUIRES_NEW 之间的选择。它依赖于数据库的 Savepoint 机制。如果数据库不支持 Savepoint,其行为等同于 REQUIRED

当在现有事务中调用一个 PROPAGATION_NESTED 的方法时,逻辑也位于 handleExistingTransaction 中:

// org.springframework.transaction.support.AbstractPlatformTransactionManager
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    // 1. 允许在JTA等非JDBC事务中使用嵌套事务,但通常JDBC才支持savepoint
    if (useSavepointForNestedTransaction()) {
        // 2. 创建一个“Holder” TransactionStatus,复用现有连接
        DefaultTransactionStatus status =
                prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
        // 3. 创建Savepoint
        status.createAndHoldSavepoint();
        return status;
    } else {
        // 如果不使用savepoint(例如JTA),则回退到REQUIRES_NEW的逻辑
        return startTransaction(definition, transaction, debugEnabled, null);
    }
}

这里的关键在于 status.createAndHoldSavepoint(),它最终会调用 JDBC 连接上的 con.setSavepoint(savepointName),并把这个 Savepoint 对象存储起来。后续的数据库操作仍然在原有的连接上进行,并没有创建一个新的物理连接。

当这个嵌套事务需要回滚时,Spring 会调用 con.rollback(savepoint),仅将事务回滚到保存点之前的状态,而外层事务的其余部分不受影响。当外层事务最终提交时,整个事务(包括嵌套部分)一并提交;如果外层事务回滚,嵌套事务的操作也会被回滚。

通过这三种传播行为的剖析,我们可以清晰地看到 Spring 是如何通过灵活的“挂起/恢复”机制和对数据库特性的精妙运用,在应用层实现了对复杂事务场景的统一管理。

7. JdbcTemplate 事务参与的完整链路

将前面所有知识串联起来,我们就能够完全理解 JdbcTemplate 是如何“自动”参与到 Spring 管理的事务中的。这一过程的桥梁是 DataSourceUtils

7.1 从 JdbcTemplate 回看 DataSourceUtils

我们在前文《JdbcTemplate 的设计与模板方法模式》中提到,JdbcTemplate 的所有数据库操作,最终都会调用 DataSourceUtils.getConnection(getDataSource()) 来获取连接。

// org.springframework.jdbc.core.JdbcTemplate (简化版)
@Override
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    Connection con = DataSourceUtils.getConnection(obtainDataSource());
    // ... 执行操作 ...
}

DataSourceUtils.doGetConnection 就是实现这个“智能获取”逻辑的核心:

// org.springframework.jdbc.datasource.DataSourceUtils
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");

    // 1. 核心步骤:检查 TransactionSynchronizationManager 中是否存在绑定的连接
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    
    // 2. 如果存在绑定的 ConnectionHolder,并且其内部连接是有效的
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
        // 请求连接计数+1,用于管理连接的释放
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            // 如果Holder中没有连接(可能被延迟获取),则从数据源获取并填充
            Connection con = fetchConnection(dataSource);
            conHolder.setConnection(con);
        }
        // 返回这个与当前事务绑定的连接
        return conHolder.getConnection();
    }

    // 3. 如果没有绑定事务,则直接从数据源获取一个新连接
    Connection con = fetchConnection(dataSource);

    // 4. 如果开启了事务同步(即使没有事务也可以开启),则将此连接绑定到线程
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        // ... 将新连接绑定,后续同一线程的操作可以复用 ...
        ConnectionHolder holderToUse = conHolder;
        if (holderToUse == null) {
            holderToUse = new ConnectionHolder(con);
        } else {
            holderToUse.setConnection(con);
        }
        holderToUse.requested();
        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
        // ...
    }
    return con;
}

源码解读:

  • 获取而非创建:方法首先不是去 DataSource.getConnection(),而是去 TransactionSynchronizationManager.getResource(dataSource) 查找。这彻底贯彻了“事务资源统一管理”的核心思想。
  • 两阶段逻辑
    • 存在绑定:说明在事务上下文中。直接返回已绑定到事务中的 Connection,并递增引用计数。这保证了事务内所有操作使用同一个连接。
    • 不存在绑定:不在事务上下文中,则降级为从 DataSource 获取一个新的 Connection
  • 事务同步的扩展支持:即使没有事务,如果 TransactionSynchronizationManager.isSynchronizationActive() 返回 trueDataSourceUtils 也会将新连接绑定到线程。这为无事务但需要统一资源管理的场景(例如,统一在一个请求中复用同一个连接)提供了支持。
  • 与前文的呼应:你之前学习的 JdbcTemplate 之所以能够无感地参与事务,正是因为 DataSourceUtils 的这一层代理。JdbcTemplate 以为它只是“获取一个连接”,但实际上它获取到的是一个可能已经被事务管理器精心管理起来的连接。

JdbcTemplate 使用完连接后,它不会关闭连接,而是调用 DataSourceUtils.releaseConnection(con, getDataSource())。该方法会递减 ConnectionHolder 的引用计数,只有当连接不再被事务持有且引用计数归零时,它才会被真正关闭。这样就避免了在本该共用的连接上过早地执行 close() 操作。

8. 事务同步回调(TransactionSynchronization)及其应用

TransactionSynchronization 接口为开发者在事务生命周期的关键节点(如提交前、提交后、完成后)提供了编程钩子,允许执行自定义的业务逻辑。这是 Spring 事务管理中一个非常强大且实用的扩展点。

8.1 TransactionSynchronization 接口定义

// org.springframework.transaction.support.TransactionSynchronization
public interface TransactionSynchronization {
    /** 事务提交完成(事务已提交或回滚)后的回调 */
    int STATUS_COMMITTED = 0;
    /** 事务回滚完成后的回调 */
    int STATUS_ROLLED_BACK = 1;
    /** 未知状态 */
    int STATUS_UNKNOWN = 2;

    /**
     * 事务提交前调用,可用于刷新缓存等。
     * @param readOnly 事务是否是只读的
     */
    default void beforeCommit(boolean readOnly) {}
    
    /**
     * 事务提交/回滚事务之前调用。
     * 与 beforeCommit 的区别在于,即使事务被标记为 rollbackOnly,此方法也会被调用。
     */
    default void beforeCompletion() {}
    
    /**
     * 事务成功提交后立即调用。
     * 此回调中抛出的异常会被记录日志但不传播给调用方。
     */
    default void afterCommit() {}
    
    /**
     * 事务完成后(提交或回滚)调用,是执行资源清理的理想场所。
     * @param status 事务完成的状态,STATUS_COMMITTED、STATUS_ROLLED_BACK 或 STATUS_UNKNOWN。
     */
    default void afterCompletion(int status) {}
    
    // ... 其他方法
}

8.2 回调的注册与执行时机

要使用同步回调,首先需要获取对 TransactionSynchronizationManager 的访问权限并注册它。

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
    @Override
    public void afterCommit() {
        // 仅在事务成功提交后执行,例如发送消息。
        System.out.println("事务已提交,可以放心地发送MQ消息了。");
    }
});

这些回调的执行时机,在 AbstractPlatformTransactionManagercommitrollback 模板方法中有着严格的定义和顺序。其整体顺序如下:

成功提交流程:

  1. beforeCommit(false/true):提交前。此时事务还未真正提交,可以在此刷新缓存到数据库。
  2. beforeCompletion():完成前。
  3. doCommit():执行底层数据库提交。
  4. afterCommit():提交后。此时事务已成功提交,适合发送消息或执行其他无法回滚的操作。
  5. afterCompletion(STATUS_COMMITTED):完成后。执行最终的资源清理。

失败回滚流程:

  1. beforeCompletion():回滚前也会调用,注意 beforeCommit 不会被调用。
  2. doRollback():执行底层数据库回滚。
  3. afterCompletion(STATUS_ROLLED_BACK):完成后。执行最终的资源清理。

TransactionSynchronization 机制是 Spring 声明式事务事件(@TransactionalEventListener)的基石。当我们在 afterCommit 阶段发布一个事件时,监听器可以确保在事务成功提交后才处理该事件,从而保证数据的一致性。详细的事件机制分析将在后续《声明式事务的 AOP 实现》篇章展开。

9. 事务管理器配置与扩展点(简述)

在实际项目中,极少需要直接实现 PlatformTransactionManager,但理解其扩展点对于排查问题非常有帮助。通常,我们使用 Spring Boot 的自动配置即可。

当需要自定义资源获取逻辑时,可以实现 AbstractPlatformTransactionManager 的子类。例如,可以重写 doGetTransaction 来返回封装了特定上下文的对象。

一个更常用的扩展点是TransactionAwareDataSourceProxy。这是一个 DataSource 的代理,目的是解决直接从 DataSource 获取 Connection 的遗留代码也能参与到 Spring 事务中的问题。当代码调用 dataSource.getConnection() 时,如果当前存在 Spring 事务,代理会返回与事务绑定的连接;否则,返回一个新连接。这可以作为将非 Spring 管理的代码平滑集成到 Spring 事务体系中的桥梁,但JdbcTemplate 本身已经通过 DataSourceUtils 处理了这一切,所以现代开发中直接使用的场景已经不多。

10. 生产事故排查专题

理论最终要服务于实践。以下是两个因对事务抽象理解不足而引发的典型生产事故。

10.1 事故一:REQUIRES_NEW 导致的数据不一致幽灵

事故背景:一家电商公司在订单创建流程中,需要在一个独立事务中记录审计日志,即使订单创建失败也要保留日志。开发者使用了 @Transactional(propagation = Propagation.REQUIRES_NEW) 来注解审计日志的 Service 方法。

事故现象:在高并发下,偶尔会发现某条订单虽已创建成功,但其对应的审计日志丢失。或者更诡异的是,一条创建失败的订单却留下了一条“订单创建成功”的审计日志。

排查过程

  1. 代码审查:审查订单创建的主方法,看到主方法调用了审计日志的 Service 方法。

    @Service
    public class OrderService {
        @Autowired private AuditLogService auditLogService;
        @Autowired private OrderDao orderDao;
    
        @Transactional // Propagation.REQUIRED
        public void createOrder(Order order) {
            orderDao.insert(order); // 步骤1
            try {
                auditLogService.recordAudit("Order Created: " + order.getId()); // 步骤2
            } catch (Exception e) {
                log.error("Audit logging failed, but should not affect order creation", e);
            }
        }
    }
    
    @Service
    public class AuditLogService {
        @Autowired private AuditLogDao auditLogDao;
    
        @Transactional(propagation = Propagation.REQUIRES_NEW) // 步骤2.1
        public void recordAudit(String message) {
            auditLogDao.insert(new AuditLog(message));
            // 假设这里偶尔会因数据库死锁等原因抛出异常
        }
    }
    
  2. 初步分析:看起来逻辑似乎是正确的:用 try-catch 包裹了 REQUIRES_NEW 方法,即使它失败了也不会影响主事务。

  3. 深层分析:问题在于 事务的边界与异常的时机

    • 如果 auditLogService.recordAudit 内部的 auditLogDao.insert 执行成功,但在方法结束时、Spring 提交这个新事务的瞬间发生了数据库异常(例如,主库提交成功但从库同步超时引发警告或异常),Spring 会抛出异常。
    • 这个异常被外层 createOrder 方法的 catch 块捕获,因此主事务不受影响,提交了订单。
    • 但是,内层审计事务已经提交成功了。异常发生在提交的极短瞬间之后,导致开发者误以为内层事务失败了,但实际上数据已经持久化。
  4. 更坏的情况:如果 auditLogDao.insert 本身成功,但 recordAudit 方法后面还有一段非事务性的代码抛出了 RuntimeException(非 SQLException)。此时,内层事务虽已提交,但异常还是会被外层 catch 捕获。这是一种典型的“业务逻辑误判”。

根因与教训

  • 根因REQUIRES_NEW 创建的是物理上完全独立的事务,其提交/回滚与外部捕获异常无关。开发者天真地认为“外部 catch 住内层 REQUIRES_NEW 的异常,内层就回滚了”,混淆了逻辑异常与事务边界。REQUIRES_NEW 事务的提交发生在方法正常返回时,而不是在 catch 之后。
  • 教训
    1. 慎用 REQUIRES_NEW。不要试图用它来“装饰”一个核心逻辑,或者用它来防止核心业务受影响。它的使用会让数据一致性模型变得极其复杂。
    2. 明确失败场景。必须透彻理解,REQUIRES_NEW 方法的“成功”与“失败”边界在哪里。一旦方法正常返回,其内部的事务即告提交,外部无法再控制。
    3. 正确姿势:对于此场景,将审计日志发送到消息队列通常是更好的选择。使用 TransactionSynchronization.afterCommit() 在订单主事务提交成功后再发送消息,可以保证“有订单必有日志”的最终一致性。

10.2 事故二:rollbackFor 配置不当引发的事务静默提交

事故背景:某财务系统的一次对账操作,批量更新了 1000 条记录的状态。方法被 @Transactional 注解。运行过程中某一条更新抛出异常,但另外 999 条记录的状态却被持久化了,导致账务不平。

事故现象:没有回滚。

排查过程

  1. 代码审查
    @Service
    public class ReconciliationService {
        @Autowired private JdbcTemplate jdbcTemplate;
    
        @Transactional // 默认 rollbackFor = RuntimeException, Error
        public void batchReconcile(List<Long> ids) {
            for (Long id : ids) {
                try {
                    // 执行一些更新操作
                    processReconciliation(id);
                } catch (Exception e) {
                    // 记录日志,但继续处理下一条
                    log.error("Reconciliation failed for id: {}, but continuing...", id, e);
                }
            }
        }
    }
    
  2. 关键疑问:异常被 try-catch 捕获了,事务会回滚吗?
  3. Spring 事务回滚触发机制分析
    • Spring 声明式事务的回滚,是由 AOP 拦截器在业务方法抛出指定异常时触发的。
    • 默认情况下,指定异常是 RuntimeExceptionError
    • 但是,如果业务方法自己 catch 了异常,并且没有再将其抛出,那么 AOP 拦截器就感知不到这个异常。对拦截器来说,batchReconcile 方法是正常返回的。
    • 因此,事务管理器会认为一切正常,并提交事务。

根本原因与教训

  • 根因:开发者错误地认为,只要抛出了运行时异常,事务就会回滚。他们忽略了 AOP 的触发条件是异常必须穿透业务方法,被代理拦截器捕获,而内部的 try-catch 无声地“吞噬”了异常。
  • 教训
    1. 切勿轻易“吞噬”异常:在事务性方法中,如果捕获了异常,一定要非常清楚它是否应该导致事务回滚。
    2. “手动”回滚以备万一:对于必须捕获的异常,如果希望事务回滚,不能在吞掉异常后什么都不做,必须调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 来显式地将事务标记为“必须回滚”。
    3. 精确配置 rollbackFor:注解中的 rollbackFor 属性是用来 扩充 回滚异常类型的(比如让受检异常也回滚),而不是用来定义“只有这些异常才回滚”。它在内部是被 catch 住后由拦截器判断的,前提依然是异常得先抛出来。

11. 面试高频专题

  1. 问题:Spring 是如何实现事务管理与具体数据访问技术(JDBC, Hibernate)解耦的?

    • 参考答案:核心是通过 PlatformTransactionManager 接口和 AbstractPlatformTransactionManager 模板类实现的策略模式与模板方法模式。PlatformTransactionManager 定义了统一的事务操作 API,而 AbstractPlatformTransactionManager 则把事务的通用流程(如传播行为处理)固定下来,把特定于技术的操作(如 doBegin, doCommit)留给子类 DataSourceTransactionManagerHibernateTransactionManager 实现。上层代码只依赖接口,从而解耦。
    • 追问:如果上层代码想同时操作两个不同数据库,怎么办?
    • 加分回答:使用 JtaTransactionManager 进行分布式事务管理,它实现了 PlatformTransactionManager 接口,但委托给 JTA 实现。上层代码依然只面对同一个接口。
  2. 问题PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW 的内部实现机制有何不同?

    • 参考答案REQUIRED 判断当前有无事务,有就加入,没有就新建,逻辑简单。REQUIRES_NEW 的关键在于“挂起-恢复”机制。当检测到当前有事务时,它会调用 doSuspend 钩子,从 TransactionSynchronizationManager 中解绑当前连接并保存;然后创建一个全新的连接和事务绑定到线程上;内层事务完成后,通过 doResume 将之前保存的原连接重新绑定,恢复外层事务上下文。两者使用的是物理上不同的数据库连接,生命周期完全隔离。
    • 追问:如果 REQUIRES_NEW 内层方法被调用了 50 次,会发生什么?
    • 加分回答:会长时间占用 50 个数据库连接,极易导致连接池耗尽。这是一个典型的性能与资源陷阱,应避免在一个循环中反复通过 REQUIRES_NEW 调用方法。
  3. 问题:请编程式地实现一个事务,来演示如何使用 PlatformTransactionManagerTransactionDefinition

    • 参考答案
      public class TransactionExample {
          @Autowired
          private PlatformTransactionManager txManager;
          @Autowired
          private JdbcTemplate jdbcTemplate;
          
          public void executeInTransaction() {
              DefaultTransactionDefinition def = new DefaultTransactionDefinition();
              def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
              def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
              
              TransactionStatus status = txManager.getTransaction(def);
              try {
                  jdbcTemplate.update("INSERT INTO ...");
                  txManager.commit(status);
              } catch (DataAccessException e) {
                  txManager.rollback(status);
                  throw e;
              }
          }
      }
      
    • 追问:这个 commit 调用一定会提交吗?什么情况下不会?
    • 加分回答:不一定。如果在 try 块代码中,通过 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()status.setRollbackOnly() 将当前事务标记为“必须回滚”,那么 commit 最终执行的将是 rollback
  4. 问题TransactionSynchronizationManager 的作用是什么?为什么它那么重要?

    • 参考答案:它利用多个线程局部变量(ThreadLocal),充当当前线程事务上下文的“中央仓库”。最重要的职责是绑定事务资源(如 DataSourceConnectionHolder 的映射)和管理事务同步回调。申明式事务的自动参与、JdbcTemplate 接管连接、@TransactionalEventListener 等高级特性都直接依赖它。
    • 追问:如果我在一个事务方法中新开了一个线程,新线程能获取到当前事务吗?
    • 加分回答:不能。TransactionSynchronizationManager 的根本是 ThreadLocal,它天然与线程绑定。新线程拥有独立的 ThreadLocal 副本,无法看到父线程的事务上下文。如果需要在新线程中传播事务,必须借助 TaskExecutor + TransactionTemplate 或异步声明式事务等机制。
  5. 问题:一个方法上有 @Transactional,但是内部调用自己的另一个也有 @Transactional 的方法,为什么传播行为好像失效了?

    • 参考答案:这是因为 Spring AOP 的代理机制。事务是通过动态代理(JDK Proxy 或 CGLIB)来增强的。当在同一个类内部调用另一个方法时,调用的是 this.method(),而不是代理对象上的 proxy.method(),绕过了 AOP 拦截器,因此 @Transactional 注解完全失效。
    • 追问:如何解决?
    • 加分回答:可以通过注入自身(@Autowired private XService self),然后使用 self.method() 调用,或者将方法抽取到一个单独的 Bean 中。也可以使用AspectJ 织入,但通常过于复杂。这是我们在下一篇《声明式事务的 AOP 实现》中将重点剖析的问题。
  6. 问题JdbcTemplate 是怎么参与到 Spring 管理的事务中的?

    • 参考答案JdbcTemplate 自己不管理事务,但它通过 DataSourceUtils.getConnection() 来获取连接。DataSourceUtils 会首先检查 TransactionSynchronizationManager.getResource(dataSource),看当前线程是否有绑定的事务连接。如果有,直接返回这个连接;如果没有,才从 DataSource 获取新连接。在事务结束时,DataSourceTransactionManager 通过同一套机制解绑并关闭连接。整个过程对 JdbcTemplate 和开发者都是透明的。
    • 追问:那如果我想确保 JdbcTemplate 永远不参与事务,每次都是新连接,怎么办?
    • 加分回答:除了设置传播行为为 NEVER,还有一个“旁门左道”就是给那个特定的 JdbcTemplate 设置一个独立于事务管理器的 DataSource,不交给 Spring 管理。但这会破坏应用的统一架构。
  7. 问题:请描述一下 AbstractPlatformTransactionManagercommit 方法的模板流程。

    • 参考答案:流程如下:
      1. 状态检查,确保事务未完成。
      2. 调用 status.isRollbackOnly(),如果为 true,则走回滚流程。
      3. 调用钩子方法 doCommit(status),这是子类真正干活的步骤。
      4. 提交成功后,遍历并调用 TransactionSynchronizationafterCommit
      5. 最后无论是提交还是回滚,都会执行 finally 块中的 afterCompletiondoCleanupAfterCompletion 钩子,完成事务同步回调和资源的最终清理。
    • 追问status.setRollbackOnly() 之后,事务是在哪里被真正回滚的?
    • 加分回答:就在这个 commit 方法里。因为 commit 方法内部检测到 rollback-only 标记后,会调用 doRollback 来执行物理回滚。
  8. 问题DataSourceTransactionManager.doBegin 做了哪些关键操作?

    • 参考答案doBegin 的核心是“开启”一个 JDBC 事务:①从 DataSource 获取一个新的 Connection。②如果 ConnectionautoCommittrue,将其设置为 false。③根据 TransactionDefinition 的设置,配置事务的隔离级别和只读模式。④将新的 Connection 封装进 ConnectionHolder,并通过 TransactionSynchronizationManager.bindResource(dataSource, holder) 将其绑定到当前线程。
    • 追问:如果获取连接失败,会发生什么?
    • 加分回答doBegin 会捕获所有异常,在抛出 CannotCreateTransactionException 之前,会先调用 doCleanupAfterCompletion 来解绑和释放已经拿到的资源。
  9. 问题PROPAGATION_NESTEDPROPAGATION_REQUIRES_NEW 在底层数据库操作上有何根本区别?

    • 参考答案:根本在于物理连接。REQUIRES_NEW 使用一个全新的、独立的数据库物理连接,通过 Spring 的挂起/恢复机制管理。NESTED 复用当前事务的同一个物理连接,通过 JDBC 3.0 的 Savepoint 机制实现“部分回滚”。因此,NESTED 的性能和资源消耗远小于 REQUIRES_NEW
    • 追问:什么时候不能用 NESTED
    • 加分回答:当底层数据库事务管理器不支持 Savepoint(如 JTA 环境)时,NESTED 会退化为 REQUIRES_NEWREQUIRED,取决于 useSavepointForNestedTransaction 的值。
  10. 问题TransactionSynchronization 中的 beforeCommitafterCommit 回调分别适用于什么场景?

    • 参考答案beforeCommit 适用于需要在数据真正持久化到数据库之前的最后一刻执行的操作,比如将内存中的缓存刷新到数据库(此时数据库事务还未提交)。afterCommit 只能执行那些已经不可撤销的事情,因为此时事务已成功提交,比如发送一个“订单已创建”的 MQ 消息。如果 afterCommit 里发消息失败,它已经无法回滚订单数据库的记录了,需要依赖消息的重试和死信队列来保证最终一致性。
    • 追问afterCommit 中抛出的异常会影响到主事务吗?
    • 加分回答:不会。事务已经提交完毕,一切都晚了。Spring 只会记录下这个异常日志,但不会再次回滚。
  11. 问题:如何阻止一个事物被提交?有多少种方法?

    • 参考答案:在Spring事务上下文中,可以有几种方式。第一,让方法抛出未被捕获的RuntimeException或Error,让AOP拦截器捕获并触发回滚。第二,在方法内部通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()来标记当前事务为必须回滚状态,事务最终结束时,即使调用的是commit,也会被管理器转为rollback
    • 追问:如果事务已经被标记为rollbackOnly,但后续代码仍然调用commit会发生什么?
    • 加分回答:会抛出UnexpectedRollbackException。这正是Spring的设计,明确告诉调用者“你期望的提交没有发生,因为我被标记为回滚了”。
  12. 系统设计题:我们有一个电商订单支付链路,需要“扣减库存(服务A) -> 创建订单(服务B) -> 扣减积分(服务C)”。这三步需要保持强一致性。请设计一个基于“尝试+确认/取消”模式的方案来规避分布式事务的使用。

    • 参考答案:这是Saga模式的一种。为A、B、C各赋予一个状态机和三个操作:tryconfirmcancel
      1. Try阶段:各服务预留资源,并不真正扣减。例如库存服务try将库存冻结,积分服务try将积分冻结。都成功后转到确认阶段。
      2. Confirm阶段:依次调用各服务的confirm接口,将资源状态由“冻结”转为“已消耗”。 如果Try阶段任何一个服务失败,逐个调用所有已成功Try的服务的cancel接口,释放资源。 难点在于Confirm阶段也可能失败,这时需要协调器不断重试,直至最终成功。
    • 追问:那如果Confirm阶段积分服务的消息丢失了怎么办?
    • 加分回答:需要一个高可用的协调器和消息系统(如RocketMQ的事务消息)。积分服务的Confirm消息应被持久化,在服务恢复后可以扫描未完成的任务继续执行。这是实现最终一致性所必须的补偿机制。
  13. 系统设计题:一个报表生成系统,@Transactional 的 saveReport() 方法非常慢。生成报表时需从一个只读的大表读取数据,如果希望它不和主业务事务共用连接,且不会被主业务的排它锁阻塞,应该如何设计?

    • 参考答案:这是 NOT_SUPPORTEDREQUIRES_NEW 加上只读属性的典型组合。为了绝对不受主事务影响且不占用连接池名额,可以这样做:将报表生成逻辑分离到一个新方法中,标记为 @Transactional(propagation = Propagation.NOT_SUPPORTED)。这会让它在无事务状态下运行,不受任何正在运行的写事务锁影响。
    • 追问:使用 NOT_SUPPORTED 挂起事务会影响性能吗?
    • 加分回答NOT_SUPPORTED 同样会触发事务“挂起”,释放当前线程绑定连接,这将从数据库连接池中获取一个新连接。这本身有开销,但相比让一个慢查询长时间占用带锁的事务连接导致整个系统阻塞,这个开销是完全值得的。

文末速查表:Spring 事务核心接口与类

接口/类作用关键方法或属性
PlatformTransactionManager事务管理器顶层接口,定义事务生命周期操作getTransaction(TransactionDefinition), commit(TransactionStatus), rollback(TransactionStatus)
AbstractPlatformTransactionManager模板方法骨架,实现流程协作getTransaction(final), commit, rollback, 钩子方法: doGetTransaction, doBegin, doCommit, doRollback, doSuspend, doResume
DataSourceTransactionManagerJDBC事务管理器实现依赖 DataSourceConnection 接口
JtaTransactionManagerJTA分布式事务管理器实现委托给 Java EE 的 UserTransactionTransactionManager
TransactionDefinition事务属性定义接口getPropagationBehavior(), getIsolationLevel(), getTimeout(), isReadOnly(), getName()
DefaultTransactionDefinitionTransactionDefinition 的默认实现默认 PROPAGATION_REQUIRED, ISOLATION_DEFAULT, timeout=-1, readOnly=false
TransactionStatus事务运行时状态接口isNewTransaction(), isRollbackOnly(), setRollbackOnly(), isCompleted()
DefaultTransactionStatusTransactionStatus 的默认实现持有事务对象、挂起资源SuspendedResourcesHolder
TransactionSynchronizationManager线程资源与事务同步管理器核心 ThreadLocalresources, synchronizationsbindResource, unbindResource, getResource
DataSourceUtilsJDBC 工具类,桥接 JdbcTemplate 与事务管理器getConnection(DataSource) -> doGetConnection(...)
TransactionSynchronization事务同步回调接口beforeCommit(), beforeCompletion(), afterCommit(), afterCompletion(int status)

延伸阅读

  • Spring Framework 官方文档:Transaction Management 章节
  • 《Spring 揭秘》作者:王福强 — 第 9 章“数据访问”和第 10 章“事务管理”
  • “Spring Transaction Synchronization”相关源码分析博客,深入理解回调机制。