" />
文章

Spring 是如何管理事务的:事务管理机制详解

Spring 是如何管理事务的:事务管理机制详解

面试指北 · 蔚来

题意

这道题表面在问“Spring 事务怎么用”,实际考察的是:Spring 如何把数据库、JPA、Hibernate、JTA 等不同资源的事务能力统一封装起来,并通过 AOP 把事务逻辑织入业务方法。面试官通常希望听到几条主线:

  • Spring 自己不实现数据库事务,而是通过事务管理器适配底层资源;
  • 声明式事务基于 AOP 代理,@Transactional 只是元数据,真正执行事务的是事务拦截器和事务管理器;
  • 传播行为、隔离级别、超时、只读、回滚规则分别控制什么;
  • 声明式事务和编程式事务的适用场景;
  • 为什么会出现“加了 @Transactional 但事务没生效”。

回答时建议先给总纲,再展开核心抽象、声明式/编程式、实现原理和失效场景,避免只背七种传播行为。

结论

Spring 事务管理可以概括为一条主线:

以声明式事务为主,以 AOP 代理为入口,以 PlatformTransactionManager 为事务执行核心,以 TransactionDefinition 描述事务属性,以 TransactionStatus 记录事务状态,最终把事务的开启、提交、回滚和资源释放委托给具体资源的事务管理器。

具体来说:

  • @Transactional 通过 AOP 代理拦截方法调用,方法执行前由事务管理器开启或加入事务,正常返回则提交,抛出异常则按回滚规则决定是否回滚;
  • 默认情况下,RuntimeException 和 Error 会回滚,受检异常 Checked Exception 不会回滚;
  • 默认传播行为是 REQUIRED,即当前有事务就加入,没有就新建;
  • 编程式事务通过 PlatformTransactionManager 或 TransactionTemplate 手动控制事务边界,适合需要精细控制的场景;
  • this 自调用、方法非 public、对象未被 Spring 管理、异常被吞掉、数据库引擎不支持事务等,都会导致“事务看起来没生效”。

要点展开

1. 核心抽象:事务管理器、事务定义与事务状态

Spring 事务管理的核心接口是 PlatformTransactionManager,它只定义三个动作:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition);
    void commit(TransactionStatus status);
    void rollback(TransactionStatus status);
}

围绕它还有两个重要对象:

  • TransactionDefinition:描述事务属性,包括传播行为、隔离级别、超时时间、是否只读等;
  • TransactionStatus:记录当前事务状态,例如是否是新事务、是否已回滚、是否已完成等。

Spring 提供了多种事务管理器实现,常用包括:

  • DataSourceTransactionManager:用于管理单个关系型数据库的事务,底层基于 JDBC Connection;
  • JpaTransactionManager:用于管理 JPA 持久化框架的事务;
  • HibernateTransactionManager:用于管理 Hibernate 的事务;
  • JtaTransactionManager:用于分布式事务的管理。

以 DataSourceTransactionManager 为例,它并不是“自己实现了一套事务”,而是通过 DataSourceUtils 获取连接,并借助 TransactionSynchronizationManager 把连接绑定到当前线程。这样同一个线程、同一个数据源内多次数据库操作可以复用同一个连接,从而处于同一个事务中。事务提交或回滚后,再解绑并释放资源。

2. 声明式事务:@Transactional 是如何工作的

声明式事务使用基于注解或 XML 的方式定义事务边界,Spring 推荐使用注解。

注解方式:使用 @Transactional 注解声明事务:

@Service
public class UserService {

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);  // 插入操作
        logRepository.saveLog("User created"); // 插入日志
    }
}
  • 注解可用于类或方法上。放在类上表示该类所有 public 方法默认使用该事务配置,方法上的配置可以覆盖类级别配置。
  • 配置属性包括:传播行为、隔离级别、超时、只读、回滚异常类型等。

@Transactional 常见属性可以这样理解:

属性 作用
propagation 传播行为,默认 REQUIRED
isolation 隔离级别,默认 DEFAULT,即交给数据库默认值
timeout 超时时间,单位秒,默认 -1 表示不限制
readOnly 只读提示,默认 false,不是强制禁止写
rollbackFor / noRollbackFor 指定哪些异常回滚、哪些异常不回滚

XML 方式:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.service.*.*(..))"/>
</aop:config>

声明式事务真正生效,依赖 @EnableTransactionManagement 注册事务基础设施,以及 TransactionInterceptor 这类事务拦截器。方法被代理后,调用会先进入拦截器:

  1. 根据 @Transactional 元数据生成 TransactionDefinition;
  2. 调用 PlatformTransactionManager.getTransaction(...) 开启或加入事务;
  3. 执行业务方法;
  4. 正常返回则 commit,抛出异常则根据回滚规则决定 rollback;
  5. 最后清理事务资源。

Spring 提供两种代理方式:

  • JDK 动态代理:用于代理实现了接口的类;
  • CGLIB 代理:用于代理没有实现接口的类。

事务是否生效,关键不在于业务代码有没有写 @Transactional,而在于这次调用有没有经过代理对象。

3. 编程式事务:手动控制事务边界

编程式事务通过手动调用事务管理器接口实现事务管理,适合对事务边界有精确控制的场景。

示例:

@Service
public class UserService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void createUser(User user) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);

        try {
            userRepository.save(user); // 插入用户
            logRepository.saveLog("User created"); // 插入日志
            transactionManager.commit(status); // 提交事务
        } catch (Exception e) {
            transactionManager.rollback(status); // 回滚事务
            throw e;
        }
    }
}

手写编程式事务时,要保证 commit 和 rollback 成对出现,并且回滚后通常要把异常继续抛出,避免上层误以为执行成功。

实际项目里更常用 TransactionTemplate,它把模板代码封装起来,减少漏写 commit/rollback 的风险:

@Service
public class UserService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void createUser(User user) {
        transactionTemplate.execute(status -> {
            userRepository.save(user);
            logRepository.saveLog("User created");
            return null;
        });
    }
}

声明式事务是方法级粒度,编程式事务可以做到代码块级粒度,适合批量处理分段提交、需要在方法内部动态决定事务边界等场景。

4. 事务传播行为:七个常量怎么理解

Spring 支持七种事务传播行为,由 TransactionDefinition 接口定义。传播行为解决的是:一个事务方法被另一个事务方法调用时,当前事务应该怎么处理。

传播行为 当前已有事务时 当前没有事务时 说明
PROPAGATION_REQUIRED 加入当前事务 创建新事务 默认值,最常用
PROPAGATION_REQUIRES_NEW 挂起当前事务,创建新事务 创建新事务 内外事务独立提交、回滚
PROPAGATION_SUPPORTS 加入当前事务 以非事务方式运行 适合查询等可有可无事务的场景
PROPAGATION_NOT_SUPPORTED 挂起当前事务 以非事务方式运行 明确不需要事务
PROPAGATION_NEVER 抛出异常 以非事务方式运行 要求调用方不能有事务
PROPAGATION_MANDATORY 加入当前事务 抛出异常 要求调用方必须有事务
PROPAGATION_NESTED 在当前事务中创建嵌套事务,通常基于 savepoint 行为类似 REQUIRED,创建新事务 回滚到保存点,不是完全独立事务

需要重点区分 REQUIRES_NEW 和 NESTED:

  • REQUIRES_NEW 会挂起外层事务,开启一个独立的新事务。内层提交或回滚不影响外层,但会占用额外连接,也要注意锁竞争;
  • NESTED 是在当前事务内创建保存点,内层失败可以回滚到保存点,外层仍可选择继续或整体回滚。它依赖底层资源支持 savepoint,不是所有事务管理器都支持。

另外,传播行为只有在经过代理调用时才生效。同一个类里 this.methodB() 调用 methodB(),不会触发事务拦截,传播配置自然也不会按预期生效。

5. 隔离级别、超时、只读与回滚规则

Spring 支持的事务隔离级别包括:

  • ISOLATION_DEFAULT(默认):使用数据库的默认隔离级别;
  • ISOLATION_READ_UNCOMMITTED:读未提交;
  • ISOLATION_READ_COMMITTED:读已提交;
  • ISOLATION_REPEATABLE_READ:可重复读;
  • ISOLATION_SERIALIZABLE:序列化。

Spring 只是把隔离级别传递给底层数据库或持久化框架,实际支持程度取决于数据库。例如 MySQL InnoDB 默认是可重复读,Oracle 默认通常是读已提交。

超时:指定事务的最大执行时间,超过时间会尽量触发回滚。timeout 单位是秒,默认 -1 表示不限制。是否精确生效取决于底层事务管理器和资源实现,不能把它当成精确的“定时杀线程”。

只读:readOnly = true 是优化提示,可能让 JDBC 连接设置为只读,也可能让 JPA/Hibernate 调整 flush 模式,但它不是强制安全边界,不能依赖它阻止写操作。

回滚规则:默认情况下,RuntimeException 和 Error 会导致事务回滚,Checked Exception 不会回滚。如果业务需要受检异常也回滚,可以显式配置:

@Transactional(rollbackFor = Exception.class)
public void createUser(User user) {
    // ...
}

同样,如果异常在事务方法内部被 try-catch 吞掉,没有继续抛出,也没有手动标记回滚,Spring 就感知不到异常,事务仍然可能提交。

6. 实现原理串联:AOP 拦截到资源释放

把前面的点串起来,声明式事务的执行链路大致是:

  1. 业务 Bean 被 AOP 代理;
  2. 调用进入 TransactionInterceptor;
  3. 拦截器读取 @Transactional 配置,构造 TransactionDefinition;
  4. 调用 PlatformTransactionManager.getTransaction(...);
  5. 具体事务管理器根据传播行为决定加入、挂起还是新建事务;
  6. DataSourceTransactionManager 等实现把连接等资源绑定到当前线程;
  7. 执行业务方法;
  8. 正常返回则提交,异常则按回滚规则回滚;
  9. 清理事务同步状态并释放资源。

所以,Spring 事务管理的关键词是“协调”和“委托”:AOP 负责把事务逻辑织入方法调用,事务管理器负责协调底层资源,TransactionDefinition 和 TransactionStatus 负责描述事务属性与状态。Spring 并没有替代数据库的事务能力,而是让开发者以更统一的方式使用这些能力。

7. 事务管理的配置

配置事务管理器:

@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

启用事务注解:

@Configuration
@EnableTransactionManagement
public class AppConfig {
    // 配置数据源和事务管理器
}

在传统 Spring 配置中,需要显式开启事务管理并声明事务管理器;Spring Boot 场景下,通常会根据数据源自动配置事务管理器,一般不需要自己手写 DataSourceTransactionManager。但理解 @EnableTransactionManagement 的作用仍然重要,它负责注册事务拦截相关基础设施,让 @Transactional 有代理可走。

易错点与对比

声明式事务与编程式事务对比

维度 声明式事务 编程式事务
入口 @Transactional、XML 配置 PlatformTransactionManager、TransactionTemplate
粒度 方法级 代码块级
优点 侵入小、业务代码干净 控制精细、边界灵活
缺点 自调用等场景容易失效 代码有侵入,需要手动处理提交回滚
适用场景 大多数普通业务方法 批量分段、动态事务边界、需要精细控制

常见失效与踩坑场景

  • 自调用失效:this.method() 不经过代理,@Transactional 不生效。解决办法是拆到另一个 Bean、注入代理对象,或使用 AopContext.currentProxy(),但后两者要谨慎设计。
  • 方法可见性不合适:事务方法通常应为 public。private 方法无法被常规代理拦截,final 方法也可能导致 CGLIB 无法覆盖。
  • 对象不在 Spring 容器中:自己 new 出来的对象没有代理,注解不会生效。
  • 异常被吞掉:方法内部捕获异常但没有抛出,也没有手动标记回滚,事务不会回滚。
  • 受检异常默认不回滚:需要 rollbackFor = Exception.class 或类似配置。
  • 多线程不共享事务:事务资源通常绑定在当前线程,子线程里的数据库操作不会自动加入父线程事务。
  • 数据库引擎不支持事务:例如 MySQL 的 MyISAM 引擎不支持事务,配置再对也没用。
  • 多数据源事务管理器不匹配:操作多个数据源时,要明确事务管理器,必要时使用分布式事务方案。
  • 传播行为使用不当:REQUIRES_NEW 会挂起外层事务并占用新连接,可能引发连接池压力和锁竞争;NESTED 依赖 savepoint
License:  CC BY 4.0