来聊聊事务监听

写在文章开头

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

你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:写代码的SharkChili,实时获取笔者最新的技术推文同时还能和笔者进行深入交流。

事务监听基础示例

整体流程

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

封装事件

了解整体工作流程之后,我们就可以开始编码,由上可知,我们感知失败事务时要发送错误消息,所以我们封装一个事件对象,记录保存失败的TData 。

kotlin 复制代码
/**
 * tdata事件,记录tdata的信息
 */
public class TransactionEvent {

    private TData data;

    public TransactionEvent(TData data) {
        this.data = data;
    }

    public TData getData() {
        return data;
    }
}

为了文章的完整性我们给出TData的代码

kotlin 复制代码
@Data
public class TData {
    private Integer id;

    private String data;

    private Byte type;
}

发布异常

完成事件编写之后,我们就需要事件发布事务失败事件这一步,所以我们要编写一个事件发布者,如下所示,可以看到笔者注入Spring的事件发布工具,通过eventPublisher来发布我们的事务失败事件TransactionEvent

typescript 复制代码
@Component
public class TransactionEventPublisher {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishEvent(TransactionEvent event) {
        eventPublisher.publishEvent(event);
    }
}

感知事件

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

less 复制代码
@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模拟事务保存异常:

ini 复制代码
 @Transactional
    public int saveData(TData tData) {

        int count = tDataMapper.insert(tData);
        int i = 1 / 0;
        return count;
    }

对应的Controller层,设置try-catch异常捕获,感知事务失败的错误后,发布一个错误事件:

less 复制代码
@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;
        }


    }
}

对应输出结果如下,可以看到发布事务监听事件之后,监听器即可获取到本次失败的数据内容并进行进一步的处理。

kotlin 复制代码
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将其进行封装:

java 复制代码
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的业务代码就和事务监听解耦

less 复制代码
    @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. 事务监听器完成处理。

我是sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili ,同时我的公众号也有我精心整理的并发编程JVMMySQL数据库个人专栏导航。

相关推荐
程序员cxuan1 小时前
速度太快了!本地可以跑 DeepSeek-V4-Flash 了
人工智能·后端·程序员
阿祖zu2 小时前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才2 小时前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒3 小时前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20233 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei3 小时前
controller注册及调用时机
后端
edwarddamon3 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈4 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas1004 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
计算机魔术师5 小时前
传统基础架构 - 微服务
大数据·后端·微服务·架构·开发工具·编程语言