方案一:全局异常处理类捕获到异常判断回滚
在您的订单模块代码中,库存扣减失败后被全局异常处理捕获并返回统一的 Result
JSON 结果,导致 Feign 调用无法捕获到库存扣减失败的异常,从而影响 Seata 分布式事务的回滚。
为了解决这个问题,您可以在全局异常处理类中,针对特定的异常类型(如库存扣减失败的异常),手动触发 Seata 的全局事务回滚。
以下是修改后的全局异常处理类示例:
package com.atguigu.storage.handler; import com.atguigu.storage.result.Result; import com.atguigu.storage.result.ResultCodeEnum; import io.seata.core.context.RootContext; import io.seata.core.exception.TransactionException; import io.seata.core.model.GlobalTransactionContext; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE) public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Result error(Exception e) { e.printStackTrace(); System.err.println("触发异常拦截: " + e.getMessage()); // 判断是否为库存扣减失败的异常 if (e.getMessage().contains("扣减库存失败")) { // 获取全局事务 XID String xid = RootContext.getXID(); if (xid != null) { try { // 手动回滚全局事务 GlobalTransactionContext.reload(xid).rollback(); } catch (TransactionException ex) { ex.printStackTrace(); } } } return Result.fail(); } @ExceptionHandler({IllegalArgumentException.class}) @ResponseBody public Result llegalArgumentException(Exception e) { System.err.println("触发异常拦截: " + e.getMessage()); return Result.build(null, ResultCodeEnum.ARGUMENT_VALID_ERROR); } }
在上述代码中,当捕获到异常时,首先判断异常信息中是否包含“扣减库存失败”字样,如果是,则获取当前的全局事务 XID,并手动触发回滚操作。
请注意,这种方式需要您在异常信息中明确标识出库存扣减失败的情况,以便在全局异常处理类中进行判断。
此外,您还可以考虑在 Feign 的 @FeignClient
注解中,使用 fallback
或 fallbackFactory
属性来处理远程调用失败的情况,并在降级方法中手动触发全局事务回滚。
例如,使用 fallback
属性指定降级实现类:
@FeignClient(name = "storage-service", fallback = StorageClientFallback.class) public interface StorageClient { // ... }
在降级实现类 StorageClientFallback
中,您可以手动触发全局事务回滚。
请根据您的具体业务需求,选择合适的方式来处理库存扣减失败的异常,以确保 Seata 分布式事务能够正确回滚。
方案二:切面类捕获service业务代码异常手动回滚
package com.atguigu.handler; import java.lang.reflect.Method; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import io.seata.core.context.RootContext; import io.seata.core.exception.TransactionException; import io.seata.tm.api.GlobalTransaction; import io.seata.tm.api.GlobalTransactionContext; @Aspect @Component public class TestAspect { private final static Logger logger = LoggerFactory.getLogger(TestAspect.class); //方式二 @AfterThrowing(throwing = "e", pointcut = "execution(* com.atguigu.*.service.*.*(..))") public void doRecoveryActions(Throwable e) throws TransactionException { logger.info("方法执行异常:{}", e.getMessage()); if (!StringUtils.isBlank(RootContext.getXID())) GlobalTransactionContext.reload(RootContext.getXID()).rollback(); } }
方式三:直接在全局异常继续抛出异常
java
@Override public void deduct(String code, Integer count) { log.info("开始扣减库存"); try { LambdaUpdateWrapper<Storage> update = new LambdaUpdateWrapper<>(); update.eq(Storage::getCommodityCode, code); update.setSql("count = count - " + count); this.update(update); } catch (Exception e) { throw new IllegalArgumentException("扣减库存失败,可能是库存不足!", e); } log.info("扣减库存成功"); package com.atguigu.handler; import com.atguigu.reuslt.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 全局异常处理类 * */ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Result error(Exception e){ e.printStackTrace(); System.err.println("触发异常拦截: " + e.getMessage()); //方式一 // 获取全局事务 XID // String xid = RootContext.getXID(); // if (xid != null) { // try { // // 手动回滚全局事务 // GlobalTransactionContext.reload(xid).rollback(); // } catch (TransactionException ex) { // ex.printStackTrace(); // } // } return Result.fail(); } @ExceptionHandler({IllegalArgumentException.class}) @ResponseBody public Result llegalArgumentException(Exception e) { System.err.println("触发异常拦截: " + e.getMessage()); throw new RuntimeException(e.getMessage()); // return Result.build(null, ResultCodeEnum.ARGUMENT_VALID_ERROR); } }
## 方式四:获取feign结果判断result响应码是否为200