【工作中问题解决实践 九】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…...

Github 创建自己的博客网站
参考pku大佬视频制作,附上B站视频:【GitHub Pages 个人网站构建与发布】 同时还参考了:【Python版宝藏级静态站点生成器Material for MkDocs】 GitHub Pages 介绍 内容参考:GitHub Pages - 杨希杰的个人网站 (yang-xijie.githu…...

Windows上安装 jdk 环境并配置环境变量 (超详细教程)
👨🎓博主简介 🏅云计算领域优质创作者 🏅华为云开发者社区专家博主 🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 🐋 希望大家多多支…...

高效构建 vivo 企业级网络流量分析系统
作者:vivo 互联网服务器团队- Ming Yujia 随着网络规模的快速发展,网络状况的良好与否已经直接关系到了企业的日常收益,故障中的每一秒都会导致大量的用户流失与经济亏损。因此,如何快速发现网络问题与定位异常流量已经成为大型企…...

认识awk
awk 认识awk awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行…...

【C#学习笔记】数组和索引器
文章目录 数组单维数组多维数组交错数组 索引器类上的索引器方法1方法2 接口中的索引器 数组 数组具有以下属性: 数组可以是一维、多维或交错的。创建数组实例时,将建立纬度数量和每个纬度的长度。 这些值在实例的生存期内无法更改。数值数组元素的默认…...

常见距离计算的Python实现
常见的距离有曼哈顿距离、欧式距离、切比雪夫距离、闵可夫斯基距离、汉明距离、余弦距离等,用Python实现计算的方式有多种,可以直接构造公式计算,也可以利用内置线性代数函数计算,还可以利用scipy库计算。 1.曼哈顿距离 也叫城市…...

开发运营监控
DevOps 监控使管理员能够实时了解生产环境中的元素,并有助于确保应用程序平稳运行,同时提供最高的业务价值,对于采用 DevOps 文化和方法的公司来说,这一点至关重要。 什么是开发运营监控 DevOps 通过持续开发、集成、测试、监控…...

食品小程序的制作教程
在今天的互联网时代,小程序已经成为了各行业推广和销售的重要途径。特别是对于食品行业来说,拥有一个专属的小程序商城可以带来更多的用户和销售机会。那么,如何制作一个完美的食品小程序呢?下面就跟随我来一步步教你,…...

Kubernetes(K8s)从入门到精通系列之十三:软件负载平衡选项
Kubernetes K8s从入门到精通系列之十三:软件负载平衡选项 一、软件负载平衡选项二、keepalived and haproxy三、keepalived配置四、haproxy配置五、选项 1:在操作系统上运行服务六、选项 2:将服务作为静态 Pod 运行 一、软件负载平衡选项 当…...

数据特征选择 | Matlab实现具有深度度量学习的时频特征嵌入
文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 数据特征选择 | Matlab实现具有深度度量学习的时频特征嵌入。 深度度量学习尝试学习非线性特征嵌入或编码器,它可以减少来自同一类的示例之间的距离(度量)并增加来自不同类的示例之间的距离。 以这种方式工作的…...