Spring默认仅对RuntimeException及其子类、Error自动回滚,检查异常(如Exception子类)需显式配置rollbackFor才回滚;自定义异常应继承RuntimeException或在@Transactional中指定rollbackFor属性。

在 Java 中,自定义异常本身不会自动触发事务回滚,关键在于是否被 Spring 事务管理器识别为“回滚异常”。默认情况下,Spring 只对 未检查异常(RuntimeException 及其子类) 和 Error 自动回滚;而检查异常(Exception 及其非 RuntimeException 子类)默认不回滚。
确保自定义异常继承 RuntimeException
这是最常用、最直接的方式。让积分扣减失败时抛出的异常属于运行时异常,Spring 就会默认回滚当前事务:
- 定义异常类时,直接继承
RuntimeException:
public PointsDeductionFailedException(String message) {
super(message);
}
public PointsDeductionFailedException(String message, Throwable cause) {
super(message, cause);
}
}
- 在服务方法中使用:
public void placeOrder(Long userId, BigDecimal amount) {
// 扣减积分
if (!pointsService.deduct(userId, requiredPoints)) {
throw new PointsDeductionFailedException("用户积分不足或扣减失败");
}
// 创建订单...
orderMapper.insert(...);
}
此时一旦抛出该异常,整个 @Transactional 方法内所有数据库操作(如订单插入)都会回滚。
若必须用检查异常:显式配置 rollbackFor
有时业务规范要求异常必须是检查异常(比如需强制 try-catch),这时可在 @Transactional 上指定:
- 使用
rollbackFor = PointsDeductionFailedException.class - 注意:该异常仍需继承
Exception,但不能是RuntimeException
public void placeOrder(Long userId, BigDecimal amount) throws PointsDeductionFailedException {
if (!pointsService.deduct(userId, requiredPoints)) {
throw new PointsDeductionFailedException("积分扣减失败");
}
// ...
}
避免事务失效的常见坑
-
方法不是 public:Spring AOP 代理仅对 public 方法生效,private/protected 方法上加
@Transactional无效 - 同一类内调用:A 方法调用本类的 B 方法(即使 B 有 @Transactional),事务不生效——因为没走代理对象
- 异常被吞掉:try-catch 了异常但没重新 throw,事务不会回滚
- 数据库引擎不支持事务:比如 MySQL 用了 MyISAM 表,InnoDB 才支持事务回滚
加分项:结合业务做补偿或状态标记
单纯靠数据库回滚只能保证本地一致性。真实场景中,积分可能由独立服务提供(如积分中心),此时需考虑分布式事务或最终一致性:
- 记录“待扣减”订单状态 + 本地事务,再异步调用积分服务
- 扣减失败时更新订单为“积分异常”,触发人工或定时任务处理
- 使用 Seata、RocketMQ 事务消息等实现跨服务一致性(进阶场景)
本地单库场景下,按前两点配置即可可靠回滚;分布式场景需跳出纯异常回滚思维,转向 Saga 或 TCC 等模式。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











