【工作中问题解决实践 九】Spring中事务传播的问题排查
最近在工作中遇到了两个关于事务操作的问题,顺便就着这两个问题又回顾了一遍Spring的事务相关的操作,想着一次性把这个问题研究明白了,后续使用事务的时候也能踏实点,让事务发挥真实的作用
什么是事务?什么是事务管理?什么是Spring事务
什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错。
什么是事务管理,事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不受损失
关于事务的基本概念和定义可以参照我的另一篇Blog:【Spring学习笔记 九】Spring声明式事务管理实现机制。Sping事务简而言之就是一种JTA事务,这里不再详细展开。
一个用来演示的例子
我们还是沿用:【Spring学习笔记 九】Spring声明式事务管理实现机制这篇文章中的例子,只不过为了更贴近工作实战,这里我重构了一下代码实现。
单元测试入口
package com.example.springboot;import com.example.springboot.model.Person;
import com.example.springboot.service.PersonAggService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.addPerson(person, 100086L);}
}
聚合的Service方法
package com.example.springboot.service;import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonAggService {@Resourceprivate PersonService personService;@Resourceprivate PersonMaintainService personMaintainService;public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);//保存人员创建者personMaintainService.savePersonCreator(creatorId);}
}
数据服务方法
package com.example.springboot.service;import com.example.springboot.dao.PersonDao;
import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}public void insert(Person person) {personDao.insert(person);}}
人员维护人添加方法
package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}
数据表落库

不使用事务的情况
不使用事务的情况虽然单元测试报错了

但是数据库落库还是成功了:

遇到的两个事务问题
依据以上的基本case示例,模拟我遇到的两个问题和解决方案
Transaction rolled back because it has been marked as rollback-only
为了保证整体数据与预期一致可以回滚,我使用了事务,首先在外层加事务:
@Transactional(rollbackFor = Exception.class)public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);try {//发送人员同步到下游系统personMaintainService.savePersonCreator(creatorId);} catch (Exception e) {System.out.println("保存人员维护人异常但是被catch住了");}}
同时呢人员创建人这块我认为这里不需要报错阻塞整体操作,如果这里有问题只要有日志记录就行了,我通过巡检检查关注到即可,所以对这块代码加了try catch,但是呢因为内部代码不知道是谁写的也加了事务,
@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}
因为它们用的都是默认的传播机制,所以可以看做一个事务,使用REQUIRED传播模式,addAndSendPerson和savePersonCreator在同一个事务里面,savePersonCreator抛出异常要回滚,addAndSendPerson try Catch了异常正常执行commit,同一个事务一个要回滚,一个要提交,会报read-only异常,结果就是全部回滚,而外层所以这里就会出现rollback-only

解决方法有两种,一种是
干掉内层事务
把内层的savePersonCreator事务干掉,这时数据也能落库成功了,事实上因为JTA的事务是有非常强的业务含义的,所以对于DAO层或简单的数据操作指令,不要加事务,否则对于较长的外部调用链路,会在传播过程中导致意外情况发生

内层声明为新事务
还有一种解决思路就是内层的事务声明为新事务
package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;/*** @author tianmaolin004* @date 2023/8/6*/
@Service
public class PersonMaintainService {@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Long userId) {System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}
声明后再跑单测:

数据也落库成功了,因为是两个独立事务,所以内层事务遇到异常回滚,外层事务捕获到了异常catch住了,没有继续回滚
事务设置为什么不生效?
还有个例子是方法设置了事务但是不生效,我们再调整下以上的代码,模拟一种场景:savePerson要执行很多事项,但是不希望saveDate的执行异常回滚影响整体回滚,所以saveDate中的核心数据操作被try catch,并且声明内部的savePersonCreator方法为新事务,符合上边我们提到的那种场景,这种情况下理论上savePersonCreator抛出异常后会使 personDao.insert(person);回滚,数据不能写入
@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}}@Service
public class PersonAggService {@ResourcePersonDao personDao;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");saveDate(person, creatorId);}@Transactional(rollbackFor = Exception.class)public void saveDate(Person person, Long creatorId) {//本地新增人员try {savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println("捕获到创建人员异常");}}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}
但事实上,数据库写入数据能成功:

数据库数据写入成功了

这是因为:Spring中事务的默认实现使用的是AOP,也就是代理的方式,如果大家在使用代码测试时,同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名来调用,this.方法名调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用,所以实际上saveDate和savePersonCreator的事务都没有生效
把需要成为事务的方法单独抽出来
上述代码我们把需要有事务机制的savePersonCreator单独抽到一个方法中
@SpringBootTest
class SpringbootApplicationTests {@Resourceprivate PersonAggService personAggService;@Testpublic void springTransTest() {Person person = new Person();person.setUsername("wcong");person.setAge(30);person.setEmail("111111@qq.com");person.setPassword("111111");person.setPhone(11111111);person.setHobby("跳远");personAggService.savePerson(person, 100086L);}}@Service
public class PersonAggService {@ResourcePersonService personService;@Transactional(rollbackFor = Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println("执行其它事项");saveDate(person, creatorId);}@Transactional(rollbackFor = Exception.class)public void saveDate(Person person, Long creatorId) {//本地新增人员try {personService.savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println("捕获到创建人员异常");}}
}@Service
public class PersonService {@ResourcePersonDao personDao;public List<Person> getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println("保存人员创建者失败" + userId);throw new RuntimeException();}
}
这样savePersonCreator的事务就生效了,数据没有插入成功
Spring事务的更多传播机制
以上两个示例是真实工作中遇到的,基于安全原则模拟了两个类似的case,其实spring还有更多的花式的事务使用机制,可以参照带你读懂Spring 事务——事务的传播机制
总结一下
照例总结一下,在单一的数据操作方法不要加事务,事务应该是一系列操作指令的聚合,添加了细粒度的事务可能会导致上层使用者在方法添加事务时产生了非预期的传播机制。当然如果内外层的方法调用都很复杂,则基于自己的预期进行考虑,如果不希望内层方法影响外层方法,可以使用外层方法异常捕获加内层事务的REQUIRES_NEW传播机制解决。需要注意的是Spring的事务是基于AOP实现的,所以对象内部方法调用,不会通过Spring代理,也就是事务不会起作用,这点非常重要。
相关文章:
【工作中问题解决实践 九】Spring中事务传播的问题排查
最近在工作中遇到了两个关于事务操作的问题,顺便就着这两个问题又回顾了一遍Spring的事务相关的操作,想着一次性把这个问题研究明白了,后续使用事务的时候也能踏实点,让事务发挥真实的作用 什么是事务?什么是事务管理…...
【导出Word】如何使用Java+Freemarker模板引擎,根据XML模板文件生成Word文档(只含文本内容的模板)
这篇文章,主要介绍如何使用JavaFreemarker模板引擎,根据XML模板文件生成Word文档。 目录 一、导出Word文档 1.1、基础知识 1.2、制作模板文件 1.3、代码实现 (1)引入依赖 (2)创建Freemarker工具类 &…...
Devart dbForge Studio for MySQL Crack
Devart dbForge Studio for MySQL Crack dbForge Studio for MySQL是一个用于MySQL和MariaDB数据库开发、管理和管理的通用GUI工具。IDE允许您通过直观的界面创建和执行查询、开发和调试存储例程、自动化数据库对象管理、分析表数据。MySQL客户端提供了数据和模式比较和同步工具…...
C++、Java、JavaScript和python几个语句的对比介绍
C、Java、JavaScript和python几个语句的对比介绍 C、Java、JavaScript和python语言的for语句 C、Java和JavaScript的for语句的语法类似如下: for (初始条件; 循环条件; 循环后操作) { // 循环体代码 } 初始条件是在进入循环之前执行的语句,初始化循环…...
第20节 R语言医学分析:某保险医疗事故赔偿因素分析
文章目录 某保险医疗事故赔偿因素分析源码源文件下载某保险医疗事故赔偿因素分析 我们分析数据集“诉讼”的第一个方法是确定样本数量、变量类型、缩放/编码约定(如果有)用于验证数据清理。 接下来,数据集看起来很干净,没有缺失值,并且对于分类变量,将编码约定替换为实际…...
【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight 4
知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…...
解决K8S集群设置污点后,污点不生效,下发应用的问题
问题:在集群中部署了三个daemonset,一开始加了容忍Toleration,后边去掉Toleration后,还是一直往边缘节点上部署应用,非常离谱 解决:删掉Toleration后,需要把annotations一起删掉,因为…...
使用$test$plusargs提高RTL验收速度
文章目录 0 前言1 语法介绍2 示例3 多种情况的testbench怎么写 0 前言 这段时间在整合一个小ip,因为要验证每个feature是否可行,需要用testbench C语言的方式进行仿真验证,由于每种feature不仅要在C语言中修改寄存器配置,还要再…...
MySQL~mysql基础应用相关题
整卷阅览: 想要获取试卷原版请点击以下链接下载: https://download.csdn.net/download/qq_53142796/88168133https://download.csdn.net/download/qq_53142796/88168133 解题过程: 数据库:studentdb 数据库表如下: …...
Redis | 哨兵模式
Redis | 哨兵模式 1. 简介 Redis Sentinel(哨兵)是Redis提供的一种高可用性解决方案。它是一个独立的进程,用于监控和管理Redis主从模式的节点,并在主节点故障时自动进行故障转移。哨兵模式可以确保Redis集群的高可用性和数据一…...
MySQL语句性能分析与优化
目录 SQL性能分析 SQL执行频率 SQL慢查询日志 Profile Explain SQL优化 插入数据的优化 主键优化 Order By优化 Group By优化 Limit 优化 Count 优化 Update 优化 多表连接查询优化 SQL性能分析 通过SQL性能分析来做SQL的优化,主要是优化SQL的查询语…...
SpringBoot实现数据库读写分离
SpringBoot实现数据库读写分离 参考博客https://blog.csdn.net/qq_31708899/article/details/121577253 实现原理:翻看AbstractRoutingDataSource源码我们可以看到其中的targetDataSource可以维护一组目标数据源(采用map数据结构),并且做了路由key与目标…...
Linux(四)--包软件管理器与Linux上环境部署示例
一.包软件管理器【yum和apt】 1.先来学习使用yum命令。yum:RPM包软件管理器,用于自动化安装配置Linux软件,并可以自动解决依赖问题。通过yum命令我们可以轻松实现软件的下载,查找,卸载与更新等管理软件的操作。 最常用…...
自监督去噪:Recorrupted-to-Recorrupted原理分析与总结
文章目录 1. 方法原理1.1 相关研究1.2 研究思路1.3 小结 2. 实验结果3. 总结 文章地址: https://ieeexplore.ieee.org/document/9577798/footnotes#footnotes 参考博客: https://github.com/PangTongyao/Recorrupted-to-Recorrupted-Unsupervised-Deep-Learning-for-Image-Den…...
【css】css实现水平和垂直居中
通过 justify-content 和 align-items设置水平和垂直居中, justify-content 设置水平方向,align-items设置垂直方向。 代码: <style> .center {display: flex;justify-content: center;align-items: center;height: 200px;border: 3px…...
常见Charles在Windows10抓包乱码问题
废话不多说 直接开整 最近反复安装证书还是乱码 网上各种百度还是不行 首先计算机查看安装好的证书 certmgr.msc 找到并删除掉 重新安装证书 具体解决方法: 第一步:点击 【工具栏–>Proxy–>SSL Proxying Settings…】 第二步:配置…...
汽车维修保养记录查询API:实现车辆健康状况一手掌握
在当今的数字化世界中,汽车维修保养记录的查询和管理变得前所未有地简单和便捷。通过API,我们可以轻松地获取车辆的维修和保养记录,从而实现对手中车辆健康状况的实时掌握。 API(应用程序接口)是进行数据交换和通信的标…...
正则表达式学习记录(Python)
正则表达式学习记录(Python) 一、特殊符号和字符 多个正则表达式匹配 ( | ) 用来分隔不同的匹配模式,相当于逻辑或,可以符合其中任何一个正则表达式 at | home # 表示匹配at或者home bat | bet | bit # 表示匹配bat或…...
Ubuntu20.04操作系统安装Docker
1、添加Docker仓库 curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -sudo add-apt-repository \"deb [archamd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \$(lsb_release -cs) \stable"2、安装Docker sudo…...
python制作小程序制作流程,用python编写一个小程序
这篇文章主要介绍了python制作小程序代码宠物运输,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。 1 importtkinter2 importtkinter.messagebox3 importmath4 classJSQ:5 6 7 d…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...
