全局异常处理Seata事务失效解决方案

2022-08-21 13:45:18

全局异常处理Seata事务失效解决方案

最近的项目用到了seata来管理全局事务,在进行测试的时候,发现当service A 调用Service B时,如果ServiceA报错,ServiceB能回滚,但是如果ServiceB报错,ServiceA是无法进行回滚的。

经过查找,发现是因为当时为了系统能统一处理全局异常,返回统一的异常信息给前端做了一个全局异常拦截,具体代码如下

/**
 * Controller统一异常处理
 *
 * @author : 777666
 * @date : 2022/01/19
 */@ControllerAdvicepublicclassAllControllerAdvice{privatestaticLogger logger=LoggerFactory.getLogger(AllControllerAdvice.class);/**
     * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
     */@InitBinderpublicvoidinitBinder(WebDataBinder binder){}/**
     * 把值绑定到Model中,使全局@RequestMapping可以获取到该值
     */@ModelAttributepublicvoidaddAttributes(Model model){}/**
     * 捕捉BusinessException自定义抛出的异常
     *
     * @return
     */@ResponseStatus(HttpStatus.OK)@ExceptionHandler(value=BusinessException.class)@ResponseBodypublicResultDatahandleBusinessException(BusinessException e){String message= e.getMessage();if(message.indexOf(":--:")>0){String[] split= message.split(":--:");returnResultData.error(split[0], split[1]);}returnResultData.error(CodeEnum.DATA_ERROR.getCode(), message,null);}@ResponseStatus(HttpStatus.OK)@ExceptionHandler(value=HttpMessageNotReadableException.class)@ResponseBodypublicResultDatahandleHttpMessageNotReadableException(HttpMessageNotReadableException e){
        logger.info("参数错误:"+ e.getMessage());returnResultData.error(CodeEnum.PARAM_ERROR.getCode(), e.getMessage(),null);}@ResponseStatus(HttpStatus.OK)@ExceptionHandler(value=MethodArgumentNotValidException.class)@ResponseBodypublicResultDatahandleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        logger.info("参数错误:"+ e.getMessage());returnResultData.error(CodeEnum.PARAM_ERROR.getCode(), e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),null);}/**
     * 全局异常捕捉处理
     */@ResponseBody@ExceptionHandler(value=Exception.class)@ResponseStatus(HttpStatus.OK)publicResultData<String>errorHandler(Exception ex){
        logger.error("接口出现严重异常:{}", ex.getMessage());returnResultData.error(CodeEnum.ERROR.getMsg());}}

这么做的一个好处就是,无论你代码里面报了什么异常,我这个AOP都可以拦截到,并且返回给前端一个约定的值,而不会说返回给前端一串英文异常信息。

那么在使用seata的时候,如果这么做会有什么问题呢?

我们使用ServiceA调用ServiceB,此时让ServiceB 抛出一个异常,会发现一个现象,ServiceB回滚了,ServiceA没回滚,那么此时可以说分布式事务失效了。

经过查阅资料发现,如果异常被我们内部处理了(我们自定义的全局异常AOP拦截了),seata不会再处理这个异常信息,导致ServiceA的事务回滚失败

明白了这个,问题就比较好解决了

要么进行手动回滚,要么让所有分布式事务请求的接口不被这个AOP拦截不就可以了吗

针对AOP的处理,seata官方有一个解决方案

在这里,我们不用这个方式

首先,我们看下@ControllerAdvice这个注解

在这里插入图片描述

在这个注解中有一个basePackages的属性,也就是说,如果配置了这个属性,将只会拦截basePackages指定的包下面发生的异常

所以,我们只需要指定basePackages的值,再把远程调用的所有服务抽出来,关键代码如下

全局异常拦截器只需要处理正常controller下的所有请求

在这里插入图片描述

此时远程调用的包结构如下

在这里插入图片描述

此时再试一下,在ServiceB抛出一个除零异常
在这里插入图片描述

ServiceA此时感知到后,进行Rollback

在这里插入图片描述

说一说这种方式的局限性

在ServiceA和ServiceB都配置了全局异常的前提下,ServiceA去调用ServiceB

如果ServiceA排除了远程调用的feign远程包,ServiceB没排除,那么ServiceB的分布式异常将被内部自己处理,seata不再处理,所以ServiceA不会回滚。

有一点就是,无论ServiceA、ServiceB是否排除feign远程包,只要作为调用方的ServiceA出现异常,作为被调用方ServiceB的事务都将发生回滚(由于对seata的内部原理还不是很清楚,具体情况需要后续研究了才明白)。

总结:在使用了seata管理分布式事务的情况下,如果项目内部使用了全局异常处理,需要把远程调用的包排除出去,否则可能会导致分布式事务失效

  • 作者:XX777666
  • 原文链接:https://blog.csdn.net/XX777666/article/details/122577337
    更新时间:2022-08-21 13:45:18