关于@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 代理实现的。当事务方法抛出异常时,代理会检查异常是否需要回滚:

  1. 默认回滚规则​:仅 RuntimeExceptionError 触发回滚。
  2. 异常传播路径​:如果异常在方法内部被 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 = ...) 显式指定需要回滚的异常。
  • 全局处理器中谨慎处理异常​:
    • 如果希望事务回滚,优先重新抛出异常。
    • 如果需返回友好提示,必须手动标记回滚。
  • 日志记录​:在全局处理器中记录异常堆栈,即使事务已回滚。

总结:

  • 事务回滚依赖异常传播​:只有未被捕获的异常会触发回滚。
  • 全局异常处理器的陷阱​:直接处理异常会“吞掉”异常,导致事务不回滚。
  • 解决方案​:重新抛出异常或手动标记回滚,确保数据一致性。

 

posted @ 2025-05-17 20:37  lksses  阅读(148)  评论(0)    收藏  举报