Spring @Transactional 必须加 rollbackFor = Exception.class 的原因详解

一、先说结论

Spring 的 @Transactional 默认只对 RuntimeException 和 Error 回滚,不会对受检异常(Checked Exception,如 IOExceptionSQLException)回滚。因此阿里巴巴开发规范要求必须显式指定 rollbackFor = Exception.class,防止部分操作成功、部分失败导致的数据不一致问题。


二、默认回滚行为 vs 显式指定回滚

不加 rollbackFor(默认行为)

异常类型 是否回滚
RuntimeException 及其子类(如 NullPointerException ✅ 回滚
Error 及其子类(如 OutOfMemoryError ✅ 回滚
受检异常(如 IOExceptionSQLException 不回滚

rollbackFor = Exception.class

异常类型 是否回滚
RuntimeException 及其子类 ✅ 回滚
Error 及其子类 ✅ 回滚
受检异常(如 IOExceptionSQLException 回滚
简单来说,加了 rollbackFor = Exception.class 后,所有 Exception 及其子类都会触发回滚,包括运行时异常和受检异常。

三、一个真实场景:不加 rollbackFor 的后果

@Transactional
public void createAircraft() {
    mapper.insert(aircraft);       // 第1步:插入数据库,成功
    importDataFromFile();           // 第2步:读取文件,抛出 IOException
}

不加 rollbackFor 第2步抛出 IOException(受检异常),Spring 认为不需要回滚,第1步的数据已经落库,导致数据库中存在一条不完整的数据。
加了 rollbackFor = Exception.class 第2步抛出 IOException,Spring 触发事务回滚,第1步的插入操作也会被撤销,数据库保持一致。

四、什么是 IOException?

IOException 是 Java 中的受检异常(Checked Exception),表示输入/输出操作失败或中断时抛出的异常。
常见触发场景:

// 1. 读取不存在的文件
FileInputStream fis = new FileInputStream("不存在的文件.txt");
// 2. 网络连接中断
InputStream is = new URL("http://example.com").openStream();
// 3. 写入文件失败(磁盘满、权限不足)
FileOutputStream fos = new FileOutputStream("/readonly/test.txt");
fos.write(data);

注意:编译器会强制要求你处理受检异常try-catchthrows 声明),否则代码无法编译。这也是它叫"受检异常"的原因。

五、受检异常 vs 非受检异常

继承关系

Object
 └── Throwable
      ├── Error                       ← JVM 级别错误,通常不处理
      └── Exception
           ├── RuntimeException       ← 非受检异常,编译器不强制处理
           │    ├── NullPointerException
           │    ├── IllegalArgumentException
           │    └── ArrayIndexOutOfBoundsException
           └── IOException            ← 受检异常,编译器强制处理
           └── SQLException           ← 受检异常,编译器强制处理
           └── ClassNotFoundException ← 受检异常,编译器强制处理

核心区别

受检异常(Checked) 非受检异常(Unchecked)
继承 Exception 的非 RuntimeException 子类 RuntimeException 及其子类
编译器 强制处理(try-catch 或 throws) 不强制处理
常见示例 IOExceptionSQLExceptionClassNotFoundException NullPointerExceptionIllegalArgumentException
@Transactional 默认回滚 ❌ 不回滚 ✅ 回滚

六、最佳实践

✅ 推荐:始终显式指定 rollbackFor

@Transactional(rollbackFor = Exception.class)
public void createAircraft() {
    mapper.insert(aircraft);
    importDataFromFile();
}

❌ 不推荐:依赖默认行为

@Transactional
public void createAircraft() {
    mapper.insert(aircraft);
    importDataFromFile();  // 如果抛出 IOException,事务不会回滚!
}

七、总结

对比项 不加 rollbackFor rollbackFor = Exception.class
RuntimeException 回滚 回滚
Error 回滚 回滚
IOException(受检) 不回滚 ⚠️ 回滚
SQLException(受检) 不回滚 ⚠️ 回滚
阿里巴巴规范 ❌ 违规 ✅ 符合
一句话总结: 只要你的业务方法中涉及文件操作、网络请求、第三方接口调用等可能抛出受检异常的操作,就必须加 rollbackFor = Exception.class,否则一旦受检异常发生,事务不会回滚,将导致严重的数据不一致问题。养成习惯,所有 @Transactional 都显式指定 rollbackFor,是最安全的做法。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐