聊聊Spring注解@Transactional失效的那些事 | 京东云技术团队

news2024/11/25 4:53:04

一、前言

emm,又又又踩坑啦。这次的需求主要是对逾期计算的需求任务进行优化,现有的计算任务运行时间太长了。简单描述下此次的问题:在项目中进行多个数据库执行操作时,我们期望的是将其整个封装成一个事务,要么全部成功,或者全部失败,然而在自测异常场景时发现,里面涉及的第一个数据状态更新成功了,但是后面的数据在插入出现异常,后面查询数据表发现,该数据的状态已经被更新成功啦

emmm,查看代码发现确实是使用了@Transactional注解没问啊。于是通过查询网上相关资料发现,在使用Spring中事务注解@Transactional时会存在几种场景下该注解失效,即不能按照预期封装成一个事务操作,于是对该注解进行学习并对相关失效场景进行分析,整理文章如下;

二、@Transactional注解失效场景实例验证

1、@Transactional注解属性

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationEnum:Propagation·可选的事务传播行为设置
isolationEnum:Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致事务回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组

2、 propagation属性

propagation代表事务的传播行为,默认值为Propagation.REQUIRED

属性描述
Propagation.REQUIRED若当前存在事务则加入该事务,若不存在则创建一个新事务(默认)
Propagation.SUPPORTS若当前存在事务则加入该事务,若不存在则以非事务的方式继续进行
Propagation.MANDATORY若当前存在事务则加入该事务,若不存在则抛出异常
Propagation.REQUIRES_NEW重新创建一个新的事务,若当前存在事务则暂定当前事务
Propagation.NOT_SUPPORTED以非事务的方式运行,若当前存在事务则暂定当前事务
Propagation.NEVER以非事务的方式运行,若当前存在事务则抛出异常
Propagation.NESTED与Propagation.REQUIRED效果一样

3、 @Transactional注解使用场景?

@Transactional注解可以作用在接口、类、类方法中。

  • 当作用于类时,表示所有该类的public方法都配置相同的事务属性信息。

  • 当作用于方法时,当类配置了@Transactional注解,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

  • 当作用于接口时,不推荐使用,因为在接口使用@Transactional并且配置了Spring AOP使用CGLib动态代理将会导致其失效。

4、 @Transactional注解失效场景?

  • @Transactional注解作用在非public修饰的方法上,会失效。

失效原因:在Spring AOP代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy的内部类)的Intercept方法或JDKDynamicAopProxyinvoke方法会间接调用AbstractFallbackTransationAttributeSourcecomputeTransactionAttribute方法,获取@Transactional注解的事务配置信息。

1 protected TransactionAttribute computeTransactionAttribute(Method method,
2    Class<?> targetClass) {
3        // Don't allow no-public methods as required.
4        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
5        return null;
6    }

此方法会检查目标方法的修饰符是否为public,非public作用域则不会获取@transactional的属性配置信息。其中protected、private修饰的方法上使用 @Transactional注解,事务会失效但不会有任何报错。

  • @Transactional注解属性propagation设置错误导致注解失效

失效原因:配置错误, PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER三种事务传播方式不会发生回滚。

▪ 实例验证:写了一个demo进行测试。demo主要功能如下:执行两次数据库插入操作,并在扩展信息字段中添加备注;

▪ 运行结果如下,构造的单号不存在订单查询为空触发异常,观察数据库发现,第一次数据库插入操作已经执行成功,故而验证@Transactional注解失效

  • @Transactional注解属性rollbackFor设置错误导致注解失效

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了unchecked异常(继承自RuntimeException)或者Error才会回滚事务。若事务中抛出了其他类型的异常,但却期望Spring能够回滚事务,就需要指定rollbackFor属性,否则就会失效。

  • 同一类中方法调用,导致@Transactional失效

比如类demo中有方法A和B,方法B中使用@Transactional注解,方法A没有注解,但是demo类通过方法A调用方法B,像这种间接调用会导致方法B中的@Transactional事务注解失效。

失效原因:只有当事务方法被当前类以外的代码调用时,才会有Spring生成的代理对象管理。(Spring AOP代理机制造成的)。

实例验证:demo中构造场景为在同一个类中,在test方法中添加@Transactional注解,querRiskScore方法中不添加该注解,然后在querRiskScore方法中调用test方法;观察下多个插入操作是否会因为异常而中断回滚;

▪ 运行结果如下,还是通过构造的单号不存在订单查询为空触发异常,观察数据库发现,第一次数据库插入操作已经执行成功第二次数据插入操作失败,并没有因为异常而触发事务操作,故而验证@Transactional注解方法间的调用会失效

  • 多线程任务可能导致@Transaction案例失效

失效原因:线程不属于Spring托管,故线程不能够默认使用Spring的事务,也不能获取Spring注入的bean,在被Spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。

  • 异常被方法内catch捕获导致@Transactional失效

比如B方法内部抛了异常,而A方法此时try-catch了B方法的异常,则该事务不能正常回滚。

失效原因:因为B方法中抛出异常以后,标识当前事务需要rollback,但是A方法中由于你手动的捕获这个异常并进行处理,A方法认为当前事务应该正常commit,此时就出现前后不一致,会抛出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常。

实例验证:这个场景的本质还是异常被捕获导致无法正常的抛出,进而导致@Transactional注解无法正常工作,我简化了下demo实例场景,构造场景如下:在querRiskScore方法中添加@Transactional注解,然后在querRiskScore方法中对异常进行捕获;观察下多个插入操作是否会因为异常而中断回滚;

▪ 运行结果如下,还是通过构造的单号不存在订单查询为空触发异常,但是我们在方法内部对该异常进行捕获,并未向上层抛出,我们期望的场景是两次数据插入执行失败,但是观察数据库发现,第一次数据库插入操作已经执行成功第二次数据插入执行成功,与我们的预期结果不符,故而验证@Transactional注解在方法中异常被捕获的场景中失效

究其原因:Spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit 或 rollback,事务是否执行取决于是否抛出runtime异常,如果抛出runtime exception并在你的业务方法中并没有catch到的话,事务就会回滚。

三、“事务”知识回顾

1.什么是事务?

事务(Transaction)是由一系列对系统中数据进行访问与更新的操作组成的一个程序执行逻辑单元(Unit)。

通常我们所指的事务是指数据库事务,使用数据库事务有以下两处优点:

  • 当多个应用程序并发访问数据库时,事务可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰

  • 事务为数据库操作序列提供了一个从失败恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持数据一致性的方法。

2. 事务具有的特性?

原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性,简称事务的ACID特性。

  • 原子性

事务的原子性是指事务必须是一个原子的操作序列单元,即事务中包含的各项操作在一次执行过程中只会出现两种状态:全部成功执行、全部不执行。任何一项操作失败都将导致整个事务失败,同时其他已经被执行的操作都将被撤销并回滚,只打所有的操作全部成功,整个事务才算是成功完成。

  • 一致性

事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。也就是说,事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态,因此当数据库只包含成功事务提交 的结果时,就能说数据库处于一致性状态。而如果数据库系统在运行过程中发生故障, 有些事务尚未完成就被迫中断,这些未完成的事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。

  • 隔离性

事务的隔离性是指在并发环境中,并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰。也就是说,不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的 各个事务之间不能互相干扰。

  • 持久性

事务一旦提交,其所做的修改就会永久保存到数据库中,即使数据库发生故障也不应该对其有任何影响。需要注意的是,事务的持久性不能做到100%的持久,只能从事务本身的角度来保证永久性,而一些外部原因导致数据库发生故障,如硬盘损坏,那么所有提交的数据可能都会丢失。

3. 什么是Spring中的事务?

Spring中同样提供了很好的事务管理机制,主要分为编程式事务声明式事务

  • 编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。编程式事务方式需要开发者在代码中手动的管理事务的开启、提交、回滚等操作。

public void test() {

      TransactionDefinition def = new DefaultTransactionDefinition();

      TransactionStatus status = transactionManager.getTransaction(def);

      try {

         // 事务操作

         // 事务提交

         transactionManager.commit(status);

      } catch (DataAccessException e) {

         // 事务提交

         transactionManager.rollback(status);

         throw e;

      }

}
  • 声明式事务

声明式事务是基于AOP面向切面,它将具体业务和事务处理部分解耦,代码侵入性很低,实际开发中比较常用。我们常用TX和AOP的xml配置文件方式和@Transactional注解方式。

▪声明式事务的优点:

对代码无侵入性,方法内只需要写业务逻辑,节省很多代码量。

▪声明式事务的缺点:

1、声明式事务粒度问题:声明式事务的局限就是最小粒度要作用在方法上,且不适合耗时长高并发场景。

2、声明式事务容易被开发者忽略,当事务嵌套的方法中存在RPC远程调用、MQ发送、Redis更行、文件写入等操作可能存在以下场景:

▪ 事务嵌套的方法中RPC调用成功了,但是本地事务回滚导致RPC调用无法回滚(暂不讨论分布式事务)。

▪事务嵌套的方法中远程调用会拉长整个事务周期,导致事务的数据库连接一致被占用,类似操作过多会导致数据库连接池耗尽。

3、声明式事务使用错误会导致在某些场景下失效

四、总结

作者:京东科技 宋慧超

来源:京东云开发者社区

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

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

相关文章

Spring源码学习-后置处理器,Autowired实现原理

目录 Autowired实现原理populateBeanInstantiationAwareBeanPostProcessorAutowiredAnnotationBeanPostProcessor 后置处理器BeanFactory的后置处理器BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessoConfigurationClassPostProcessor Bean的后置处理器BeanProcess…

2020年国赛高教杯数学建模D题接触式轮廓仪的自动标注解题全过程文档及程序

2020年国赛高教杯数学建模 D题 接触式轮廓仪的自动标注 原题再现 轮廓仪是一种两坐标测量仪器&#xff08;见图1&#xff09;&#xff0c;它由工作平台、夹具、被测工件、探针、传感器和伺服驱动等部件组成&#xff08;见图2&#xff09;。   接触式轮廓仪的工作原理是&am…

antd-React Table 中文转化

1.首先需要进行中文包导入 2.引入标签对Table进行包裹即可 import zh_CN from antd/lib/locale-provider/zh_CN;import {ConfigProvider} from antd;<ConfigProvider locale{zh_CN}><Tablecolumns{columns}rowKey{record > record.id}dataSource{data}pagination{p…

[Java]重写equals为什么要重写hashcode???配合HashMap源码一起理解

文章目录 1、什么是hashCode2、为什么要有hashCode3、为什么重写 equals 时必须重写 hashCode 方法&#xff1f;4、易错点 1、什么是hashCode hashCode()是Object定义的方法&#xff0c;它将返回一个整型值&#xff0c;这个方法通常用来将对象的内存地址转换为整数之后返回&am…

融云「北极星」数据监控平台:数据可视通晓全局,精准分析定位问题

↑ 点击预约“融云北极星”直播↑ 点击预约“实时社区”直播 近期&#xff0c;融云“北极星”数据系统完成功能迭代&#xff0c;新模块“数据监控平台”正式“履新上任”。关注【融云全球互联网通信云】了解更多 点击图片查看更多详情 此前&#xff0c;“北极星”系统主要为客…

MYSQL中的锁(面试难点重点)

首先说一下 这个加锁是个啥子过程呢 我们拿一条记录举例,这个记录就放在这,没人操作它,他就没生成锁结构, 直到有个事务操作它了,然后给它才生成了个锁结构,锁结构两个参数 trx(生成该锁的事务) is_waiting(正在等待就是:true 没在等待就是 false) (锁里面很多参数 这里这是为…

Linux/Unix-gcc编译回顾

1、gcc编译为可执行程序四步骤&#xff1a;预处理->编译->汇编->链接 注意&#xff1a;-o 用于修改生产的文件名 2、gcc常用参数 指定头文件&#xff1a;-I 语法&#xff1a; gcc -I 头文件所在文件夹路径 源文件 -o 生成文件名 如果头文件和源文件中同一个文件夹…

事务的实现原理

事务的实现 简介特性&#xff08;ACID&#xff09;状态与分类 实现机制日志机制redo logundo log 锁机制 如何使用 简介 有许多小伙伴初学事务还不太清楚是干什么的&#xff0c;那么我们在简介中一次性将事务给搞懂 首先我们先来简单的说一下事务是什么&#xff0c;以便更好的去…

企业数据治理实战总结--数仓面试必备

文章整理自涤生大数据老师宇哥&#xff0c;宇哥是历任中国电信&#xff0c;平安银行&#xff0c;微众银行&#xff0c;众安保险等多家公司擅长大数据求职面试&#xff0c;数仓开发管理&#xff0c;数据治理&#xff0c;数据质量等工作&#xff0c;主导过相关平台的建设 1 数据…

mybatis和dbeaver安装部署连接测试

ORM对象关系映射&#xff0c;把对象和数据库中的数据进行映射。mybatis是orm框架&#xff0c;mybatis-plus是mybatis的增强工具&#xff0c;简化开发。 1.准备mysql数据库 首先安装mysql https://dev.mysql.com/downloads/file/?id518835 设置密码&#xff1a;0000 然后下db…

大数据学习01-Zookeeper分布式集群部署

二、下载 zookeeper官网&#xff0c;本文使用的是zookeeper3.4.8版本 三、安装部署 将下载好的安装包上传至linux服务器上 解压安装包 tar -zxvf zookeeper-3.4.8.tar.gz -C /home/localzookeeper目录重命名 mv zookeeper-3.4.8 zookeeper配置环境 vim /etc/profile添加…

自定义实现list及其功能

#pragma once #include <iostream> #include <assert.h> using namespace std;namespace test {//******************************设置结点******************************template<class T>struct list_node{T _data;list_node<T>* _next;list_node&l…

SQL27 查看不同年龄段的用户明细

selectdevice_id,gender,casewhen age>25 then 25岁及以上when age>20 then 20-24岁when age<20 then 20岁以下else 其他end as age_cut from user_profile

Python property 定义与应用

目录 一、前言二、定义 一、前言 在 Python 类这一节中&#xff0c;会涉及到属性的私有化&#xff0c;私有化的好处在于我们无法轻易地更改类体中属性值&#xff0c;而对于类体中的私有化属性其实也并非真正的私有化&#xff0c;而是一种伪私有化&#xff0c;我们可通过 dir()…

71、redis主从复制的核心原理

redis主从复制的核心原来 通过执行slaveof命令或设置slaveof选项&#xff0c;让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作&#xff0c;当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的&#xff0c;并接受主数据库同步过来的数…

Android JetPack Compose之主题的理解与使用

目录 概述1.什么是MaterialTheme2.MaterialTheme与CompositionLocal的联系2.1 MaterialTheme的工作原理2.2 CompositionLocal2.3 CompositionLocal的两种创建方式2.3.1 compositionLocalOf2.3.2 staiticCompositionLocalOf 2.4 CompositionLocal总结 概述 根据百度百科知识&am…

Springboot + Vue 上传Word文档并保留内部格式

因为业务需求&#xff0c;上传Word文件需要编辑&#xff0c;但如何使用Blob方式&#xff0c;在数据库里存文件&#xff0c;就会造成格式消失。所以修改思路&#xff1a;上传文件到服务器本地&#xff0c;保证数据存储的完整性。 前端 <el-upload class"upload-demo&quo…

复习PHP基础教程

PHP 安装 PHP 简介PHP 语法 我需要什么&#xff1f; 如需开始使用 PHP&#xff0c;您可以&#xff1a; 使用支持 PHP 和 MySQL 的 web 主机在您的 PC 上安装 web 服务器&#xff0c;然后安装 PHP 和 MySQL。 使用支持 PHP 的 Web 主机 如果您的服务器支持 PHP&#xff0c…

基数排序|RadixSort|C++实现

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量干货博客汇总https://blog.csdn.net/yu_cblog/c…

电商 api 接口文档

电商 api 接口文档 1、开篇 欢迎使用ShowDoc&#xff01; API格式&#xff1a; 备注&#xff1a;电商API必须返回如下3个字段&#xff1a; 参数名必选类型说明status是int状态message是string信息提示result否mix结果 2、用户相关 2.1、登录/退出 简要描述&#xff1a; …