@Transactional 是 Spring 框架中声明式事务管理的核心注解。它的作用可以用一句话概括:让你只需要通过一个注解,就能让一个方法或类中的所有数据库操作,自动在一个事务边界内执行,无需手动编写事务开启、提交、回滚的代码。

下面我将从最基础到最深入的细节,为你全面拆解这个注解。

核心原理:AOP 的经典应用

@Transactional 之所以能“自动”工作,完全依赖于 Spring 的AOP面向切面编程。简单来说,Spring 会在运行时为带有 @Transactional 注解的类动态创建一个代理对象

这个代理对象的工作流程就像一个标准的数据库事务模板:

// Spring AOP 代理为你自动生成的伪代码逻辑
public void 代理方法() {
    try {
        // 1. 开启事务 (BEGIN TRANSACTION)
        开启事务();
        
        // 2. 调用你原本的业务方法 (target.method())
        你的业务方法(); 
        
        // 3. 如果业务方法正常执行完毕,提交事务 (COMMIT)
        提交事务();
    } catch (Exception e) {
        // 4. 如果捕获到异常,执行回滚 (ROLLBACK)
        回滚事务();
        // 5. 最后将异常重新抛出,让调用方知道
        throw e;
    }
}

核心属性详解:精准控制事务行为

你可以通过注解的参数来精细控制事务的方方面面:

属性

作用

示例

propagation

传播行为:决定当一个事务方法被另一个事务方法调用时,它自己该如何“自处”。

@Transactional(propagation = Propagation.REQUIRES_NEW)

isolation

隔离级别:定义事务与事务之间关于数据可见性的隔离程度。

@Transactional(isolation = Isolation.REPEATABLE_READ)

rollbackFor

回滚规则:指定遇到哪些异常时必须回滚。

@Transactional(rollbackFor = CustomException.class)

noRollbackFor

不回滚规则:指定遇到哪些异常时,即使抛出也不回滚(提交事务)。

@Transactional(noRollbackFor = BusinessException.class)

timeout

超时时间:设置事务允许执行的最大秒数,超时则自动回滚。

@Transactional(timeout = 30)

readOnly

只读事务:标记事务是否只读,是数据库连接池和数据库优化的一个提示。

@Transactional(readOnly = true)


核心属性深度解析

1. propagation 传播行为

propagation(传播行为)是 @Transactional 注解中最核心、也最容易让人困惑的一个属性。它解决的核心问题是:当一个事务方法调用另一个事务方法时,第二个方法应该如何处理事务?是复用当前的事务,还是挂起当前事务并开启一个新事务?它解决了“方法调用链”中的事务边界问题。

传播行为

行为描述

典型场景

REQUIRED (默认)

如果当前没有事务,就新建一个;如果已经存在一个事务,就加入到这个事务中。

绝大多数业务方法。比如:用户下单、更新库存等操作,它们天生应该属于同一个大事务。

REQUIRES_NEW

无论当前是否存在事务,都会新建一个事务。如果当前有事务,则将当前事务挂起。

独立的记录日志、审计、发消息。比如在一个下单的大事务中,记录一条操作日志,这条日志的记录不应该因为下单失败而丢失。

NESTED

如果当前存在事务,则在嵌套事务内执行;如果没有,则行为同 REQUIRED。嵌套事务是主事务的一个“保存点”,它的回滚不影响主事务。

批量处理。比如批量导入用户,其中一条失败,只回滚这一条,而不影响整个批次。

SUPPORTS

如果当前有事务,则支持;如果没有,则以非事务方式执行。

查询方法,有事务也行,没有也行。

MANDATORY

必须在一个已有的事务中运行,否则抛出异常。

强制要求调用方已开启事务的方法。

NOT_SUPPORTED

始终以非事务方式执行。如果当前有事务,则挂起它。

不需要事务,且不希望被当前事务影响的非关键操作。

NEVER

必须在非事务中执行,否则抛出异常。

绝对不允许在事务中运行的方法。

2. isolation 隔离级别

这主要解决的是数据库并发问题,Spring 定义了以下几种隔离级别:

隔离级别

脏读

不可重复读

幻读

说明

READ_UNCOMMITTED

✅可能

✅可能

✅可能

读未提交,性能最高,但安全性最低。

READ_COMMITTED

❌不会

✅可能

✅可能

读已提交,Oracle等数据库默认,可防止脏读。

REPEATABLE_READ

❌不会

❌不会

✅可能

可重复读,MySQL默认,可防止脏读和不可重复读。

SERIALIZABLE

❌不会

❌不会

❌不会

串行化,最高隔离级别,但性能极低,相当于所有事务排队执行。

3. rollbackFor / noRollbackFor
  • 默认行为:Spring 事务的默认回滚策略是只对非检查型异常(RuntimeException及其子类)和 Error 进行回滚,而检查型异常(Exception的子类但非RuntimeException)不会触发回滚

  • 为什么:这是 Spring 的设计哲学,认为检查型异常代表一种“可预期的业务结果”,而非系统故障。

  • 如何修改:使用 rollbackFor 让检查型异常也回滚:@Transactional(rollbackFor = Exception.class);使用 noRollbackFor 让某些运行时异常不回滚:@Transactional(noRollbackFor = NullPointerException.class)


五大常见“坑”与最佳实践

坑1:同一个类内部的方法调用,事务失效

现象:类中的 methodA() 调用 methodB(),只有 methodB() 加了 @Transactional,但事务不生效。

原因:代理机制。外部调用走代理,内部调用直接走 this 引用,绕过了代理。

解决方案

  1. 拆分到不同类(推荐):将 methodB() 移到另一个 @Service 中,然后注入调用。

  2. 自注入:在类中注入自己的代理对象,用 self.methodB() 调用。

  3. 修改为 public 方法(基础要求):@Transactional 只在 public 方法上生效,private/protected 会被忽略。

坑2:异常被你“吃”掉了,事务不回滚

现象:方法内 try-catch 了异常,但没有重新抛出,事务正常提交。

原因:代理捕获不到异常,认为一切正常。

解决方案:在 catch 块中手动回滚或重新抛出。

// 错误示例
try { ... } catch (Exception e) {
    log.error(...); // 只记日志,事务提交!
}

// 正确示例1:重新抛出
try { ... } catch (Exception e) {
    log.error(...);
    throw e;
}

// 正确示例2:手动回滚
try { ... } catch (Exception e) {
    log.error(...);
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
坑3:方法权限不正确

现象:注解加在 privateprotected 方法上,无任何效果。

原因:Spring 的 AOP 代理(无论是 JDK (实现接口)还是 CGLIB(继承))只能通过 public 方法入口增强。

解决方案始终将 @Transactional 标注在 public 方法上

坑4:错误的数据库引擎(MySQL)

现象:事务配置一切正确,但发生异常后数据依然被修改。

原因:你的 MySQL 表可能使用了 MyISAM 引擎,它不支持事务

解决方案:将表引擎改为 InnoDBALTER TABLE 表名 ENGINE=InnoDB;

坑5:多数据源配置错误

现象:配置了多个数据源,但 @Transactional 不知道管哪个。

原因:未明确指定事务管理器。

解决方案:通过 valuetransactionManager 属性指定。

@Transactional(value = "postgresTransactionManager")
public void updateUser() { ... }

总结:核心记忆点

  1. 原理@Transactional 的本质是 AOP 动态代理,它帮你自动完成 BEGINCOMMITROLLBACK

  2. 默认规则

    1. 传播行为REQUIRED(加入或新建)。

    2. 回滚策略:只回滚 RuntimeExceptionError,不回滚 Exception

    3. 生效范围:只能作用于 public 方法。

  3. 常见失效:同类内部调用、异常被吞、非 public 方法,这三大原因占了 90% 的事务失效问题。

  4. 最佳实践

    1. Service 层的 public 方法上使用。

    2. 明确指定 rollbackFor = Exception.class 可有效防坑。

    3. 保持事务粒度尽可能小,避免在长事务中进行耗时的非DB操作(如RPC调用、文件处理)。

更多推荐