spring 事务失效的几种场景

一、背景

        在 springBoot 开发过程中,我们一般都是在业务方法上添加 @Transactional 注解来让 spring 替我们管理事务,但在某些特定的场景下,添加完注解之后,事务是不生效的,接下来详细介绍下。

二、方法不是 public

2.1 场景描述

        当添加 @Transactional 注解的方法不是 public 类型的,事务会失效。如下代码:

@Transactional
private void someTransactionalMethod() {// 业务逻辑
}

2.2 原因分析

        在 Spring 中,只有 public 方法才能被 AOP 代理处理,因此如果 @Transactional 注解的方法不是 public 的,事务管理将失效。

2.3 解决方案

        确保 @Transactional 注解的方法是 public。如下:

@Transactional
public void someTransactionalMethod() {// 业务逻辑
}

三、方法内部调用

3.1 场景描述

        当一个类内部的方法调用另一个标注了 @Transactional 的方法时,事务管理将失效。如下代码:

@Service
public class MyServiceImpl {public void outerMethod() {publicMethod();}	@Transactionalpublic void publicMethod() {// 业务逻辑}
}

3.2 原因分析

        这是因为内部方法方法的调用没有经过代理类,即在 outerMethod() 方法里面调用的 publicMethod() 方法是 MyServiceImpl 对象调用的,并不是经过 spring 代理类来调用的,所以事务会失效。

3.3 解决方案

        解决方案就是通过代理对象方法调用,使用 AOP 代理进行事务管理,如下代码:

@Service
public class MyServiceImpl {public void outerMethod() {// 通过代理对象调用 publicMethod((MyServiceImpl) AopContext.currentProxy()).publicMethod();}	@Transactionalpublic void publicMethod() {// 业务逻辑}
}

四、未被 spring 管理

4.1 场景描述

        当一个类没有被 spring 管理时,事务不会生效,如下代码:

​public class MyServiceImpl {@Transactionalpublic void someTransactionalMethod() {// 业务逻辑}
}

4.2 原因分析

        只有在 Spring 容器中管理的 bean,才能被 AOP 代理。如果 @Transactional 注解的方法所在的类没有被 Spring 管理,事务管理将失效。

4.3 解决方案

        确保类被 Spring 容器管理,如通过 @Service@Component 等注解。

@Service​
public class MyServiceImpl {@Transactionalpublic void someTransactionalMethod() {// 业务逻辑}
}

五、方法用 final 或 static 修饰

5.1 场景描述

        有时候,某个方法不想被子类重写,这时可以将该方法定义成 final 的。普通方法这样定义是没问题的,但如果将事务方法定义成 final,那么事务将会失效。

@Service
public class UserService {@Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}

5.2 原因分析

        spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者 cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

        注意:如果某个方法是 static 的,同样无法通过动态代理,变成事务方法。

5.3 解决方案

        不使用 final 或者 static 修饰方法,如下:

@Service
public class UserService {@Transactionalpublic void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}

六、配置不当

6.1 场景描述

        @Transactional 注解的一些配置属性,可能会影响事务的行为,如下代码:

@Transactional(readOnly = true) 
public void someTransactionalMethod() {// 业务逻辑
}

6.2 原因分析

        配置了 readOnly=true 属性,那么执行增删改操作时就会报错。因为这个属性指定了此方法只能进行读操作。

6.3 解决方案

        检查配置的具体含义,确保其适当应用。

@Transactional(readOnly = false) 
public void someTransactionalMethod() {// 业务逻辑
}

七、多线程调用

7.1 场景描述

        spring 事务在多线程场景下,会有问题,如下代码

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() -> {roleService.doOtherThing();}).start();}
}@Service
public class RoleService {@Transactionalpublic void doOtherThing() {System.out.println("保存role表数据");}
}

7.2 原因分析

        从上面的例子中,我们可以看到事务方法 add 中,调用了事务方法 doOtherThing,但是事务方法 doOtherThing 是在另外一个线程中调用的。

        这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果将来 doOtherThing 方法中抛了异常,add 方法也回滚是不可能的。

        如果看过 spring 事务源码的朋友,可能会知道 spring 的事务是通过数据库连接来实现的。当前线程中保存了一个 mapkey 是数据源,value 是数据库连接。

        我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

7.3 解决方案

        避免在多线程中使用 @Transactional,或者手动管理线程间的事务。

@Service
public class MyService {@Transactionalpublic void someTransactionalMethod() {ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.submit(() -> {// 手动管理事务TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// 业务逻辑transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);throw e;}});}
}

八、错误的传播特性

8.1 场景描述

        如果我们在手动设置 propagation 参数的时候,把传播特性设置错了,事务可能就不会生效,如下代码:

@Service
public class UserService {@Transactional(propagation = Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}

8.2 原因分析

        propagation 参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:

        EQUIRED:如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。

       SUPPORTS:如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

        MANDATORY:当前上下文中必须存在事务,否则抛出异常。

        REQUIRES_NEW:每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

        NOT_SUPPORTED:如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

        NEVER:如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

        NESTED:如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

        我们可以看到 add 方法的事务传播特性定义成了 Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

8.3 解决方案

        目前只有这三种传播特性才会创建新事务:REQUIREDREQUIRES_NEWNESTED

@Service
public class UserService {@Transactional(propagation = Propagation.REQUIRED)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);}
}

九、自己吞了异常

8.1 场景描述

        开发者在代码中手动 try...catch 了异常,事务不会生效,如下代码:

@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}}
}

8.2 原因分析

        这种情况下 spring 事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。

8.3 解决方案

        如果想要 spring 事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则 spring 认为程序是正常的。如下代码:

@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel)throws Exception {saveData(userModel);updateData(userModel);}
}

十、手动抛了别的异常

8.1 场景描述

        即使开发者没有手动捕获异常,但如果抛的异常不正确,spring 事务也不会回滚。如下代码:

@Slf4j
@Service
public class UserService {@Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}}
}

8.2 原因分析

        上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。

        因为 spring 事务,默认情况下只会回滚 RuntimeException(运行时异常)和 Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。

8.3 解决方案

        别采取这种写法。

十一、自定义了回滚异常

11.1 场景描述

        在使用 @Transactional 注解声明事务时,有时我们想自定义回滚的异常,spring 也是支持的。可以通过设置 rollbackFor 参数,来完成这个功能。但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,如下代码:

@Slf4j
@Service
public class UserService {@Transactional(rollbackFor = BusinessException.class)public void add(UserModel userModel) throws Exception {saveData(userModel);updateData(userModel);}
}

11.2 原因分析

        如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了 SqlExceptionDuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。

        即使 rollbackFor 有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

11.3 解决方案

        如果使用默认值,一旦程序抛出了 Exception,事务不会回滚,这会出现很大的 bug。所以,建议一般情况下,将该参数设置成:Exception Throwable

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

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

相关文章

[leetcode hot 150]第一百三十六题,只出现一次的数字

题目: 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。 根据题目关于空间、…

心链9----组队功能开发以及请求参数包装类和包装类实现

心链 — 伙伴匹配系统 组队功能开发 需求分析 理想的应用场景 我要跟别人一起参加竞赛或者做项目,可以发起队伍或者加入别人的队伍 用户可以 创建 一个队伍,设置队伍的人数、队伍名称(标题)、描述、超时时间 P0 队长、剩余的人数…

漏电保护器的工作原理

漏电保护器上的每月按一次按钮,如果按下后开关立即跳闸,则证明漏保功能良好该按钮的工作原理是模拟线路漏电,在漏保内部存在一个零序互感器,零序互感器分别测量零线和火线的电流值,如果二者数值不相等,零火…

【WEB系列】过滤器Filter

Filter,过滤器,属于Servlet规范,并不是Spring独有的。其作用从命名上也可以看出一二,拦截一个请求,做一些业务逻辑操作,然后可以决定请求是否可以继续往下分发,落到其他的Filter或者对应的Servl…

海报在线制作系统

文章转载自:FastAdmin海报在线制作系统 - 源码1688 应用介绍 介绍 新机构海报是一款基于FastAdminThinkPHP开发的一款新机构海报。 采用JavaScript vue canvas技术,实现在线一键制作海报,生成海报。 功能特性 1、自由创作 2、一键制作…

Django使用正则表达式

本书1-7章样章及配套资源下载链接: https://pan.baidu.com/s/1OGmhHxEMf2ZdozkUnDkAkA?pwdnanc 源码、PPT课件、教学视频等,可以从前言给出的下载信息下载,大家可以评估一下。 在Django框架的新版本(v2.0 )中,URLc…

文件读取技巧

在CTF(Capture The Flag)比赛中,PHP文件读取是一种常见的挑战类型,通常涉及利用PHP中的文件包含功能或者一些不安全的文件操作来读取服务器上的敏感文件。这些敏感文件可能包含源代码、配置文件、密码甚至是FLAG(比赛的…

ECharts 图形化看板 模板(简单实用)

目录 一、官网 二、模板 ①定义请求​编辑 ② 将请求统一管理,别的页面引用多个请求时更便于导入。​编辑 ③最终模板 三、执行效果 四、后端代码 4.1 controller 4.2 xml 4.3 测试接口 一、官网 获取 ECharts - 入门篇 - 使用手册 - Apache ECharts 二、…

ARM32开发——串口库封装(初级)

🎬 秋野酱:《个人主页》 🔥 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 开发流程分组创建 接口定义完整代码 开发流程 在文件系统中,创建库目录Library在keil工程中,创建分组管理…

Vue3-组件通信详解

文章目录 组件通信的含义Vue3组件通信和Vue2的区别组件通信的具体实现props(父子组件通信)自定义事件(子传父)mitt(任意组件间通信)v-model$attrs (非props的父子组件通信) r e f s …

知识图谱应用---智慧金融

文章目录 智慧金融典型应用 智慧金融 智慧金融作为一个有机整体,知识图谱提供了金融领域知识提取、融合、分析、推断、决策等功能,如下图所示。在场景方面,智慧金融涵盖智慧支付、智慧财富管理、智慧银行、智慧证券、智慧保险、智慧风控等诸多…

智能分析设备助力废固运输车辆信息采集

进出车辆信息采集,这一环节可谓是整个废固生产及处理企业监管体系中的基石。前端摄像机以其敏锐的感知能力,精准捕捉废固运输车辆的车牌、车头、车尾以及车厢的细致画面,同时记录下对应的视频流信息。这些信息的采集不仅为后续的监管提供了详…

Git存储库的推送保护

Git存储库的推送保护 昨天有一个提交一直提示:Push rejected Push rejected Push master to origin/master was rejected by remote起初在网络上找各种解决办法,先列举以下找到的各类方法 提交用户的用户名和邮箱与Git不一致,这个只需要通…

Warning:成交前,永远相信意外即将发生

作为一名首次次创业者,随着创业进入深层次阶段,越来越感觉到:创业是一条不归路,因为路上不止有惊喜,还有风尘。创业之前我认为世界是“天圆地方”的, 创业后你猜我怎么看这个世界的? 创业前我一…

【C/C++】C语言实现std::move

C语言中的模拟 std::move 在C中&#xff0c;std::move 用于将一个对象转换为右值引用&#xff0c;以便可以使用移动语义。在C语言中&#xff0c;我们可以通过传递指针来模拟这种行为。 #include <stdio.h> #include <stdlib.h>typedef struct {int *data; } Arra…

项目-基于LangChain的ChatPDF系统

问答系统需求文档 一、项目概述 本项目旨在开发一个能够上传 PDF 文件&#xff0c;并基于 PDF 内容进行问答互动的系统。用户可以上传 PDF 文件&#xff0c;系统将解析 PDF 内容&#xff0c;并允许用户通过对话框进行问答互动&#xff0c;获取有关 PDF 文件内容的信息。 二、…

Unity3D DOTS JobSystem物理引擎的使用详解

Unity3D DOTS&#xff08;Data-Oriented Technology Stack&#xff09;是Unity引擎的一项新技术&#xff0c;旨在提高游戏性能和扩展性。其中的Job System是一种用于并行处理任务的系统&#xff0c;可以有效地利用多核处理器的性能。在本文中&#xff0c;我们将重点介绍如何使用…

excel 插入图片不变形的方法

在单元格内插入形状&#xff0c; 设置图片格式 设置图片的高宽

整理好了!2024年最常见 20 道 Kafka面试题(八)

上一篇地址&#xff1a;整理好了&#xff01;2024年最常见 20 道 Kafka面试题&#xff08;七&#xff09;-CSDN博客 十五、Kafka 与传统 MQ 消息系统之间有什么区别&#xff1f; Kafka与传统的消息队列&#xff08;MQ&#xff09;系统有多个显著的区别&#xff0c;这些区别主…

登录状态在页面跳转保存的三种方法

使用Cookie 工作原理&#xff1a;当用户登录网站后&#xff0c;服务器发送一个或多个 Cookie 到客户端&#xff08;浏览器&#xff09;&#xff0c;客户端会保存这些 Cookie&#xff0c;并在随后的每个请求中自动将它们发送回服务器&#xff0c;以便服务器识别用户。 优点&…