@Transactional使用中的三类坑

我们知道事务有声明式事务和编程式事务两种,编程式事务代码侵入较高,声明式事务侵入较低,在项目中常有使用,然而,不正确的使用声明式事务,可能让代码未能按照我们的预期执行。

一、事务可能没有生效

  1. @Transactional只有定义在public方法上才生效,因为spring使用动态代理的方式实现aop,来实现对目标方法增强,而private方法是无法代理的,故不能生效(CGLIB通过继承方式实现代理类,private在子类不可见,无法进行事务增强)
  2. 必须通过代理过的类从外部调用方法才生效
    下面这种方式是不会生效的,外部调用的createUserWrong2()方法,再由内部调用createUserPublic()方法
public int createUserWrong2(String name) {try {this.createUserPublic(new UserEntity(name));} catch (Exception ex) {log.error("create user failed because {}", ex.getMessage());}return userRepository.findByName(name).size();
}//标记了@Transactional的public方法
@Transactional
public void createUserPublic(UserEntity entity) {userRepository.save(entity);if (entity.getName().contains("test"))throw new RuntimeException("invalid username!");
}

this指针代表对象自己,spring不可能注入this
可以通过自己注入自己的方式

@Autowired
private UserService self;public int createUserWrong2(String name) {try {self.createUserPublic(new UserEntity(name));} catch (Exception ex) {log.error("create user failed because {}", ex.getMessage());}return userRepository.findByName(name).size();
}//标记了@Transactional的public方法
@Transactional
public void createUserPublic(UserEntity entity) {userRepository.save(entity);if (entity.getName().contains("test"))throw new RuntimeException("invalid username!");
}

打断点可以看到,self是spring通过CGLIB方式增强过的类,二者调用的逻辑如下图

由上面两个坑可以得出,我们务必确认调用@Transactional注解标记的方法是public的,并且是通过Spring注入的 Bean进行调用的
在这里插入图片描述

二、生效了也不一定回滚

要想在出现异常后回滚,需要满足以下两个条件:

  1. 异常传播出了标记了@Transactional方法
  2. 出现RuntimeException或Error

对于第一点的理解,看下面这段代码

    @Transactionalpublic void createUserWrong1(String name, Integer age) {try {UserInfo userInfo = new UserInfo(name, age);userInfoRepository.save(userInfo);throw new RuntimeException("error");} catch (Exception e) {log.error("create user failed", e);}}

由于在方法内捕获了所有异常,导致异常未能传播出去,事务无法回滚

对于第二点的理解,对于下面的受检异常,是无法回滚的

    @Transactionalpublic void createUserWrong2(String name, Integer age) throws IOException {userInfoRepository.save(new UserInfo(name, 20));otherTask();}//因为文件不存在,一定会抛出一个IOExceptionprivate void otherTask() throws IOException {Files.readAllLines(Paths.get("file-that-not-exist"));}

那么,这两个问题应当如何解决呢?
首先,第一个问题,如果实在是想自己捕获异常处理,可以手动设置回滚

    @Transactionalpublic void createUserRight1(String name, Integer age) {try {userInfoRepository.save(new UserInfo(name, age));throw new RuntimeException("error");} catch (Exception e) {log.error("create user failed", e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}

对于第二个问题,想要对所有异常都进行回滚,要在注解中进行声明

    @Transactional(rollbackFor = Exception.class)public void createUserRight2(String name, Integer age) throws IOException {userInfoRepository.save(new UserInfo(name, 20));otherTask();}

三、确认事务传播机制符合业务逻辑

有这么一个场景:一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的子用户。我们希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影响主流程,即不影响主用户的注册

通常情况下,我们很容易想到下面这样的代码实现

    // userService@Transactionalpublic void createUserWrong4(String name, Integer age) {UserInfo userInfo = new UserInfo(name, age);// 主流程userInfoRepository.save(userInfo);// 子流程subUserService.createSubUserWithExceptionWrong(name, age);}// subUserService@Transactionalpublic void createSubUserWithExceptionWrong(String name, Integer age) {UserInfo userInfo = new UserInfo(name + "_sub", age);userInfoRepository.save(userInfo);throw new RuntimeException("invalid status");}

子用户抛出一个异常,很明显子任务会失败,如果不加以特殊处理,异常肯定会进一步逃离主任务的createUserWrong4方法,导致主任务也回滚
所以首先想到的是将子任务的异常捕获,这样异常就不会逃离主任务的方法了

    // userService@Transactionalpublic void createUserWrong5(String name, Integer age) {UserInfo userInfo = new UserInfo(name, age);// 主流程userInfoRepository.save(userInfo);// 子流程try {subUserService.createSubUserWithExceptionWrong(name, age);} catch (Exception e) {log.error("create sub user error:{}", e.getMessage());}}

然而,实际情况却是主方法并没有抛出异常,却直接静默回滚了,他在提交的时候,发现子方法把当前事务设置了回滚,因此无法完成提交

org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

这是因为我们主任务和子任务都是同一个事务,子任务标记了事务回滚,主任务自然也不能提交了,处理办法就是将子任务在独立的事务中运行

    @Transactional(propagation = Propagation.REQUIRES_NEW)public void createSubUserWithExceptionRight(String name, Integer age) {UserInfo userInfo = new UserInfo(name + "_sub", age);userInfoRepository.save(userInfo);throw new RuntimeException("invalid status");}

propagation指定事务传播策略,REQUIRES_NEW表示当前方法开启一个新事务运行,这样子任务和主任务就互不干扰了。
从上面可以看出,如果方法涉及多次数据库操作,我们务必仔细思考事务的传播方式,防止出现异常的结果

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

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

相关文章

概念:Android 虚拟机

Android中的Dalvik虚拟机(DVM)和Android运行时(ART)都是运行在内存中的。它们是Android操作系统的一部分,为Android应用提供了一个管理执行代码的环境。具体来说: Dalvik虚拟机 (DVM):Dalvik是…

2024-4.python4基本数据类型

基本数据类型 引言 提问 前面我们说过,计算机的本质作用就是用来存储和运算二进制的数据。但是在实际应用中,我们看到计算机存储或者运算的数据并非只有二进制的数据,例如使用Excel可以对一些数值数据、文本数据或者图像数据进行不同形式的处…

从iPhone恢复已删除照片的最佳软件

本文分享了从iPhone恢复已删除照片的最佳软件。如果您正在寻找如何从iPhone恢复已删除的照片,请查看这篇文章。 为什么您需要软件从iPhone恢复已删除的照片? 没有什么比丢失iPhone上的重要数据更痛苦的了,尤其是一些具有珍贵回忆的照片。有时…

从零开始学习深度强化学习及其在自动驾驶下的实战项目

深度强化学习及其在自动驾驶下的实战项目 本课程是为了帮助大家快速入门强化学习和学会应用深度强化学习进行算法的开发。 鉴于网上有许多开源的强化学习理论基础的视频,而且许多项目都是基于gym游戏进行开发的简单项目,并未涉及使用前沿的carla环境等自…

公司文件加密软件有监视功能吗?

公司文件加密软件不仅提供了强大的文件加密能力,还具备了监视功能,确保文件在使用过程中的安全性。华企盾DSC数据防泄密系统中的监控功能体现在以下几个方面: 加密文件操作日志:记录所有加密文件的申请、审批、扫描加解密、自动备…

Jvm垃圾回收器cms和g1区别

Jvm垃圾回收器cms和g1区别 G1垃圾回收器(Garbage First)和CMS (Concurrent Mark-Sweep)垃圾回收器是Java虚拟机(JVM))中的两种不同的垃圾回收策略,它们各有优缺点。以下是两者的比较…

【Camera2 教程二】Camera2相机打开和关闭接口调用详细说明

上一章《Camera2教程一》里我们介绍了一些 Camera2 的基础知识,但是并没有涉及太多的 API,从本章开始我们会开发一个具有完整相机功能的应用程序,并且将相机知识分成多个篇章进行介绍,而本章所要介绍的就是相机的开启流程。 阅读本章之后,你将学会以下几个知识点: 如何…

C#值传递和引用传递,ref和out关键字,装箱和拆箱

C#值传递和引用传递 1.值传递和引用传递 值传递:值传递时,系统首先为被调用方法的形参分配内存空间,并将实参的值按位置一一对应复制给形参,被调用方法中形参得任何改变都不会影响到相应的实参。 引用传递时:系统不是…

【学习笔记十】EWM自动产品包装配置

一、确定包装物料建议的程序 1.定义内向交货处理的凭证类型 2.确定包装物料建议的程序确定原理 使用可以确定包装材料建议的过程来指定业务代码。系统使用这些业务代码查找包装规格。包装期间,系统可建议包装材料。如果系统确定包装规格并建议包装材料,…

Suno AI

Suno is the latest big name in AI, but what is it? Keep reading to learn everything you need to know about Suno AI, including what it is, what it can do, and how much it costs. Suno AI是一款由Anthropic公司开发的人工智能音乐生成器,它利用先进…

Gradle 构建自动化工具入门

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 目录 1. 前言 2. 简介 3. 常见的项目构建工具 4. 安装 4.1. 安装说明 4.2. 下载…

[管理者与领导者-167] :团队管理 - 高效执行力 -6-授权-领导者做个统筹调度的领导者,而不是做冲锋陷阵的士兵

目录 关键词: 前言: 一、管理者做自己该做的事,下属做下属该做的事 二、科学分配任务和职责 三、只有信任员工,才能有效授权 四、授权管头(定目标)管尾(把结果),但…

『大模型笔记』视觉语言模型解释

视觉语言模型解释 文章目录 一. 视觉语言模型解析1.什么是视觉语言模型?2. 开源视觉语言模型概览3. 如何找到合适的视觉语言模型MMMUMMBench 4. 技术细节5.使用变压器 (transformers) 运用视觉语言模型6. 使用 TRL 微调视觉语言模型 二. 参考文章 一. 视觉语言模型…

在Python中如何使用正则表达式? —— 使用Python正则表达式高效处理文本。

在Python中使用正则表达式,你需要导入re模块。re模块提供了一组函数,用于在字符串中搜索匹配特定模式的文本。 下面是一些常用的正则表达式函数: re.search(pattern, string):在字符串中搜索匹配正则表达式pattern的文本&#x…

10.基础乐理-高音点、低音点

首先唱名(do、re、mi、fa、sol、la、si 1234567)先对应在 小字一组上,一般调号 1c 时都是对应在 小字一组上 然后从 小字一组 开始往左或往右,往左的音是越低的,往右的音是越高的,这时也需要给唱名&#xf…

47---PCIE硬件电路设计

视频链接 PCIe硬件电路设计01_哔哩哔哩_bilibili PCIe硬件电路设计 1、PCIE介绍 1.1、PCIe简介 PCI-Express (peripheral component interconnect express)是一种高速串行计算机扩展总线标准,用于在CPU与外围组件之间实现高速串行通信。如今已成为主板扩展总线…

jenkins+git+maven+nodejs安装(linux系统)

前文已经安装完成sonarqube和Sonar Scanner了,接下来可以开始jenkins了 jenkins安装 命令(版本为 2.440) wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo wget https://pkg.jenkins.io/redh…

全方位深入解析CSS background属性

CSS的background属性是我们进行网页布局和设计时不可或缺的一部分,它为元素提供了丰富的背景绘制能力,包括颜色、图像、渐变、重复模式、定位、剪裁等。本文将深入解析background属性的各个组成部分,通过详细的理论讲解与实战代码示例&#x…

【刷题笔记】第四天

文章目录 [2924. 找到冠军 II](https://leetcode.cn/problems/find-champion-ii/description/?envTypedaily-question&envId2024-04-13!!!)题目描述思路分析 [1702. 修改后的最大二进制字符串](https://leetcode.cn/problems/maximum-binary-string-after-change/)题目描述…

学习java第四十四天

Resource 是如何被查找、加载的 Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。 Spring 为 Resource 接口提供了如下实现类: UrlRes…