Spring Transactional注解失效的几种典型场景及解决办法

1.注解的方法不是用public修饰

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

我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须得是public的。

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

2.方法用final修饰

同理,如果方法用final修饰,也是没法被动态代理的类重写的,因此注解也会失效。

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

3.同一个类中的方法直接在方法内部调用

public void test(){QueryWrapper<StoreRebateCalculateLog> queryWrapper;queryWrapper = new QueryWrapper<>();queryWrapper.eq("delete_flag", 0);List<StoreRebateCalculateLog> storeRebateCalculateLogs1 = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));updateTest(storeRebateCalculateLogs1);}@Transactional(rollbackFor = Exception.class)public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {log.setNeedRepeatCalFlag("0");this.storeRebateCalculateLogDao.updateById(log);if(log.getCalculateLogId() == 90012){throw new IllegalArgumentException("lll");}}}

如上,如果是在同一个类中调用被@Transactional注解的方法,那么也不会生效,因为调用的是this对象的方法,而不是代理对象的方法。

可以打断点调试看到这个现象。原因是在InvocationHandlerImpl#invoke中method.invoke(subject, args);这里调用的是目标类subject的方法,直接执行目标类方法,不会执行代理类的方法

image-20240315230137495

那么如果有些时候一定想在同一个类中调用,有没有办法呢?答案是可以的。

3.1新加一个Service方法

这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:

@Servcie
public class ServiceA {@Autowiredprvate ServiceB serviceB;public void save(User user) {queryData1();queryData2();serviceB.doSave(user);}}@Servciepublic class ServiceB {@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}

这种方法还是有点麻烦,需要多建一个类,那么可不可以不用新建类呢?

3.2直接在Service中注入自己

@Servcie
public class ServiceA {@Autowiredprvate ServiceA serviceA;//注入自己public void save(User user) {queryData1();queryData2();serviceA.doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}

这种办法虽然可以解决问题,但是看起来有点别扭。

3.3使用AopContext类直接获取当前类代理对象

@Overridepublic void test(){QueryWrapper<StoreRebateCalculateLog> queryWrapper;queryWrapper = new QueryWrapper<>();queryWrapper.eq("delete_flag", 0);List<StoreRebateCalculateLog> storeRebateCalculateLogs1 = storeRebateCalculateLogDao.selectBatchIds(Arrays.asList(90010, 90011, 90012, 90013));//使用当前类的代理对象调用((StoreRebateCalculateLogServiceImpl) AopContext.currentProxy()).updateTest(storeRebateCalculateLogs1);}@Transactional(rollbackFor = Exception.class)public void updateTest(List<StoreRebateCalculateLog> storeRebateCalculateLogs1) {for (StoreRebateCalculateLog log: storeRebateCalculateLogs1) {log.setNeedRepeatCalFlag("0");this.storeRebateCalculateLogDao.updateById(log);if(log.getCalculateLogId() == 90012){throw new IllegalArgumentException("lll");}}}

可以打断点调试发现,此时执行的是当前类的代理对象:

image-20240315230517447

@Servcie
public class ServiceA {public void save(User user) {queryData1();queryData2();((ServiceA)AopContext.currentProxy()).doSave(user);}@Transactional(rollbackFor=Exception.class)public void doSave(User user) {addData1();updateData2();}}

4.类没有被spring管理

这种正常情况一般不会出现,但是在忘记了的情况下可能出现。如:类上面的@Service注解没有

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

5.调用的方法与被调用的注解的方法不是同一个线程

@Slf4j
@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表数据");}
}

事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。

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

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

private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");

6.错误的传播特性

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

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

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

关于事务的传播特性,可以参考:

事务传播行为总结

7.异常被自己捕获了没有往外抛

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

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

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

8.异常不是运行时异常

@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);}}
}

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

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

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

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

相关文章

(二)丶RabbitMQ的六大核心

一丶什么是MQ Message Queue(消息队列&#xff09;简称MQ&#xff0c;是一种应用程序对应用程序的消息通信机制。在MQ中&#xff0c;消息以队列形式存储&#xff0c;以便于异步传输&#xff0c;在MQ中&#xff0c;发布者&#xff08;生产者&#xff09;将消息放入队列&#xff…

实验01 ASP.NET网站的建立及运行

【实验目的】 &#xff08;1&#xff09;能熟悉ASP.NET的开发环境Visual Studio Community 2019&#xff08;VSC 2019&#xff09;。 &#xff08;2&#xff09;能通过解决方案管理网站&#xff0c;会在解决方案中创建网站。 &#xff08;3&#xff09;会设置IIS 10中的网站…

Java学习笔记------常用API(四)

BigDecima 用于小数的精准计算 用来表示很大的小数 构造方法获取BigDecimal对象 public BigDecimal(double val)//有可能不精确&#xff0c;不建议使用 public BigDecimal(String val) 静态方法获取BigDecimal对象 public static BigDecimal valueOf(double val)//超出do…

SpringBoot3整合Elasticsearch8.x之全面保姆级教程

整合ES 环境准备 安装配置ES&#xff1a;https://blog.csdn.net/qq_50864152/article/details/136724528安装配置Kibana&#xff1a;https://blog.csdn.net/qq_50864152/article/details/136727707新建项目&#xff1a;新建名为web的SpringBoot3项目 elasticsearch-java 公…

Laravel Class ‘Facade\Ignition\IgnitionServiceProvider‘ not found 解决

Laravel Class Facade\Ignition\IgnitionServiceProvider not found 问题解决 问题 在使用laravel 更新本地依赖环境时&#xff0c;出现报错&#xff0c;如下&#xff1a; 解决 这时候需要更新本地的composer&#xff0c;然后在更新本地依赖环境。 命令如下&#xff1a; co…

邮件自动化:简化Workplace中的操作

电子邮件在职场中的使用对于企业和组织的日常活动起着重要的作用。电子邮件不再仅仅是一种通信方式&#xff0c;已经成为现代企业和组织实施日常运营的关键要素。 除了通信&#xff0c;电子邮件对于需求生成、流程工作流、交易审批以及各种其他与业务相关的活动至关重要。在当…

(三)丶RabbitMQ的四种类型交换机

前言&#xff1a;四大交换机工作原理及实战应用 1.交换机的概念 交换机可以理解成具有路由表的路由程序&#xff0c;仅此而已。每个消息都有一个称为路由键&#xff08;routing key&#xff09;的属性&#xff0c;就是一个简单的字符串。最新版本的RabbitMQ有四种交换机类型&a…

Node.js基础+原型链污染

Node.js基础 概述&#xff1a;简单来说Node.js就是运行在服务端的JavaScript&#xff0c;Node.js是一个基于Chrome JavaScript运行时建立的一个平台 大小写变换&#xff1a; toUpperCase&#xff08;&#xff09;&#xff1a;将小写字母转为大写字母&#xff0c;如果是其他字…

C语言- strcat(拼接函数的使用和模拟)

strcat&#xff08;拼接函数的使用和模拟&#xff09; strcat的语法 strcat 是 C 语言标准库中的一个字符串拼接函数&#xff0c;它用于将一个字符串&#xff08;source&#xff09;拼接到另一个字符串&#xff08;destination&#xff09;的末尾。该函数定义在 <string.h…

开箱即用之 windows部署jdk、设置nginx、jar自启

jdk安装 官网下载对应的安装包&#xff0c;解压之后放在本地指定的文件夹下 传送门https://www.oracle.com/java/technologies/downloads/#jdk21-windows 我比较喜欢下载zip方式的&#xff0c;解压之后直接能用&#xff0c;不需要安装了 配置环境 JAVA_HOME 添加path路径 …

Ubuntu 安装 KVM 虚拟化

1. Ubuntu 安装 KVM 虚拟化 KVM 是 Linux 内核中一个基于 hypervisor 的虚拟化模块&#xff0c;它允许用户在 Linux 操作系统上创建和管理虚拟机。 如果机器的CPU不支持硬件虚拟化扩展&#xff0c;是无法使用KVM(基于内核的虚拟机)直接创建和运行虚拟机的。此时最多只能使用…

3.Gen<I>Cam文件配置

Gen<I>Cam踩坑指南 我使用的是大恒usb相机&#xff0c;第一步到其官网下载大恒软件安装包,安装完成后图标如图所示&#xff0c;之后连接相机&#xff0c;打开软件&#xff0c;相机显示一切正常。之后查看软件的安装目录如图&#xff0c;发现有GenICam和GenTL两个文件&am…