如何优雅地Spring事务编程

本文已收录至Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录

在开发中,有时候我们需要对 Spring 事务的生命周期进行监控,比如在事务提交、回滚或挂起时触发特定的逻辑处理。那么如何实现这种定制化操作呢?

Spring 作为一个高度灵活和可扩展的框架,早就提供了一个强大的扩展点,即事务同步器 TransactionSynchronization 。通过 TransactionSynchronization ,我们可以轻松地控制事务生命周期中的关键阶段,实现自定义的业务逻辑与事务管理的结合。

package org.springframework.transaction.support;import java.io.Flushable;public interface TransactionSynchronization extends Flushable {/** 事务提交状态 */int STATUS_COMMITTED = 0;/** 事务回滚状态 */int STATUS_ROLLED_BACK = 1;/**系统异常状态 */int STATUS_UNKNOWN = 2;//挂起该事务同步器default void suspend() {}//恢复事务同步器default void resume() {}//flush底层的session到数据库default void flush() {}// 事务提交之前default void beforeCommit(boolean readOnly) {}// 操作完成之前(包含commit/rollback)default void beforeCompletion() {}// 事务提交之后default void afterCommit() {}// 操作完成之后(包含commit/rollback)default void afterCompletion(int status) {}
}

TransactionSynchronization 是一个接口,它里面定义了一系列与事务各生命周期阶段相关的方法。比如,我们可以这样使用:

public class UserService {@Transactional(rollbackFor = Exception.class)public void saveUser(User user) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {System.out.println("saveUser事务已提交...");}});userDao.saveUser(user);}
}

在 Spring 事务刚开始的时候,我们向 TransactionSynchronizationManager 事务同步管理器注册了一个事务同步器,事务提交前/后,会遍历执行事务同步器中对应的事务同步方法(一个 Spring 事务可以注册多个事务同步器)。

需要注意的是注册事务同步器必须得在一个 Spring 事务中才能注册,否则会抛出 Transaction synchronization is not active 这个错误。

isSynchronizationActive() 方法用来判断当前是否存在事务(判断线程共享变量,是否存在 TransactionSynchronization)

Spring 在创建事务的时候,会初始化一个空集合放到 synchronizations 属性中,所以只要当前存在事务,isSynchronizationActive() 就为 true。

TransactionSynchronizationManager 解析

Spring 对于事务的管理都是基于 TransactionSynchronizationManager 这个类,先看下 TransactionSynchronizationManager 的一些属性:

    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
  • resources:保存连接资源,因为一个方法里面可能包含多个事务,所以就用 Map 来保存资源, key为 DataSource,value 为connectionHolder。线程可以通过该属性获取到同一个 Connection 对象。
  • synchronizations:事务同步器,是 Spring 交由程序员进行扩展的代码,每个线程可以注册N个事务同步器。
  • currentTransactionName:事务的名称。
  • currentTransactionReadOnly:事务是否是只读。
  • currentTransactionIsolationLevel:事务的隔离级别。
  • actualTransactionActive:用于保存当前事务是否还是 Active 状态(事务是否开启)。

Spring 创建事务时,DataSourceTransactionManager.doBegin 方法中,将新创建的 connection 包装成 connectionHolder ,通过 TransactionSynchronizationManager#bindResource 方法存入 resources 中。

然后标注到一个事务当中的其它数据库操作就可以通过 TransactionSynchronizationManager#getResource 方法获取到这个连接。

    @Nullablepublic static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");}return value;}@Nullableprivate static Object doGetResource(Object actualKey) {Map<Object, Object> map = (Map)resources.get();if (map == null) {return null;} else {Object value = map.get(actualKey);if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {map.remove(actualKey);if (map.isEmpty()) {resources.remove();}value = null;}return value;}}

从上面我们也能看到,Spring 对于多个数据库操作的事务实现是基于 ThreadLocal 的,所以 Spring 事务操作是无法使用多线程的。

应用场景

TransactionSynchronization 可以用于一些需要在事务结束后执行清理操作或其他相关任务的场景。

应用场景举例:

  • 资源释放:在事务提交或回滚后释放资源,如关闭数据库连接、释放文件资源等。
  • 日志记录:在事务结束后记录相关日志信息,例如记录事务的执行结果或异常情况。
  • 缓存更新:在事务完成后更新缓存数据,保持缓存和数据库数据的一致性。
  • 消息通知:在事务结束后发送消息通知相关系统或用户,如发送邮件或短信通知。

举例: 假设一个电商系统中存在订单支付的业务场景,当用户支付订单时,需要在事务提交后发送订单支付成功的消息通知给用户。

由于事务是和数据库连接相绑定的,如果把发送消息和数据库操作放在一个事务里面。当发送消息时间过长时会占用数据库连接,所以就要把数据库操作与发送消息到 MQ 解耦开来。

这时就可以通过 TransactionSynchronization 来实现在事务提交后发送消息通知的功能。具体示例代码如下:

@Component
public class OrderPaymentNotification implements TransactionSynchronization {private String orderNo;public OrderPaymentNotification(String orderNo) {this.orderNo = orderNo;}@Overridepublic void beforeCommit(boolean readOnly) {// 在事务提交前不执行任何操作}@Overridepublic void beforeCompletion() {// 在事务即将完成时不执行任何操作}@Overridepublic void afterCommit() {// 在事务提交后发送订单支付成功的消息通知sendMessage("订单支付成功", orderNo);}@Overridepublic void afterCompletion(int status) {// 在事务完成后不执行任何操作}private void sendMessage(String message, String orderNo) {// 发送消息通知的具体实现逻辑System.out.println(message + ": " + orderNo);}
}
    @Transactionalpublic void finishOrder(String orderNo) {// 修改订单成功updateOrderSuccess(orderNo);// 发送消息到 MQTransactionSynchronizationManager.registerSynchronization(new OrderPaymentNotification(orderNo));}

这样当事务成功提交之后,就会把消息发送给 MQ,并且不会占用数据库连接资源。

@TransactionalEventListener

在 Spring Framework 4.2版本后还可以使用 @TransactionalEventListener 注解处理数据库事务提交成功后的执行操作。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;// 表明若没有事务的时候,对应的event是否需要执行,默认值为false表示,没事务就不执行了。boolean fallbackExecution() default false;@AliasFor(annotation = EventListener.class,attribute = "classes")Class<?>[] value() default {};@AliasFor(annotation = EventListener.class,attribute = "classes")Class<?>[] classes() default {};String condition() default "";
}public enum TransactionPhase {// 在事务commit之前执行BEFORE_COMMIT,// 在事务commit之后执行AFTER_COMMIT,// 在事务rollback之后执行AFTER_ROLLBACK,// 在事务完成后执行(包括commit/rollback)AFTER_COMPLETION;private TransactionPhase() {}
}

从命名上可以直接看出,它就是个 EventListener,效果跟 TransactionSynchronization 一样,但比 TransactionSynchronization 更加优雅。它的使用方式如下:

@Data
public class Order {private Long orderId;private String orderNumber;private BigDecimal totalAmount;
}@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate ApplicationEventPublisher eventPublisher;@Transactionalpublic void createOrder(Order order) {// 保存订单逻辑System.out.println("Creating order: " + order.getOrderNumber());orderRepository.save(order);// 发布订单创建事件OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(order);eventPublisher.publishEvent(orderCreatedEvent);}
}@Getter
@Setter
public class OrderCreatedEvent {private Order order;public OrderCreatedEvent(Order order) {this.order = order;}
}@Component
@Slf4j
public class OrderEventListener {@Autowiredprivate EmailService emailService;/** @Async加了就是异步监听,没加就是同步(启动类要开启@EnableAsync注解)* 可以使用@Order定义监听者顺序,默认是按代码书写顺序* 可以使用SpEL表达式来设置监听器生效的条件* 监听器可以看做普通方法,如果监听器抛出异常,在publishEvent里处理即可*/@Async@Order(1)@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, classes = OrderCreatedEvent.class)public void onOrderCreatedEvent(OrderCreatedEvent event) {// 处理订单创建事件,例如发送邮件通知log.info("Received OrderCreatedEvent for order: " + event.getOrder().getOrderNumber());emailService.sendOrderConfirmationEmail(event.getOrder());}
}

都看到这里了,如果觉得有帮助,还请您给我个小小的鼓励,动动手指,帮忙点个赞或收藏!谢谢喽,如果觉得文章有误,欢迎在评论区留言指正,我会第一时间讨论并改正!!!

也欢迎关注微信公众号「Java随想录」第一时间阅读,专注分享Java技术干货,文章持续更新。

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

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

相关文章

直播报名 | 科技出海新势力,遥感+AI助力一带一路

遥感技术的出海之路顺畅吗&#xff1f; 国内外遥感应用的关注点相同吗&#xff1f; 目前珈和主要辐射哪些海外国家&#xff1f; … 上周数据赋农季第三期《科技出海&#xff0c;遥感AI赋能“一带一路”提升种植园规模效益》直播预告一出&#xff0c;小伙伴们纷纷来咨询珈和的海…

CentOS安装htop工具

启用 EPEL Repository 安装Htop 首先启用 EPEL Repository: yum -y install epel-release启用 EPEL Repository 后, 可以用 yum 直接安裝 Htop: 安装htop yum -y install htop安装成功 输入htop使用工具 htop安装glances工具 yum install glances

Springboot+Vue项目-基于Java+MySQL的企业客户管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

前端零代码开发实践:页面嵌套+逻辑连线0开发扩展组件,实现切换开关控制扇叶转动。能无代码封装扩展组件,有别于常规的web组态或低代码平台

前言&#xff1a; 官网:http://www.uiotos.net/ 什么是 UIOTOS&#xff1f; 这是一款拥有独创专利技术的前端零代码工具&#xff0c;专注于解决前端界面开发定制难题&#xff0c;原型即应用&#xff01;具有页面嵌套、属性继承、节点连线等全新特性&#xff0c;学习门槛低…

网络安全之SQL注入及防御(下篇)

目录 什么是SQL注入&#xff1f; 一&#xff0c;SQL注入会导致什么呢&#xff1f; 二&#xff0c;SQL注入思想与步骤 三&#xff0c;SQL注入的绕过 四&#xff0c;sqlmap工具的使用 五&#xff0c;sql注入的防御方法 总结 什么是SQL注入&#xff1f; SQL注入&#xff08;…

第二证券|股票做短线要关注什么?

在股市中短线交易因其快速的盈利时机而招引了众多投资者&#xff0c;但做短线想要挣钱也不是那么容易的。对于股票做短线要重视什么&#xff0c;第二证券下面就为我们具体介绍一下。 短线交易需重视&#xff1a; 1、商场短期趋势。短线投资者首先需要重视的是全体商场趋势&am…

tokio多任务绑定cpu(绑核)

tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢&#xff1f; 首先我们先写一段简单的多任务程序。 use tokio; use tokio::runtime; use core_affinity;fn tokio_sample() {let rt runtime::Builde…

【软考】UML中的关系

目录 1. 说明2. 依赖3. 关联4. 泛化5. 实现 1. 说明 1.UML中有4种关系&#xff1a;依赖、关联、泛化和实现2.这 4种关系是 UML,模型中可以包含的基本关系事物。它们也有变体&#xff0c;例如&#xff0c;依赖的变体有精化、跟踪、包含和延伸 2. 依赖 1.依赖(Dependency)。2.…

【InternLM实战营---第六节课笔记】

一、本期课程内容概述 本节课的主讲老师是【樊奇】。教学内容主要包括以下三个部分&#xff1a; 1.大模型智能体的背景及介绍 2. Lagent&AgentLego框架介绍 3.Lagent&AgentLego框架实战 二、学习收获 智能体出现的背景 智能体的引入旨在克服大模型在应对复杂、动态任…

【设计模式】使用中介者模式优化表单交互

我们想象一下机场的指挥塔&#xff0c;如果没有指挥塔的存在&#xff0c;每一架飞机要和方圆 100 公里内的所有飞机通信&#xff0c;才能确定航线以及飞行状况&#xff0c;后果是不可想象的。现实中的情况是&#xff0c;每架飞机都只需要和指挥塔通信。指挥塔作为调停者&#x…

93页 | 数据中台标准技术体系方案(免费下载)

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 数据中台标准技术体系方案 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT解决方案&#…

文件上传漏洞-白名单检测

如何确认是否是白名单检测 上传一张图片与上传一个自己构造的后缀&#xff0c;如果只能上传图片不能上传其它后缀文件&#xff0c;说明是白名单检测。 绕过技巧 可以利用 00 截断的方式进行绕过&#xff0c;包括 %00 截断与 0x00 截断。除此之外如果网站存在文件包含漏洞&…

ElasticSearch总结二

正向索引和倒排索引&#xff1a; 正向索引&#xff1a; 比方说我这里有一张数据库表&#xff0c;那我们知道对于数据库它一般情况下都会基于i d去创建一个索引&#xff0c;然后形成一个b树。 那么你根据i d进行检索的速度&#xff0c;就会非常的快&#xff0c;那么这种方式的…

智慧水生态系统的架构设计与优化:内蒙古硕达智水百数低代码平台的实践

随着业务的不断扩展&#xff0c;传统的项目管理方式已无法满足现代水生态项目的需求。为提高项目管理和决策效率&#xff0c;内蒙古硕达智水生态科技决定引入百数作为其数字化转型的合作伙伴。 内蒙古硕达智水生态科技有限公司&#xff1a; 内蒙古硕达智水生态科技有限公司&a…

用于割草机器人,商用服务型机器人的陀螺仪

介绍一款EPSON推出适用于割草机器人&#xff0c;商用服务型机器人的高精度陀螺仪模组GGPM61&#xff0c;具体型号为GGPM61-C01。模组GGPM61是一款基于QMEMS传感器的低成本航向角输出的传感器模组&#xff0c;它可以输出加速度、角速度及姿态角等信息&#xff0c;为控制机器人运…

人工智能基础-Python之Pandas库教程

文章目录 前言一、Pandas是什么&#xff1f;二、使用步骤1.引入库2.数据读取2.1 数据类型2.2 数据读取1.常见操作2.txt读取 3.pandas的数据结构3.1 Series1.属性2.创建Series3.查询 3.2 DataFrame1.创建DataFrame 4.查询数据4.1 data.loc 根据行列标签值进行查询1.使用单个labe…

ASP.NET Core 3 高级编程(第8版) 学习笔记 04

第 19 章主要介绍 Restful Service 的相关知识。Restful Service 的核心内容是&#xff1a;&#xff08;1&#xff09;HTTP 请求或 HTTP 动词&#xff0c;用 HTTP 请求表达不同的操作&#xff0c;最好遵守惯例。&#xff08;2&#xff09;资源&#xff0c;通过 PATH 结合 paylo…

stm32f4单片机强制类型转换为float程序跑飞问题

如题&#xff0c;在一个数据解析函数中使用了*(float *)&data[offset]&#xff0c;其中data为uint8类型指针&#xff0c;指向的value地址为 可以看到地址0x20013A31非对齐&#xff0c;最终在执行VLDR指令时导致跑飞 VLDR需要使用对齐访问 跑飞后查看SCB寄存器发现确实是非…

【网络安全】在网络中如何对报文和发送实体进行鉴别?

目录 1、报文鉴别 &#xff08;1&#xff09;使用数字签名进行鉴别 &#xff08;2&#xff09;密码散列函数 &#xff08;3&#xff09;报文鉴别码 2、实体鉴别 鉴别(authentication) 是网络安全中一个很重要的问题。 一是要鉴别发信者&#xff0c;即验证通信的对方的确是…

微博评论爬取

import requests import csv# 打开CSV文件以写入数据 f open(data.csv, modea, encodingutf-8-sig, newline) csv_writer csv.DictWriter(f, fieldnames[昵称, 性别, 归属地, 内容]) csv_writer.writeheader()# 定义一个函数用于获取评论内容 def GetContent(max_id):# 设置请…