当前位置: 首页 > news >正文

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

相关文章:

spring 事务失效的几种场景

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

45岁程序员独白:中年打工人出路在哪里?

作为一名也是JAVA方向的互联网从业者,我发现周围超过40岁以上的同事,基本都是部门负责人或者高层,真正还在一线做开发或者当个小领导的,已经是凤毛麟角了。 同事A今年刚满40,育有一儿一女,从进入公司到现在…...

深度探讨:为何训练精度不高却在测试中表现优异?

深度探讨:为何训练精度不高却在测试中表现优异? 在深度学习领域,我们经常遇到这样一个看似矛盾的现象:模型在训练集上的精度不是特别高,但在测试集上却能达到出色的表现。这种情况虽然不是常规,但其背后的…...

动态内存管理<C语言>

导言 在C语言学习阶段,指针、结构体和动态内存管理,是后期学习数据结构的最重要的三大知识模块,也是C语言比较难的知识模块,但是“天下无难事”,只要认真踏实的学习,也能解决,所以下文将介绍动态…...

第一百零二节 Java面向对象设计 - Java静态内部类

Java面向对象设计 - Java静态内部类 静态成员类不是内部类 在另一个类的主体中定义的成员类可以声明为静态。 例子 以下代码声明了顶级类A和静态成员类B: class A {// Static member classpublic static class B {// Body for class B goes here} }注意 静态成…...

给自己Linux搞个『回收站』,防止文件误删除

linux没有像windows里一样的回收站,工作时候删除文件容易不小心删错,造成麻烦的后果。所以给自己整了个回收站: 文件删除,新建~/opts/move_to_trash.sh,然后在里面新增,将${your_name}改成你的用户名。同时…...

Springboot接收参数的21种方式

前言 最近一直在忙着开发项目(ps:其实有些摆烂),好久没有更新博客了,打开csdn一看好多网友留言私信,继上篇博客(我是如何实现HttpGet请求传body参数的!),网友议论纷纷,各抒起见。今天正好抽出时间总结一下Springboot接受参数的21种方式(Post、Get、Delete),一并…...

打造出色开发者体验的十大原则

大约十年前我是一名CIO,当时我在评估一种技术解决方案,向潜在供应商的代表讲明了我们的主要需求。他展示了该公司的至少三款产品。每种工具都有各自的用户体验、开发方法和学习要求,但是解决我们的业务需求同时需要这三种工具。作为CIO&#…...

Vue3_对接腾讯云COS_大文件分片上传和下载

目录 一、腾讯云后台配置 二、安装SDK 1.script 引入方式 2.webpack 引入方式 三、文件上传 1.new COS 实例 2.上传文件 四、文件下载 腾讯云官方文档: 腾讯云官方文档https://cloud.tencent.com/document/product/436/11459 一、腾讯云后台配置 1.登录 对…...

python免杀--base64加密(GG)

单层加密都GG~ 目录 cs生成个python的payload 将shellcode进行base64编码 执行上线代码 cs生成个python的payload msfvenom -p windows/meterpreter/reverse_tcp --encrypt base64 lhostIP lport6688 -f c cs生成c的也行. 将shellcode进行base64编码 import base64code …...

Python版与Java版城市天气信息爬取对比分析

在对比Python版和Java版城市天气信息爬取时,我们需要考虑多个方面,包括语言特性、库支持、代码简洁性、执行效率以及维护成本等。以下是对这两个版本进行的一些对比分析: 1. 语言特性 Python: 易于学习:Python的语法清…...

CSS真题合集(二)

CSS真题合集(二) 11. css3新增特性12. css3动画12.1 关键帧动画 (keyframes)12.2 animation12.3 transition12.4 transform 13. grid网格布局13.1 使用display: grid或display: inline-grid的HTML元素。13.2 定义网格13.3 13.4 自动填充和自动放置13.4 对…...

长期出汗困扰你?可能是肾合出了问题

想象一下,我们的身体是一座繁茂的秘密花园,每一寸肌肤、每一个细胞都是花园里的一朵花、一片叶。汗水,则是这花园中无声的语言,它讲述着我们的健康与否,也揭示着身体内部的微妙变化。 在炎炎夏日,身体如盛开…...

Jmeter函数二次开发说明

jmeter 二次开发使用 jmeter二次开发实现方法 使用maven依賴进行开发 导入jmeter的maven依赖&#xff0c;需要和你使用的jmeter版本一致。 <!-- https://mvnrepository.com/artifact/org.apache.jmeter/ApacheJMeter_core --> <dependency><groupId>org.ap…...

重新学习STM32(1)GPIO

概念简介 GPIO 是通用输入输出端口的简称&#xff0c;简单来说就是 STM32 可控制的引脚。STM32 芯片通过 GPIO 引脚与外部设备连接起来&#xff0c;从而实现与外部通讯、控制以及数据采集的功能。 GPIO被分成很多组&#xff0c;比如 GPIOA和GPIOB等。所有的 GPIO引脚都有基本的…...

React+TS前台项目实战(二)-- 路由配置 + 组件懒加载 + Error Boundary使用

文章目录 前言一、路由配置和懒加载lazy的使用二、TS版本Error Boundary组件封装三、在layout组件中使用Suspense组件和错误边界组件总结 前言 本文将详细介绍项目中的页面路由配置和异步组件懒加载处理&#xff0c;以提高用户体验&#xff0c;实现过渡效果。 一、路由配置和懒…...

成为电商低价神秘顾客访问员的必备条件(深圳神秘顾客公司)

电商低价神秘顾客需要具备以下条件&#xff0c;以确保能够执行有效的调查任务并为企业提供有价值的反馈&#xff1a; 1、细致的观察能力&#xff1a;神秘顾客访问员需要具备细致的观察能力&#xff0c;能够全面、细致地观察电商平台的购物流程、商品详情、服务细节等。这包括注…...

现货黄金交易多少克一手?国内外情况大不同

如果大家想参与国际市场上的现货黄金交易&#xff0c;就应该从它交易细则的入手&#xff0c;先彻底认识这个品种&#xff0c;因为它是来自欧美市场的投资方式&#xff0c;所以无论是从合约的计的单位&#xff0c;计价的货币&#xff0c;交易的具体时间&#xff0c;以及买卖过程…...

LNMP与动静态网站介绍

Nginx发展 Nginx nginx http server Nginx是俄罗斯人 Igor Sysoev(伊戈尔.塞索耶夫)开发的一款高性能的HTTP和反向代理服务器。 Nginx以高效的epoll.kqueue,eventport作为网络IO模型&#xff0c;在高并发场景下&#xff0c;Nginx能够轻松支持5w并发连接数的响应&#xff0c;并…...

教育小程序开发:技术实现与实践案例

随着信息技术的不断进步&#xff0c;教育小程序在教育领域的应用越来越广泛。教育小程序开发不仅可以提高教学效率&#xff0c;还能够提供个性化的学习体验。本文将以技术代码为例&#xff0c;详细介绍教育小程序开发的关键技术和实践案例&#xff0c;帮助开发者更好地理解和实…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...