来聊聊事务监听

写在文章开头

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数据库个人专栏导航。

相关推荐
程序猿麦小七9 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~17 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong19 分钟前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴20 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳24 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir34 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄38 分钟前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
码上一元5 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v7 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man8 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang