使用@TransactionalEventListener监听事务教程
作者:shiliang_feng 发布时间:2023-10-05 02:50:44
@TransactionalEventListener监听事务
项目背景
最近在项目遇到一个问题
A方法体内有 INSERT、UPDATE或者DELETE操作,最后会发送一段MQ给外部,外部接收到MQ后会再发送一段请求过来,系统收到请求后会执行B方法,B方法会依赖A方法修改后的结果,这就有一个问题,如果A方法事务没有提交;且B方法的请求过来了会查询到事务未提交前的状态,这就会有问题
解决办法:@TransactionalEventListener
在Spring4.2+,有一种叫做TransactionEventListener的方式,能够控制在事务的时候Event事件的处理方式。 我们知道,Spring的发布订阅模型实际上并不是异步的,而是同步的来将代码进行解耦。而TransactionEventListener仍是通过这种方式,只不过加入了回调的方式来解决,这样就能够在事务进行Commited,Rollback…等的时候才会去进行Event的处理。
具体实现
//创建一个事件类
package com.qk.cas.config;
import org.springframework.context.ApplicationEvent;
public class MyTransactionEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private IProcesser processer;
public MyTransactionEvent(IProcesser processer) {
super(processer);
this.processer = processer;
}
public IProcesser getProcesser() {
return this.processer;
}
@FunctionalInterface
public interface IProcesser {
void handle();
}
}
//创建一个监听类
package com.qk.cas.config;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
public class MyTransactionListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void hanldeOrderCreatedEvent(MyTransactionEvent event) {
event.getProcesser().handle();
}
}
//MQ方法的变动
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendCreditResult(String applyNo, String jsonString) {
eventPublisher.publishEvent(new MyTransactionEvent(() -> {
LOGGER.info("MQ。APPLY_NO:[{}]。KEY:[{}]。通知报文:[{}]", applyNo, Queues.CREDIT_RESULT, jsonString);
rabbitTemplate.convertAndSend(Queues.CREDIT_RESULT, jsonString);
}));
}
拓展
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 只有当前事务提交之后,才会执行事件监听的方法,其中参数phase默认为AFTER_COMMIT,共有四个枚举:
public enum TransactionPhase {
/**
* Fire the event before transaction commit.
* @see TransactionSynchronization#beforeCommit(boolean)
*/
BEFORE_COMMIT,
/**
* Fire the event after the commit has completed successfully.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and
* therefore executes in the same after-completion sequence of events,
* (and not in {@link TransactionSynchronization#afterCommit()}).
* @see TransactionSynchronization#afterCompletion(int)
* @see TransactionSynchronization#STATUS_COMMITTED
*/
AFTER_COMMIT,
/**
* Fire the event if the transaction has rolled back.
* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and
* therefore executes in the same after-completion sequence of events.
* @see TransactionSynchronization#afterCompletion(int)
* @see TransactionSynchronization#STATUS_ROLLED_BACK
*/
AFTER_ROLLBACK,
/**
* Fire the event after the transaction has completed.
* <p>For more fine-grained events, use {@link #AFTER_COMMIT} or
* {@link #AFTER_ROLLBACK} to intercept transaction commit
* or rollback, respectively.
* @see TransactionSynchronization#afterCompletion(int)
*/
AFTER_COMPLETION
}
注解@TransactionalEventListener
例如 用户注册之后需要计算用户的邀请关系,递归操作。如果注册的时候包含多步验证,生成基本初始化数据,这时候我们通过mq发送消息来处理这个邀请关系,会出现一个问题,就是用户还没注册数据还没入库,邀请关系就开始执行,但是查不到数据,导致出错。
@TransactionalEventListener 可以实现事务的监听,可以在提交之后再进行操作。
监听的对象
package com.jinglitong.springshop.interceptor;
import com.jinglitong.springshop.entity.Customer;
import org.springframework.context.ApplicationEvent;
public class RegCustomerEvent extends ApplicationEvent{
public RegCustomerEvent(Customer customer){
super(customer);
}
}
监听到之后的操作
package com.jinglitong.springshop.interceptor;
import com.alibaba.fastjson.JSON;
import com.jinglitong.springshop.entity.Customer;
import com.jinglitong.springshop.entity.MqMessageRecord;
import com.jinglitong.springshop.servcie.MqMessageRecordService;
import com.jinglitong.springshop.util.AliMQServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class RegCustomerListener {
@Value("${aliyun.mq.order.topic}")
private String topic;
@Value("${aliyun.mq.regist.product}")
private String registGroup;
@Value("${aliyun.mq.regist.tag}")
private String registTag;
@Autowired
MqMessageRecordService mqMessageRecordService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void hanldeRegCustomerEvent(RegCustomerEvent regCustomerEvent) {
Customer cust = (Customer) regCustomerEvent.getSource();
Map<String, String> map = new HashMap<String, String>();
map.put("custId", cust.getZid());
map.put("account", cust.getAccount());
log.info("put regist notice to Mq start");
String hdResult = AliMQServiceUtil.createNewOrder(cust.getZid(), JSON.toJSONString(map),topic,registTag,registGroup);
MqMessageRecord insert = buidBean(cust.getZid(),hdResult,registTag,JSON.toJSONString(map),registGroup);
if(StringUtils.isEmpty(hdResult)) {
insert.setStatus(false);
}else {
insert.setStatus(true);
}
mqMessageRecordService.insertRecord(insert);
log.info("put regist notice to Mq end");
log.info("regist notice userId : " + cust.getAccount());
}
private MqMessageRecord buidBean (String custId,String result ,String tag,String jsonStr,String groupId) {
MqMessageRecord msg = new MqMessageRecord();
msg.setFlowId(custId);
msg.setGroupName(groupId);
msg.setTopic(topic);
msg.setTag(tag);
msg.setMsgId(result);
msg.setDataBody(jsonStr);
msg.setSendType(3);
msg.setGroupType(1);
msg.setCreateTime(new Date());
return msg;
}
}
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
applicationEventPublisher.publishEvent(new RegCustomerEvent (XXX));
这样可以确保数据入库之后再进行异步计算
来源:https://my.oschina.net/u/4021946/blog/4915478
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 一、maven引入依赖,数据库驱动根据项目需求自行引入<!-- https://mvnrepository.com/artifact/
- springboot整合tkmapper1.导入pom依赖1.1 导入springboot的parent依赖<parent> &
- SpringBoot 如何进行参数校验在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验,例如登录的时候需要校验
- 本文实例为大家分享了Java使用开源Rxtx实现串口通讯的具体代码,供大家参考,具体内容如下使用方法:windows平台:1、把rxtxPa
- 第一次进入应用的时候,都会有一个引导页面,引导页面的实现起来也很简单,实现的方式也有很多,下面是自己写的一个引导页面的效果,大致的实现思路为
- java 单例的五种实现方式及其性能分析序言在23种设计模式中,单例是最简单的设计模式,但是也是很常用的设计模式。从单例的五种实现方式中我们
- 第一种方法:string s=abcdeabcdeabcde;string[] sArray=s.Split(c) ;foreach(str
- 0x01 新建SpringBoot项目1. 新建maven工程ps:在上一教程的基础上操作,就不用新建项目了,请参考文章:SpringBoo
- spring-retry模块支持方法和类、接口、枚举级别的重试方式很简单,引入pom包<parent> <gr
- Vector实现班级信息管理系统,供大家参考,具体内容如下代码如下:import java.util.*;public class Demo
- 因为公司现在换成了nacos,所以自己写了demo学习一下。结果第一步就走不下去。在使用nacos-config读取nacos配置时。发现b
- 限流背景在早期的计算机领域,限流技术(time limiting)被用做控制网络接口收发通信数据的速率。可以用来优化性能,减少延迟和提高带宽
- 数据加解密的实现方式多种多样,在mybatis环境中数据加解密变得非常简单易用,本文旨在提供参考,在生产中应尽可能完成单元测试,开展足够的覆
- 简介本文用示例介绍使用MyBatis-Plus进行多表查询的方法,包括静态查询和动态查询。代码controllerpackage com.e
- 问题今天在springboot中使用mybatis的时候不能字段不能够进行自动映射,mybatis的版本是3.5.11,数据库是按照下划线进
- 本文实例讲述了Spring实战之协调作用域不同步的Bean操作。分享给大家供大家参考,具体如下:一 配置<?xml version=&
- ${project.basedir}的使用<project> 是 pom.xml 的根节点,project.basedir 就是
- 写在前面,在笔者完成这个demo的时候,笔者发现现在大家已经不用Ajax来完成联级菜单了,实际上笔者这个demo也并不是为了完成这个,笔者主
- 实例如下:package com.bwsk.modules.weixin.util;import java.util.Random;/**
- 一、分步骤集成1.1 整合连接池hikariCP介绍:HikariCP 是一个高性能的 JDBC 连接池组件,可以避免连接频繁建立、关闭的开