【Spring事务】Spring事务事件控制,解决业务异步操作

news2024/11/30 15:32:27

使用背景

在业务中,经常会有这样的需求,在数据库事务提交之后,发送异步消息或者进行其他的事务操作。

例如当用户注册成功之后,发送激活码,如果用户注册后就执行发送激活码,但是在用户保存时出现提交事务异常,数据库进行回滚,用户实际没有注册成功,但是用户却收到了激活码,此时,正确的是应该在用户注册保存事务提交完成之后,然后发送激活码。

标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

使用注解@TransactionalEventListener

demo展示

事务监听器

@Component
public class TransactionListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handler(TransactionEvent transactionEvent) {
        System.out.println(transactionEvent.getSource());
    }
}

业务代码

@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void saveUser() {
    User user = new User();
    userMapper.insert(user);
    eventPublisher.publishEvent(newTransactionEvent("事务提交后发布事件1"));
}
标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

源码解析

EventListenerMethodProcessor

EventListenerMethodProcessor用来解析带有带有@EventListener注解的方法。遍历类上的方法,判断工厂是否支持,用对应的工厂生成监听器。

	private void processBean(final String beanName, final Class<?> targetType) {
		if (!this.nonAnnotatedClasses.contains(targetType) &&
				AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
				!isSpringContainerClass(targetType)) {
			//...
			if (CollectionUtils.isEmpty(annotatedMethods)) {
				//...
			}
			else {
				// Non-empty set of methods
				ConfigurableApplicationContext context = this.applicationContext;
				Assert.state(context != null, "No ApplicationContext set");
				List<EventListenerFactory> factories = this.eventListenerFactories;
				Assert.state(factories != null, "EventListenerFactory List not initialized");
				for (Method method : annotatedMethods.keySet()) {
					for (EventListenerFactory factory : factories) {
						if (factory.supportsMethod(method)) {
							Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
							ApplicationListener<?> applicationListener =
									factory.createApplicationListener(beanName, targetType, methodToUse);
							if (applicationListener instanceof ApplicationListenerMethodAdapter) {
								((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
							}
							context.addApplicationListener(applicationListener);
							break;
						}
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
							beanName + "': " + annotatedMethods);
				}
			}
		}
	}

TransactionalEventListenerFactory仅支持TransactionalEventListener注解,生成ApplicationListenerMethodTransactionalAdapter的对象。

public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {
    private int order = 50;

    public TransactionalEventListenerFactory() {
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public int getOrder() {
        return this.order;
    }

    public boolean supportsMethod(Method method) {
        return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);
    }

    public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
        return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
    }
}

AbstractTransactionManagementConfiguration会引入TransactionalEventListenerFactory

    @Bean(
        name = {"org.springframework.transaction.config.internalTransactionalEventListenerFactory"}
    )
    @Role(2)
    public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
        return new TransactionalEventListenerFactory();
    }
标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

ApplicationListenerMethodTransactionalAdapter

发布事件,主要是创建了TransactionSynchronization,注册到了TransactionSynchronizationManager

    public void onApplicationEvent(ApplicationEvent event) {
        if (TransactionSynchronizationManager.isSynchronizationActive() && TransactionSynchronizationManager.isActualTransactionActive()) {
            TransactionSynchronization transactionSynchronization = this.createTransactionSynchronization(event);
            TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
        } else if (this.annotation.fallbackExecution()) {
            if (this.annotation.phase() == TransactionPhase.AFTER_ROLLBACK && this.logger.isWarnEnabled()) {
                this.logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
            }

            this.processEvent(event);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("No transaction is active - skipping " + event);
        }

    }
标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

事务提交

TransactionSynchronizationUtils#invokeAfterCompletion,事务提交会遍历TransactionSynchronization执行afterCompletion方法

    public static void invokeAfterCompletion(@Nullable List<TransactionSynchronization> synchronizations, int completionStatus) {
        if (synchronizations != null) {
            Iterator var2 = synchronizations.iterator();

            while(var2.hasNext()) {
                TransactionSynchronization synchronization = (TransactionSynchronization)var2.next();

                try {
                    synchronization.afterCompletion(completionStatus);
                } catch (Throwable var5) {
                    logger.error("TransactionSynchronization.afterCompletion threw exception", var5);
                }
            }
        }

ApplicationListenerMethodTransactionalAdapter.TransactionSynchronizationEventAdapter#afterCompletion,调用事件监听器的processEvent方法,会反射调用被@TransactionalEventListener修饰的方法。

        public void afterCompletion(int status) {
            if (this.phase == TransactionPhase.AFTER_COMMIT && status == 0) {
                this.processEvent();
            } else if (this.phase == TransactionPhase.AFTER_ROLLBACK && status == 1) {
                this.processEvent();
            } else if (this.phase == TransactionPhase.AFTER_COMPLETION) {
                this.processEvent();
            }

        }

        protected void processEvent() {
            this.listener.processEvent(this.event);
        }
标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

使用TransactionSynchronizationManager TransactionSynchronizationAdapter

demo展示

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
 
public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();
 
    // 兼容无论是否有事务
    if(TransactionSynchronizationManager.isActualTransactionActive()) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                jmsProducer.sendEmail(userId);
            }
        });
    } else {
        jmsProducer.sendEmail(userId);
    }
}
标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

在这里插入图片描述

标题复制10行,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作【Spring事务】Spring事务事件控制,解决业务异步操作,并且每行大于10个字符【Spring事务】Spring事务事件控制,解决业务异步操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/523241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【利用AI让知识体系化】入门Express框架

思维导图 文章目录 思维导图第一章&#xff1a;介绍Express什么是ExpressExpress优点Express应用场景 第二章&#xff1a;安装和基础用法安装Express搭建第一个Express应用中间件的使用 第三章&#xff1a;路由和控制器路由的原理路由的基本用法路由的进阶用法控制器的使用 第四…

BM 34 判断是否是二叉搜索树

判断是不是二叉搜索树_牛客题霸_牛客网 (nowcoder.com) 二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。 递归去做 &#xff0c;一段一段的去判断是否满足条件 /*** struct TreeNode {* int val;* struct TreeNode *left;* str…

c++ 11标准模板(STL) std::set(二)

定义于头文件 <set> template< class Key, class Compare std::less<Key>, class Allocator std::allocator<Key> > class set;(1)namespace pmr { template <class Key, class Compare std::less<Key>> using se…

超实用!50+个ChatGPT提示词助你成为高效Web开发者(上)

如果你已经感到编写代码的重复和繁琐让你疲惫不堪&#xff0c;想要提高自己的效率&#xff0c;那么你来对地方了。ChatGPT是一款能够帮助你优化工作流程、减少错误并获得提高代码的见解的强大工具。 在这篇博客文章中&#xff0c;我们将向你提供超过50个提示和策略&#xff0c;…

OpenCV教程——形态学操作。膨胀,腐蚀,开操作,闭操作,形态学梯度,顶帽,黑帽

1.形态学操作 图像形态学操作&#xff1a;基于形状的一系列图像处理操作的合集&#xff0c;主要是基于集合论基础上的形态学数学。 形态学有四个基本操作&#xff1a;膨胀、腐蚀、开、闭。 2.膨胀与腐蚀 2.1.膨胀 跟卷积操作类似&#xff0c;假设有图像A和结构元素B&#…

路径规划算法:基于灰狼优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于灰狼优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于灰狼优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法灰狼…

自媒体的孔雀效应:插根鸡毛还是专业才华?

自媒体时代&#xff0c;让许多原本默默无闻的人找到了表达自己的平台。有人声称&#xff0c;现在这个时代&#xff0c;“随便什么人身上插根鸡毛就可以当孔雀了”。可是&#xff0c;事实真的如此吗&#xff1f; 首先&#xff0c;我们不能否认的是&#xff0c;自媒体确实为大众提…

【大数据】通过 docker-compose 快速部署 Presto(Trino)保姆级教程

文章目录 一、概述二、前期准备1&#xff09;部署 docker2&#xff09;部署 docker-compose 三、创建网络四、Trino 编排部署1&#xff09;下载 trino2&#xff09;配置1、coordinator 配置2、worker 配置 3&#xff09;启动脚本 bootstrap.sh4&#xff09;构建镜像 Dockerfile…

多尺度深度特征(下):多尺度特征学习才是目标检测精髓(论文免费下载)...

计算机视觉研究院专栏 作者&#xff1a;Edison_G 深度特征学习方案将重点从具有细节的具体特征转移到具有语义信息的抽象特征。它通过构建多尺度深度特征学习网络 (MDFN) 不仅考虑单个对象和局部上下文&#xff0c;还考虑它们之间的关系。 公众号ID&#xff5c;ComputerVisionG…

MySQL- 存储引擎

MySQL体系结构 连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念&#xff0c;为通过认证安全接入的客户端…

通过自动装箱和拆箱解释所定义基础数据类型和其对应封装类的区别

文章目录 前言一、拆装箱的实质二、拓展1.数值超过128的Integer装箱2.Java内存分配 前言 在刷软中的时候涉及到了值传递和地址传递传参的区别&#xff0c;其中提到不管是将基础数据类型的变量传值给对象数据类型的变量还是反过来都属于值传递&#xff0c;究其原因就是期间发生了…

【本地模式】第一个Mapreduce程序-wordcount

【本地模式】&#xff1a;也就是在windows环境下通过hadoop-client相关jar包进行开发的&#xff0c;我们只需要通过本地自己写好MapReduce程序即可在本地运行。 一个Maprduce程序主要包括三部分&#xff1a;Mapper类、Reducer类、执行类。 map阶段&#xff1a;将每一行单词提…

XShell远程连接

xshell 是一个强大的安全终端模拟软件&#xff0c;它支持SSH1,SSH2以及microsoft windows 平台的TELNET协议。xshell通过互联网到远程主机的安全连接。 xshell可以在windows界面下来访问远程终端不同系统下的服务器&#xff0c;从而比较好的达到远程控制终端的目的。 步骤一 …

MySQL- 索引

索引是帮助MySQL高效获取数据的数据结构(有序)。在数据之外, 数据库系统还维护着满足特定查找算法的数据结构, 这些数据结构以某种方式引用数据, 这样就可以在这些数据结构上实现高级查找算法, 这种数据结构就是索引。 索引结构 MySQL的索引是在存储层实现的, 不同的存储引擎有…

网友总结:面试超过一个小时,通过概率更低;面试时长在半小时以内,通过概率更高!...

面试时长跟通过概率有关系吗&#xff1f; 一位网友分享了自己的求职感想&#xff1a; 面试过程越长&#xff0c;差不多一个小时或者超过一个小时&#xff0c;问得越详细&#xff0c;通过的可能性越低。因为问得越细&#xff0c;说明这个公司越挑&#xff0c;需要候选人匹配度越…

【C++ 入坑指南】(05)数据类型

文章目录 一、整型sizeof 关键字 二、实型&#xff08;浮点型&#xff09;三、字符型四、字符串型4.1 C 风格字符串4.2 C 引入的 string 类类型 五、布尔类型&#xff08;bool&#xff09;六、类型转换6.1 静态转换&#xff08;Static Cast&#xff09;6.2 动态转换&#xff08…

软考A计划-真题-分类精讲汇总-第六章(软件工程)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

阿里云服务器安装宝塔Linux面板教程图解

使用阿里云服务器安装宝塔面板教程&#xff0c;阿里云服务器网以CentOS操作系统为例&#xff0c;安装宝塔Linux面板&#xff0c;先远程连接到云服务器&#xff0c;然后执行宝塔面板安装命令&#xff0c;系统会自动安装宝塔面板&#xff0c;安装完成后会返回面板地址、账号和密码…

【mongoDB】mongodb权限验证 || mongodb重启 || mongodb常用命令

mongodb版本号 6.0 前言 mongoDB刚开始无需密码登录mongoDB有3默认数据库&#xff0c;分别为&#xff1a; admin 超级用户&#xff0c;能对所有数据库操作&#xff0c;执行管理员命令config 分片集群配置的数据库local 分片集群锁信息的集合test 这个数据库一般是隐式创建的&…

新书出版了(文末送书)

大家好&#xff0c;我是麦哥。 最近一位好友的新书出版了&#xff0c;由衷的替他开心&#xff0c;赶紧来支持一波。 新书长这样 这本书的作者是前中兴高级工程师&#xff0c;某知名培训机构的教学总监&#xff0c;现于某研究所担任重要的研发工作&#xff0c;我喜欢叫他彭老师。…