【Java面试篇】Spring中@Transactional注解事务失效的常见场景

news2025/1/11 17:08:21

文章目录

  • `@Transactional`注解的失效场景
    • ☁️前言
    • 🍀前置知识
    • 🍁场景一:`@Transactional`应用在非 public 修饰的方法上
    • 🍁场景二: `propagation` 属性设置错误
    • 🍁场景三:`rollbackFor`属性设置错误
    • 🍁场景四:方法调用导致`@Transactional`失效
    • 🍁场景五:异常捕获导致`@Transactional`失效
    • 🍁场景六:数据库引擎不支持事务
    • 🍁场景七:未启用事务
    • 🍁场景八:Bean没有纳入Spring容器管理
    • 🍁场景九:事务方法启动新线程进行异步操作
    • 🍃总结

@Transactional注解的失效场景

☁️前言

最初学习Spring时,B站的杨老师就说过“在工作中不要过于依赖@Transactional注解实现事务,我们不仅要掌握注解实现事务,还需要掌握通过配置文件实现事务”,当时他没有明确说为什么,现在我应该是大致了解了,因为@Transactional注解对于新手而言是存在很多坑的,在很多情况下@Transactional注解都会失效,现在就让我们来详细学习哪些情况下@Transactional注解实现的事务会失效吧😄。

PS:关于如何通过配置文件实现事务请参考Spring学习笔记

🍀前置知识

  • 事务:指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。可以理解事务就是一段代码块或者一行SQL,这段代码或这行SQL会更新数据库,事务具有基本的ACID特性,以此保障数据的安全性。

  • 事务管理:由事务管理器1恢复管理器2锁管理器3死锁管理器4缓存管理器5构成

  • 事务管理的作用:管理事务相关的资源;更容易处理复杂的事务;简化事务相关的操作,让程序员更关注业务

  • Spring中提供了两种事务管理机制

    • 编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。(Spring提供了TransactionTemplate模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取、复用、释放、事务同步及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确)
    • 声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低。而声明式事务有两种方式实现,方式一是基于@Transaction注解实现,方式二是基于XML实现。(Spring事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可)

    大多数 Spring 框架的用户选择声明式事务管理,因为它对应用代码的影响最小, 因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务 管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵 活性。

  • @Transactional:可以作用在接口方法

    • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
    • 作用于类:当把@Transactional 注解放在类上时,代表这个类所有公共(public)非静态(static)的方法都将启用事务功能,且都会被 Spring 的事务管理器进行管理
    • 作用于方法:当把@Transactional配置在方法上,该方法被当成一个独立的事务,且被事务管理器管理。当类配置了@Transactional,方法也配置了@Transactional,此时方法的事务会覆盖类的事务配置信息
  • @Transactional的属性

    在这里插入图片描述

    • propagation属性

      propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

      在这里插入图片描述

      • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务

      • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行

      • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常

      • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

      • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务

      • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常

      • Propagation.NESTED :和 Propagation.REQUIRED 效果一样

    • isolation属性

      事务的隔离级别,默认值为 Isolation.DEFAULT

      • Isolation.DEFAULT:使用底层数据库默认的隔离级别。

      • Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读) 基本不使用

      • Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)

      • Isolation.REPEATABLE_READ:可重复读(会出现幻读)

      • Isolation.SERIALIZABLE:串行化

    • timeout属性:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务

    • readOnly属性:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 readonlytrue

    • rollbackFor属性:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

    • noRollbackFor属性:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

🍁场景一:@Transactional应用在非 public 修饰的方法上

@Transactional注解修饰的方法必须是public修饰的,同样的@Transactional修饰类时,也只有类中使用pulbic修饰的方法才能成为事务。

须知:使用@Transactional修饰的方法,必须是public修饰、非static修饰、非final修饰的,一个不满足就会导致事务失效

原因

由于Spring的事务是通过AOP实现的,在AOP代理时,事务拦截器TransactionInterceptor在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CGlib动态代理时的事务拦截器)的 intercept 方法或 JdkDynamicAopProxy (JDK动态代理时的代理对象)的 invoke 方法都会间接调用 AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute 方法,获取Transactional 注解的事务配置信息,如下图所示:

在这里插入图片描述

备注:在老版本的Spring中,这个需要十分注意,因为当我们在非pulic方法上加@Transactional,它在编译阶段是没有任何报错信息的,但是新版的Spring是能够在编译阶段就能够进行报错,所以只要是使用较新版本的版本,基本没可能会犯这种错误,比如我当前使用的Spring版本是5.2.15,直接就在编译阶段报错了:

在这里插入图片描述

🍁场景二: propagation 属性设置错误

当我们将propagation属性的值设置为一下几种取值就会导致事务失效:

  1. Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务

  2. Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常

🍁场景三:rollbackFor属性设置错误

Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性;若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

[

Spring底层通过getDepth方法来判断出现异常是否需要进行事务回滚

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njPEUQUq-1677830697622)(D:/%E7%94%A8%E6%88%B7/ghp/Pictures/Typora/image-20230205223102898.png)]

🍁场景四:方法调用导致@Transactional失效

同一个类中,A方法是非事务性方法,但是B方法是事务性方法,此时A调用B就会导致B的事务失效。

原因

这个和场景一的原因是类似的,事务的实现是基于AOP的,而AOP的实现又是基于动态代理的,而动态代理的本质就算对方法的增强,如果想要使用增强的方法(也就是想要使用事务方法),就必须是通过代理对象去触发目标对象的方法,这个我相信只要学过设计模式都是能够理解的。

解决方案

通过AopContext.currentProxy()这个API获取当前类的代理对象

示例:

@Service
Class ServiceImpl implements IService{
    // 普通方法
	@Override
    public Result A() {
        ......
        IService proxy = (Iservice) AopContext.currentProxy();
        return proxy.B();
    }
	// 事务方法
    @Override
    @Transactional
    public Result B() {
        ......
        return Result.ok();
    }
}

推荐阅读:每日一个设计模式之【代理模式】

🍁场景五:异常捕获导致@Transactional失效

当一个事务方法中抛出了异常,此时该异常通过try...catch进行了捕获,此时就会导致该方法的事务注解@Transactional失效

示例:

@Resource
private IBService bService;

@Service
Class AServiceImpl implements IAService{
    @Transactional
    public Result A(Student s) {
     	try {
            bService.save(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.ok();
    }
}

此时会报错org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

原因:因为bService执行save方法过程中出现了异常,所以bService告诉事务管理器,当前事务需要被rollback,但是aService中使用try...catch捕获了异常,它认为当前事务并没有发生异常,程序是处于正常状态,于是aService就告诉事务管理器,当前事务需要被commit,结果事务管理器发现收到两个矛盾的信号,它也搞不清是该rollback还是该commit,于是就抛了个UnexpectedRollbackException异常。

也就是说Spring中,事务是在方法调用时开始的,业务方法执行完毕后才执行rollback或commit操作,事务是否被回滚取决于是否抛出异常,且该异常是否满足场景三(也就是说抛出的异常是否有被rollbackFor指定,或rollbackFor指定异常的子类)。如果一定要使用try..catch时,一定要抛出异常(且抛出的异常必须满足场景三,一般直接抛一个运行时异常就可以了 throw new RuntimeException(),运行时异常是rollbackFor默认指定的异常),而不只是打印异常信息。

综上所诉:在Service层中,方法中最好不要随便写try...catch,如果写了则一定要手动抛异常

🍁场景六:数据库引擎不支持事务

Spring的事务本质还是得靠数据库引擎的支持,如果数据库引擎不支持事务,那么Spring就算使用事务也是白搭。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。当然相信这个问题出现的概率很小,但并不代码没有,我们还是需要有一定了解的

注意:从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM。也就是说是从MySQL5.5.5开始,MySQL才支持事务

🍁场景七:未启用事务

想要@Transactional注解实现声明式事务,首先就需要开启事务,开启事务就三步:

  1. 配置事务管理器
  2. 开启事务的注解驱动
  3. 使用@Transactional

PS:这个事件对于向我这种初学者来说概率还是存在的,对于老手应该不太可能会出现这种低级错误了😄

开启事务相关配置:

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourceRef"></property>
    </bean>

    <!--开启事务的注解驱动,将事务管理器中的环绕通知作用到连接点,连接点使用@Transactional进行标识
    transaction-manager属性用于指定事务管理器,默认是transactionManager这个id名
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

备注:在SpringBoot中,只需要在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)这个注解,就能使用基于注解实现的声明式事务了,等价于上面的配置

🍁场景八:Bean没有纳入Spring容器管理

Spring的事务管理核心是动态代理,不是动态代理的Bean是无法进行被Spring进行事务管理的

将Bean纳入Spring容器管理的方法:

  • 方式一:配置XML

     <bean id="xxx" class="xxx.xxx.xxx"></bean>
    
  • 方式二:添加注解,比如:@Controller、@Service、@Repository、@Conponent……

🍁场景九:事务方法启动新线程进行异步操作

spring 的事务是通过LocalThread来保证线程安全的,事务和当前线程绑定,此时开启新线程执行业务,这个新线程的业务就会事务失效,因为事务是基于动态代理的,要想有事务,需要被动态代理。这里提供一种解决方法,可以将新的业务单独封装成一个方法,然后改方法上添加一个@Transactional,或者将这个无法单独抽取到一个类中,将该类交给IOC容器进行管理,这样就能让新线程的业务具有事务了

🍃总结

@Transactional使用事务具有几个基本的要求:

  1. 必须开启注解事务
  2. 事务所在类,必须要交给IOC容器进行管理
  3. 事务所在目标,必须要进行动态代理
  4. 事务所在方法必须要是public

总而言之,使用@Transactional来启动事务,有很多坑,对于简单的业务还是推荐直接使用注解进行事务管理,对于复杂的业务还是推荐使用XML进行事务管理

在这里插入图片描述

参考文章

  • 一口气说出 6种,@Transactional注解的失效场景 - 掘金 (juejin.cn)
  • 咱们从头到尾说一次 Spring 事务管理(器) - 掘金 (juejin.cn)
  • spring 事务失效的 12 种场景-CSDN博客

在此致谢(^^ゞ🌹🌹🌹


  1. 事务管理器:负责产生事务并为其分配事务标识 ↩︎

  2. 恢复管理器:子事务提交时,负责将子事务的日志链接到父事务的日志上,确保事务的一致性原则 ↩︎

  3. 锁管理器:事务申请锁时,负责判断锁的相容性 ↩︎

  4. 死锁管理器:负责检测死锁 ↩︎

  5. 缓存管理器:提供对事务标识的缓存 ↩︎

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

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

相关文章

Apache druid未授权命令执行漏洞复现

简介 Apache Druid是一个实时分析型数据库&#xff0c;旨在对大型数据集进行快速的查询分析&#xff08;"OLAP"查询)。Druid最常被当做数据库来用以支持实时摄取、高性能查询和高稳定运行的应用场景&#xff0c;同时&#xff0c;Druid也通常被用来助力分析型应用的图…

【蓝桥杯嵌入式】拓展板之数码管显示

文章目录硬件电路连接方式函数实现文章福利硬件电路 通过上述原理图&#xff0c;可知拓展板上的数码管是一个共阴数码管&#xff0c;也就是说某段数码管接上高电平时&#xff0c;就会点亮。   上述原理图还给出一个提示&#xff0c;即&#xff1a;三个数码管分别与三个74HC59…

十八、Django-restframework之请求和响应(三)

1. 请求对象 REST框架引入了一个扩展了常规HttpRequest的请求对象&#xff0c; 并提供更灵活的请求解析。请求对象的核心功能是属性request.data&#xff0c;这与request.POST类似&#xff0c;但对于WebAPIs更有用。 request.POST # Only handles form data. Only works fo…

Barra模型因子的构建及应用系列五之NonLinear Size因子

一、摘要 在前期的Barra模型系列文章中&#xff0c;我们构建了Size因子、Beta因子、Momentum因子和Residual Volatility因子&#xff0c;并分别创建了对应的单因子策略&#xff0c;本节文章在该系列下进一步构建NonLinear Size因子。从回测结果看&#xff0c;自2022年以来&…

ConcurrentHashMap-Java八股面试(五)

系列文章目录 第一章 ArrayList-Java八股面试(一) 第二章 HashMap-Java八股面试(二) 第三章 单例模式-Java八股面试(三) 第四章 线程池和Volatile关键字-Java八股面试(四) 提示&#xff1a;动态每日更新算法题&#xff0c;想要学习的可以关注一下 文章目录系列文章目录一、…

基于卷积神经网络CNN的三相故障识别

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络CNN手写体识别 基本结构 主要参数 MATALB代码 结果图 展望 背影 现在生活&#xff0c;为节能减排&#xff0c;减少电能损…

Ajax学习 基础概念 发送请求 常见方法

AJAX 简介 AJAX 全称为Asynchronous Javascript And XML &#xff0c;就是异步的JS和XML。 通过AJAX可以再浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据。 AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组合在一起使用的新方式。…

从FPGA说起的深度学习(四)

这是新的系列教程&#xff0c;在本教程中&#xff0c;我们将介绍使用 FPGA 实现深度学习的技术&#xff0c;深度学习是近年来人工智能领域的热门话题。在本教程中&#xff0c;旨在加深对深度学习和 FPGA 的理解。用 C/C 编写深度学习推理代码高级综合 (HLS) 将 C/C 代码转换为硬…

锚点定位使内容在指定区域显示

1、问题描述 在使用锚点定位时&#xff0c;使用 scrollIntoView 方法&#xff0c;导致整个页面随着锚点跳转 2、问题分析 因为最开始做需求的时候&#xff0c;只在最外层设置了一个滚动条&#xff0c;所以导致整体锚点的跳转是随着最外层滚动条距离顶部的高度跳转的&#xf…

从NLP视角看电视剧《狂飙》,会有什么发现?

文章目录1、背景2、数据获取3、文本分析与可视化3.1 短评数据预处理3.2 词云图可视化3.3 top关键词共现矩阵网络3.4 《狂飙》演职员图谱构建4、短评相关数据分析与可视化5、总结原文请关注&#xff1a;实用自然语言处理 作者&#xff1a;风兮 建议查看原文&#xff1a; https…

Linux下软件部署安装管理----rpmbuild打包rpm包部署安装

来源&#xff1a;微信公众号「编程学习基地」 文章目录1.安装rpmbuild2.rpm包制作打包rpm包3.rpm包安装4.rpm包卸载1.安装rpmbuild yum install rpmbuild yum install rpmdevtools创建rpm包管理路径&#xff0c;生成rpm相关目录 RPM打包的时候需要编译源码&#xff0c;还需要…

基于Cortex-M7内核STM32F767NIH6,STM32F767VGT6,STM32F767VIT6嵌入式技术资料

STM32F7 32 位 MCUFPU 基于高性能的 ARMCortex-M7 32 位 RISC 内核&#xff0c;工作频率高达 216MHz。Cortex-M7 内核具有单浮点单元(SFPU)精度&#xff0c;支持所有 ARM 单精度数据处理指令与数据类型。同时执行全套 DSP 指令和存储保护单元&#xff08;MPU&#xff09;&#…

【完美解决】应用程序无法正常启动(0xc000007b)请单击“确定”关闭应用程序

年期安装CorelDRAW X8 (64-Bit)&#xff0c;安装完成之后运行一点毛病都没有&#xff0c;可是过了两三个月&#xff0c;再打开就出现“应用程序无法正常启动(0xc000007b)请单击“确定”关闭应用程序”这个提示框&#xff0c;如下图示 出现这个问题我就上网查找&#xff0c;无非…

Java学习笔记 --- JavaScript

一、JavaScript介绍 JavaScript语言诞生主要是完成页面的数据验证。因此它运行在客户端&#xff0c;需要运行浏览器来解析执行JavaScript代码。JS是Netcape网景公司的产品&#xff0c;最早取名为LiveScript&#xff1b;为了吸引更多java程序员。更名为 JavaScript JS是弱类型&…

File 文件操作

File 文件操作&#xff1a; 一、常用方法&#xff1a; 方法类型描述public File(String pathname&#xff09;构造给定一个要操作文件的完整路径public File(File parent, String child)构造给定要操作文件的父路径和子文件名称public boolean createNewFile() throws IOExce…

hexo部署github搭建个人博客 完整详细带图版(更新中)

文章目录0. 前置内容1. hexo创建个人博客2. GitHub创建仓库3. hexo部署到GitHub4. 常用命令newcleangenerateserverdeploy5. 添加插件5.1 主题5.2 博客基本信息5.3 创建新的菜单5.4 添加搜索功能5.5 添加阅读时间字数提示5.6 打赏功能5.7 切换主题5.8 添加不蒜子统计5.9 添加百…

小程序的拉流组件live-player的使用

前言&#xff1a; 我们在小程序中实现音视频-直播/录播 的播放时候&#xff0c;会使用到微信官方提供的两个组件&#xff0c;推流组件和拉流组件&#xff0c;这里来分享下他的拉流组件的使用和具体需要注意的点。 效果图&#xff1a; 1、拉流状态code日志 2、代码使用截图&am…

深度学习 Day26——利用Pytorch实现天气识别

深度学习 Day26——利用Pytorch实现天气识别 文章目录深度学习 Day26——利用Pytorch实现天气识别一、前言二、我的环境三、前期工作1、导入依赖项和设置GPU2、导入数据3、划分数据集四、构建CNN网络五、训练模型1、设置超参数2、编写训练函数3、编写测试函数4、正式训练六、结…

前端利用emailjs发送邮件

最近有一个需求&#xff0c;前端发送一个form表单到一个邮箱&#xff0c;找了一圈发现emailjs还不错就使用他了。首先emailjs官网注册一个账号注册完之后创建一个邮件服务&#xff08;我这里使用的是谷歌邮箱&#xff09;链接谷歌邮箱账户 然后创建服务接下来就要创建一个邮件的…

浅谈入门Servlet注解式开发

Servlet3.0版本之后&#xff0c;推出了Servlet基于注解式开发。 优点&#xff1a;开发效率高&#xff0c;直接在java类上使用注解进行标注&#xff0c;可直接省略WEB.xml文件配置import javax.servlet.annotation.WebServlet; WebServlet 使用WebServlet注解标注 WebServlet的…