关于@RestControllerAdvice的异常捕获与事务回滚
今天看到SpringBoot里的一个注解:
@RestControllerAdvice
这个注解主要包括:
@ControllerAdvice和@ResponseBody,
@ControllerAdvice主要用来注解创建全局异常处理器。
其中主要作用:
- 统一异常处理:集中处理所有控制器中抛出的异常,避免在每个方法中重复编写
try-catch。 - 封装响应格式:确保所有错误返回统一的 JSON 结构(如
{code: 500, msg: "error", data: null})。 - 减少冗余代码:通过全局配置替代分散在各处的异常处理逻辑。
但是我从这个注解引入到一个新问题:
就是 [事务回滚]和[异常处理]:
当我同时处理异常和事务回滚: 如果方法上有 @Transactional,异常需要抛出到 Spring 容器才会触发回滚。 如果异常被全局处理器捕获并“吞掉”(不重新抛出),事务可能不会回滚。
来个场景例子:
@Transactional @PostMapping("/register") public void register(User user) { userRepository.save(user); // 插入用户记录 if (someCondition) { throw new RuntimeException("模拟业务异常"); // 抛出异常 } }
下面是全局异常处理器
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) public Result handleRuntimeException(RuntimeException e) { // 直接返回错误响应,未重新抛出异常 return Result.error(500, e.getMessage()); } }
当在这个register控制器在出现【RuntimeException】的时候,异常会被异常处理器捕获处理掉,这时候就出现问题了,
异常被全局异常处理器处理掉了,此时Spring的事务管理器说:我也没发现有什么异常啊? 接着跑!
这时,用户数据插入了,异常处理了,数据说:出错了。
下面讲下原因:
Spring 的事务管理是通过 AOP 代理实现的。当事务方法抛出异常时,代理会检查异常是否需要回滚:
- 默认回滚规则:仅
RuntimeException和Error触发回滚。 - 异常传播路径:如果异常在方法内部被
try-catch捕获,或在全局处理器中被处理且未重新抛出,事务代理感知不到异常,因此不会回滚。
这时我问了AI,给出了三种解决方案:
(1) 让异常继续传播
在全局异常处理器中重新抛出异常,确保事务代理能捕获到它:
@ExceptionHandler(RuntimeException.class) public Result handleRuntimeException(RuntimeException e) throws RuntimeException { // 重新抛出 // 记录日志或处理其他逻辑 log.error("业务异常", e); throw e; // 重新抛出异常,触发事务回滚 }
(2) 手动回滚事务
如果不想重新抛出异常(例如需要返回自定义错误信息),可以手动标记事务回滚:
@ExceptionHandler(RuntimeException.class) public Result handleRuntimeException(RuntimeException e) { // 获取当前事务并标记回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return Result.error(500, e.getMessage()); }
(3) 显式配置回滚异常
通过 @Transactional(rollbackFor = Exception.class) 指定需要回滚的异常类型(包括检查型异常):
@Transactional(rollbackFor = {RuntimeException.class, SQLException.class})
public void register(User user) {
// ...
}
同时还给了我三条在实际处理时的建议:
- 明确回滚规则:使用
@Transactional(rollbackFor = ...)显式指定需要回滚的异常。 - 全局处理器中谨慎处理异常:
- 如果希望事务回滚,优先重新抛出异常。
- 如果需返回友好提示,必须手动标记回滚。
- 日志记录:在全局处理器中记录异常堆栈,即使事务已回滚。
总结:
- 事务回滚依赖异常传播:只有未被捕获的异常会触发回滚。
- 全局异常处理器的陷阱:直接处理异常会“吞掉”异常,导致事务不回滚。
- 解决方案:重新抛出异常或手动标记回滚,确保数据一致性。
难产难产难产

浙公网安备 33010602011771号