浅谈Spring事务传播行为实战
作者:西召 发布时间:2022-07-04 01:54:44
Spring框架提供了事务管理的标准实现,且可以通过注解或者XML文件的方式声明和配置事务。
通过异步事件的方式解耦服务调用,可以提高程序的响应速度,并且避免因为事务传播行为而导致的事务问题。
本文以一个电商平台包裹出库的业务为实际背景,通过异步事件与线程池的方式解耦嵌套事务,提高程序并发性能;为了便于问题的分析和方案的理解,同时还讲解了Spring的事务管理,并着重介绍了几种不同的事务传播行为。
事务小贴士
什么是事务呢?简单来讲事务就是逻辑上的一组操作,这些操作要么都执行,要么都不执行。
什么是事务管理呢?所谓事务管理,其实就是“按照给定的事务规则来执行事务的提交或者回滚操作”。
事务的机制实现很大一部依赖事务日志文件(undo log和redo log)。事务日志是一个与数据库文件分开的文件。它存储对数据库进行的所有更改,并全部记录插入、更新、删除、提交、回退和数据库模式变化。事务日志是备份和恢复的重要组件。
订单出库失败
在订单的包裹出库之前,会将出库指令下发到商城,其中包含包裹寄送的快递信息,以便销售平台展示快递跟踪信息;同时,为了防止前端因为超卖或者重复下单等原因,已经将订单取消,会根据前端商城的返回状态判断订单是否可以出库。
其中,系统交互流程如下,在前端销售平台与WMS(仓储管理系统)之间,会有统一的OMS(订单管理系统)进行销售单的管理和数据中转。
当前端销售平台收到出库信息以后,会进行如下的校验与操作:
为了防止调用通知服务的时候出现异常,影响包裹出库,调用通知服务的代码是用try-catch语句包裹起来的。
但是,某些订单在出库的时候,还是出现了如下异常,出库失败:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:534)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:305)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
根据事务的传播行为, Transaction rolled back because it has been marked as rollback-only
应该是因为被调用的事务回滚了,当调用一方提交事务的时候因为被标记为了 rollback-only ,因此无法正常提交。
Spring事务的传播机制
下面我们详细聊一下Spring事务的传播机制。
Spring的 @Transactional
注解可以用于声明方法支持事务,Spring底层通过AOP的方式实现事务的控制。
@Transactional
注解中的 Propagation
属性可以用于指定事务的传播行为。
/**
* The transaction propagation type.
* <p>Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。不支持当前事务。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。不支持当前事务。
TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。不支持当前事务。
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。支持当前事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。支持当前事务。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。支持当前事务。
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。支持当前事务。
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
利用事务传播行为的解决方案
基于上面事务传播行为的讲解,可以将事务的传播行为设置为 PROPAGATION_REQUIRES_NEW
,这样当前事务与被调用事务将是两个不同的事务,可以分别提交或者回滚。
在外层事务捕获异常以后执行如下代码,会输出 false
,说明内层事务回滚并未传播给外层事务: TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()
而在内层事务执行如下代码,会返回 true
,说明内层事务是一个新的事务,在执行改事务的时候,当前事务会被挂起: TransactionAspectSupport.currentTransactionStatus().isNewTransaction()
这样就解决了try-catch块抛出异常因事务传播行为导致的当前事务无法提交的问题。
利用多线程的解决方案
我们知道,在不同的线程之间,事务是独立的。对于发送通知消息这样的业务,适合通过抛出事件的方式,然后通过异步 * 进行处理。
其流程如下:
至于事件模型的实现方式,无论是分布式的Zookeeper、Redis和MQ,还是非分布式的Guava Event Bus、Spring Event都可以,可以根据实际需要选择。其核心原理仍然是发布订阅模式。
参考链接
数据库事务的方方面面
可能是最漂亮的Spring事务管理详解
Transaction必知必会
来源:https://juejin.im/post/5d6bc38d51882564341b994c
猜你喜欢
- 栈和队列:都是线性表,都是基于List基础上的实现线性表:数组,链表,字符串,栈,队列元素按照一条“直线&rdq
- 您可以通过为LINQ库不提供的操作添加新运算符,或者通过创建自己的标准查询运算符实现来提高可读性和性能,从而扩展Rx。 编写自定义版本的标准
- 本文将通过阅读spring源码,分析@ComponentScan注解扫描组件的原理。和@Bean注解一样,@ComponentScan注解也
- 1.问题产生情况我遇到这个问题是做微信开发的时候有些有用的头像用了微信的emoji表情,然而我的mysql数据库用的编码是utf8_gene
- IO的本质IO的作用就是从外部系统读取数据到java程序中,或者把java程序中输出的数据写回到外部系统。这里的外部系统可能是磁盘,网络流等
- C#中如何给Excel添加水印我们知道Microsoft Excel并没有内置的功能直接给Excel表添加水印,但是其实我们可以用其他变通的
- 之前学完了Java SE的知识,掌握了面向对象的编程思想,但对集合、多线程、反射、流的使用等内容理解的还不是很深入,打算再学习数据结构与算法
- 在使用feign调用其它服务时,发现获取的参数是null,当参数是对象是,是执行的Post请求,所以要在方法参数前加@RequestBody
- MongoDB的基本使用添加依赖<dependency>
- 最近在搭建springmvc的框架,遇到的这样的问题:在地址栏访问登陆界面访问不了,http://localhost/XXXX/WEB-IN
- 介绍在本文中,我们将了解如何将现有应用程序迁移到Spring 6以及如何充分利用此升级。本文中的提示基于我在Hypersistence Op
- 基类:using System;using System.Collections.Generic;using System.Linq;usi
- Controller如何进行重定向Spring MVC中进行重定向本人知道的有两种方式方法返回的URI(相对路径)中加上"redi
- 前言本文重点是要将mongodb与spring整合到项目中去,在实践中发现问题,追踪问题,然后解决问题。下面话不多说了,来一起看看详细的介绍
- 1.概述做一个项目,突然需要实现回复功能,所依记录一下此次的一个实现思路,也希望给别人分享一下,估计代码还是不够完善,有空在实现分页功能。话
- 前言作为一个写java的使用最多的轻量级框架莫过于spring,不管是老项目用到的springmvc,还是现在流行的springboot,都
- 概述在使用Spring Boot的时候我们经常使用actuator,健康检查,bus中使用/refresh等。这里记录如何使用注解的方式自定
- 本文实例讲述了Android获取手机屏幕大小的方法。分享给大家供大家参考,具体如下:这里主要用了三个对象TextView ,Button ,
- 这篇文章主要介绍了Java方法参数传递机制原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可
- springboot和vue结合的方案网络上的主要有以下两种:1. 【不推荐】在html中直接使用script标签引入vue和一些常用的组件