• Spring Boot中TransactionalEventListener的用法
  • 发布于 2个月前
  • 95 热度
    0 评论
Spring Boot为我提供了一个强大的注解TransactionalEventListener,通过该注解我们可以感知到因为失败而回滚的事务,进行进一步的操作,所以对于某些需要保证事务可靠性或者需要对失败事务进行监控的场景,该注解是非常的实用,下面笔者就会以一个简单的保存接口演示一下事务监听的实用。

事务监听基础示例
整体流程

如下所示,我们的controller接口会提交一个保存的请求,交由带有事务的service处理,一旦controller感知到service错误,在service回滚事务之后,主动将当前保存的信息发布出去,交由监听器处理。


封装事件
了解整体工作流程之后,我们就可以开始编码,由上可知,我们感知失败事务时要发送错误消息,所以我们封装一个事件对象,记录保存失败的TData 。
/**
 * tdata事件,记录tdata的信息
 */
public class TransactionEvent {
    private TData data;
    public TransactionEvent(TData data) {
        this.data = data;
    }

    public TData getData() {
        return data;
    }
}
为了文章的完整性我们给出TData的代码
@Data
public class TData {
    private Integer id;

    private String data;

    private Byte type;
}
发布异常
完成事件编写之后,我们就需要事件发布事务失败事件这一步,所以我们要编写一个事件发布者,如下所示,可以看到笔者注入Spring的事件发布工具,通过eventPublisher来发布我们的事务失败事件TransactionEvent 。
@Component
public class TransactionEventPublisher {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishEvent(TransactionEvent event) {
        eventPublisher.publishEvent(event);
    }
}
感知事件
重点来了,我们封装了一个TDataTransactionalEventListener 用于感知失败的事务事件,可以看到笔者实用TransactionalEventListener注解,让当前监听器感知回滚的事务事件,并获取当前失败的事务监听的事件内容,在进行输出打印(模拟事件处理):
@Component
@Slf4j
public class TDataTransactionalEventListener {


    //事务回滚后 对应seqId会被消耗掉
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK, fallbackExecution = true)
    public void handleRollbackEvent(TransactionEvent event) {
        TData data = event.getData();
        log.info("处理回滚事件,data:{}", JSONUtil.toJsonStr(data));
        // 执行其他异常处理逻辑
    }
}
测试
我们这里编写一个简单的service模拟事务保存异常:
 @Transactional
    public int saveData(TData tData) {

        int count = tDataMapper.insert(tData);
        int i = 1 / 0;
        return count;
    }
对应的Controller层,设置try-catch异常捕获,感知事务失败的错误后,发布一个错误事件:
@RestController("/test")
@Slf4j
public class TestController {
    @Resource
    private TDataService tDataService;

    @Autowired
    private TransactionEventPublisher transactionEventPublisher;

    @PostMapping("save")
    public int saveData(@RequestBody TData tData) {
        int count;
        try {
            return tDataService.saveData(tData);
        } catch (Exception e) {
            log.error("data保存失败,失败原因:{}", e.getMessage(), e);
            transactionEventPublisher.publishEvent(new TransactionEvent(tData));
            return 0;
        }


    }
}
对应输出结果如下,可以看到发布事务监听事件之后,监听器即可获取到本次失败的数据内容并进行进一步的处理。
2024-02-12 11:29:28.922 ERROR 9716 --- [io-18080-exec-3] c.sharkChili.controller.TestController   : data保存失败,失败原因:/ by zero

java.lang.ArithmeticException: / by zero


2024-02-12 11:29:28.924  WARN 9716 --- [io-18080-exec-3] actionalApplicationListenerMethodAdapter : Processing org.springframework.context.PayloadApplicationEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@182038d5, started on Mon Feb 12 11:29:07 CST 2024] as a fallback execution on AFTER_ROLLBACK phase
2024-02-12 11:29:28.960  INFO 9716 --- [io-18080-exec-3] c.s.e.TDataTransactionalEventListener    : 处理回滚事件,data:{"data":"demoData","type":1}
优化
事务监听很适用于业务重试、失败补偿的场景,所以我们这里通过AOP将其进行封装:
Aspect
@Component
@Slf4j
public class TDataAspect {

    @Autowired
    private TransactionEventPublisher transactionEventPublisher;

    /**
     * 定义一个切点
     */
    @Pointcut("execution(public * com.sharkChili.controller..*Controller.*(..))")
    public void transactionPointcut() {
    }

    @Around("transactionPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            Object[] args = proceedingJoinPoint.getArgs();
            transactionEventPublisher.publishEvent(new TransactionEvent((TData) args[0]));
            result = 0;
        }
        // 排除字段,敏感字段或太长的字段不显示

        return result;
    }
}
这样Controller的业务代码就和事务监听解耦
   @PostMapping("save")
    public int saveData(@RequestBody TData tData) throws Exception {
        int count;
        try {
            return tDataService.saveData(tData);
        } catch (Exception e) {
            log.error("data保存失败,失败原因:{}", e.getMessage(), e);
            throw e;
        }


    }
小结
以上便是笔者对于事务监听的日常实用姿势,整体来说事务监听的开发步骤整体如下:
1.声明事件对象。
2.封装事件发布者。
3.基于TransactionalEventListener完成事务监听逻辑封装。
4.编写切面以捕获需要进行事务监听的业务代码,通过catch捕获异常,并发布事件。
5.事务监听器完成处理。
用户评论