《后端程序猿 · Spring事务失效场景》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • Spring 事务失效场景
      • 方法访问修饰符非public
      • 方法使用 static
      • 方法使用 final
      • 同类方法自调用(★)
      • 异步调用场景(★)
      • 没有被 Spring 管理
      • 异常类型不符合
      • 传播行为不当
      • 其他场景
    • 扩展 · 部分源码分析
    • 总结陈词

写在前面的话

Spring 事务管理是通过 AOP(面向切面编程)实现的,提供了声明式事务管理的能力。尽管 Spring 提供了强大的事务管理功能,但在某些情况下,事务可能会失效。

推荐文章

《故障复盘 · 记一次事务用法错误导致的大量锁表问题》


Spring 事务失效场景

方法访问修饰符非public

场景描述:

Java 的访问权限主要是:private、default、protected、public,它们的权限则是依次变大。

如果事务方法的访问修饰符是 protected、private 或 default,Spring AOP 代理无法拦截这些方法。

逻辑分析:

AbstractFallbackTransactionAttributeSource类 的 computeTransactionAttribute 方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {// Don't allow non-public methods, as configured.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}//省略部分代码
}

解决方案:

将事务方法的访问修饰符设置为 public。


方法使用 static

场景描述:

如果事务方法被声明为 static,则 Spring AOP 代理无法拦截该方法。

逻辑分析:

静态方法属于类本身,而不是类的实例。Spring AOP 代理是基于实例的,因此无法对静态方法进行代理。

解决方案:

避免将事务方法声明为 static。


方法使用 final

场景描述:

在 Java 中,final 关键字用于修饰类、方法和变量,表示这些元素不能被修改或重写。

如果事务方法被声明为 final,则 Spring AOP 代理无法拦截该方法。

逻辑分析:

final 方法在编译时会被优化,无法被子类重写。CGLIB 代理是通过子类化来实现的,因此无法代理 final 方法。

例如,CGLIB 代理的实现中,如果方法被标记为 final,则在生成代理类时会跳过这些方法:

public class Enhancer {protected void generateMethod(ClassGenerator gen, Method method) {// 跳过 final 方法if (Modifier.isFinal(method.getModifiers())) {return; }}
}

解决方案:

避免将事务方法声明为 final。

Tips:上面几个错误很明显,IDEA也会给出相应提示,应该不容易会出现。


同类方法自调用(★)

场景描述:

当一个类中的方法 A 调用同一类中的方法 B 时,如果方法 B 上有事务注解(如 @Transactional),而方法 A 没有,事务可能会失效。

逻辑分析:

如事务注解 @Transactional 是基于动态代理实现的,Spring 采用动态代理(AOP)实现对 bean 的管理和切片,它为我们的每个 class 生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。而在同一个 class 中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象,所以 Spring 无法切到这次调用,也就无法通过注解保证事务性了。

友情提示:@Aspectj、@Async,@Transational、@Cacheable 等注解都是基于AOP 实现的,AOP是基于动态代理实现,存在问题差不多。

原理补充:

由于 Spring AOP 采用了动态代理实现,在Spring 容器中的bean(也就是目标对象)会被代理对象代替,代理对象里加入了我们需要的增强逻辑,当调用代理对象的方法时,目标对象的方法就会被拦截。通过调用代理对象的A方法,在其内部会经过切面增强,然后方法被发射到目标对象,在目标对象上执行原有逻辑,如果在原有逻辑中(同类)嵌套调用了B方法,则此时B方法并没有被进行切面增强,因为此时它已经在目标对象内部。而解决方案很好地说明了,将嵌套方法发射到代理对象,这样就完成了切面增强。

解决方案1:

迁移方法,把业务逻辑抽离到另外一个Service,然后正常注入,调用。

这种方式最稳妥,但是改造量可能偏大。

解决方案2:

获取本对象的代理对象,再进行调用,有两种方式:

1、从Bean工厂获取,注入自身,或者通过getBean方式;

2、使用 AopContext.currentProxy() 方式;

Tips:注入自身的问题,由于这种写法基于Spring的三级缓存不会导致循环依赖的问题出现。

解决方案3:

使用编程式事务,自主控制事务的提交和回滚。


异步调用场景(★)

场景描述:

如果你将 @Async 和 @Transactional 注解放在同一个方法上,通常会导致事务失效。这是因为 @Async 注解会导致该方法在一个新的线程中执行,而 Spring 的事务管理是基于代理的,通常只在同一线程中有效。

逻辑分析:

1、代理机制:Spring 的事务管理是通过 AOP(面向切面编程)实现的,通常使用 JDK 动态代理或 CGLIB 代理。当你在一个方法上使用 @Transactional 注解时,Spring 会创建一个代理对象来管理事务。

2、异步执行:当你在同一个方法上使用 @Async 注解时,Spring 会将该方法的调用转发到一个新的线程中执行。由于事务是绑定到调用线程的,而不是代理对象的,因此在新的线程中,事务不会生效。

解决方案1:

将事务和异步调用分开:将 @Transactional 注解放在一个单独的方法上,然后在该方法中调用带有 @Async 注解的方法。

解决方案2:

如果一定要先用异步逻辑,那么可以在异步逻辑中使用编程式事务。

补充:为什么同时加 @Async 和 @Transactional 时,异步生效而不是事务生效?

这主要是因为 Spring 的事务管理机制 和 异步执行机制 的工作原理不同。

1、事务管理机制:Spring 的事务管理是基于 代理 的。当一个方法被 @Transactional 注解标记时,Spring 会为该方法生成一个代理对象。这个代理对象会拦截方法调用,并在方法执行前后进行事务管理操作(例如,开始事务、提交事务、回滚事务)。

2、异步执行机制:Spring 的异步执行机制是基于 线程池 的。当一个方法被 @Async 注解标记时,Spring 会将该方法提交到一个线程池中执行。线程池会创建一个新的线程来执行该方法,而这个新的线程与当前线程是独立的。

当 @Async 和 @Transactional 同时存在时,Spring 会优先处理 @Async 注解。这意味着方法会被提交到线程池中执行,而不是被代理对象拦截。由于事务管理是基于代理的,因此在异步线程中,事务管理机制无法生效。简单来说,事务管理需要在同一个线程中进行,而异步执行会创建新的线程,导致事务管理无法生效。

总之,@Async 注解会将方法提交到线程池中执行,而 @Transactional 注解会为方法生成代理对象。Spring 会优先处理 @Async 注解,因此事务管理机制无法在异步线程中生效。


没有被 Spring 管理

场景描述:

如果一个被 @Transactional 注解的方法被一个非 Spring 管理的类调用,事务也会失效。

逻辑分析:

这个好像不用多说了,SpringAOP都不会触发。

直接检查一下是不是Bean扫描路径不对等问题。


异常类型不符合

场景描述:

默认情况下,Spring 只会对未检查异常(RuntimeException)进行回滚。如果抛出的是检查异常(Exception),事务不会自动回滚,除非在 @Transactional 注解中指定。

逻辑分析:

@Transactional
public void createUser() {// 业务逻辑throw new IOException(); // 事务不会回滚
}

@Override
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}

解决方案:

按需调整,例如:@Transactional(rollbackFor = Exception.class)


传播行为不当

场景描述:

事务的传播行为决定了方法调用时如何处理事务。如果传播行为设置不当,可能导致事务失效。例如,使用 Propagation.REQUIRES_NEW 会导致新事务的创建,而不是在现有事务中执行。

逻辑分析:

这种情况不能说事务失效,只能说要按自己的需要处理。


其他场景

1、数据库不支持事务(较少);

2、项目没有开启事务能力(较少);

3、开发捕获了异常导致没回滚(常见);

4、未完待续。。。


扩展 · 部分源码分析

模拟一个事务方法调用,当该方法被调用时,Spring AOP 会拦截这个调用,进入下面的流程。

Step1、事务拦截器触发

参考:TransactionInterceptor#invoke

说明:TransactionInterceptor 会调用 invoke 方法,检查方法上是否有 @Transactional 注解。

Step2、获取事务属性

参考:AbstractFallbackTransactionAttributeSource#getTransactionAttribute

说明:getTransactionAttribute 方法来获取事务属性,这里还涉及一个缓存机制,先不管。

Step3、开启事务

参考:TransactionAspectSupport#invokeWithinTransaction

说明:通过 PlatformTransactionManager 的 getTransaction 方法开始一个新事务,这会创建一个 TransactionStatus 对象,表示当前事务的状态。

Step4、执行目标方法

参考:retVal = invocation.proceedWithInvocation()

说明:执行目标方法,操作数据库。

Step5、提交或回滚事务

如果目标方法执行成功,TransactionInterceptor 会调用 commitTransactionAfterReturning/commit 方法提交事务。

如果在执行过程中抛出异常,TransactionInterceptor 会调用 completeTransactionAfterThrowing/rollback 方法回滚事务。

Step6、结束事务

参考:cleanupTransactionInfo

说明:TransactionInterceptor 会清理事务状态,结束事务。


总结陈词

此篇文章介绍了 Spring 事务的常见失效场景,仅供学习参考。

通过本篇文章的分析,可以看到,Spring事务失效的原因,大半部分和SpringAOP原理有关系,如果某些因素导致AOP无法生效或代理类无法操作,则事务随之失效了。从源码分析过程中,也能找到部分事务失效场景对应的代码。

Spring 的事务能讨论的知识点还很多,后续有机会再进行专题补充,

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

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

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

相关文章

2024/9/29周报

文章目录 摘要Abstract污水处理工艺流程整体介绍粗格栅细格栅曝气沉砂池提升泵房峰谷平策略 初沉池&#xff08;一级处理&#xff09;工作原理运行管理 氧化沟生化池&#xff08;二级处理&#xff09;二沉池工作原理运行参数 高效沉淀池功能与特点工作原理 深度处理&#xff08…

LeetCode 918. 环形子数组的最大和

原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n…

防止错误输入!Excel单元格限制输入内容的三种有效方式

在Excel中&#xff0c;限制单元格输入内容可以帮助避免数据输入错误&#xff0c;确保数据的一致性和准确性。今天小编分享三种方法&#xff0c;可以轻松限制Excel单元格的输入内容&#xff0c;确保数据输入符合预期要求&#xff0c;一起来看看吧&#xff01; 方法一&#xff1a…

Register Two Point Sets 注册两个点集

文章目录 Register Two Point Sets 注册两个点集Visualize Gradient Descent 可视化梯度下降Hyperparameter Search 超参数搜索JensenHavrdaCharvatTsallisPointSetToPointSetMetricv4类说明 原文url: https://examples.itk.org/src/registration/metricsv4/registertwopointse…

【AI驱动TDSQL-C Serverless数据库技术实战】 AI电商数据分析系统——探索Text2SQL下AI驱动代码进行实际业务

目录 一、Text2SQL简介二、基于TDSQL-C Serverless的Text2SQL实战2.1、程序流程图2.2、实践流程2.2.1、配置TDSQL-C2.2.2、部署LLAMA模型2.2.3、本地依赖安装2.2.4、应用构建 2.3、运行效果 三、Text2SQL下的AI驱动 Text2SQL 是一种将自然语言查询转换为 SQL 查询的技术&#x…

中航资本:沪市主板代码以几开头?各板块开头代码是多少

各板块开始代码&#xff1a; 场内商场&#xff1a; 1、沪市主板&#xff1a;股票代码以600、601、603、605开始。 60开始的股票都是在上海证券交易所上市的股票。 600开始是上海证券交易所上市的一般股票&#xff0c;601开始的股票是主板股票&#xff0c;一般是大盘股蓝筹股…

Hi.Events —— 您的全方位活动管理与票务平台

大家好&#xff01;今天给大家介绍一个超厉害的开源项目&#xff1a;Hi.Events&#xff0c;这是一个功能丰富的自托管活动管理和票务平台&#xff0c;无论是会议还是俱乐部活动&#xff0c;它都能帮你轻松搞定&#xff01; 项目介绍 Hi.Events是一款功能丰富、自托管的开源活动…

学习Webpack中图片-JS-Vue-plugin

目录 图片文件资源模块类型 JS文件babel命令行使用babel-loaderbabel-preset Vue文件vue-loadervue/compiler-sfc pluginCleanWebpackPluginHtmlWebpackPluginDefinePlugin 图片文件 需要先在项目中使用图片&#xff0c;比较常见的使用图片的方式是两种&#xff1a; img元素&…

关于git分支冲突问题

什么是冲突 在Git中&#xff0c;冲突是指两个或多个开发者对同一文件统一部份进行了不同的修改&#xff0c;并且在合并这些修改时&#xff0c;Git无法自动确定应该采用哪种修改而产生的情况。 分支冲突 如何出现并解决 在一个版本时&#xff0c;有一个master分支&#xff0c…

如何使用WinRAR锁定压缩文件,防止文件被修改或删除?

在日常工作中&#xff0c;我们经常需要分享压缩文件&#xff0c;但也可能面临文件被修改或删除的风险。想要保护压缩文件的完整性&#xff0c;不妨使用WinRAR提供的“锁定压缩文件”功能。这个功能可以防止文件被意外更改或删除&#xff0c;确保压缩文件保持原样。下面一起来看…

【Android 14源码分析】Activity启动流程-1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

Llama 3.2:利用开放、可定制的模型实现边缘人工智能和视觉革命

在我们发布 Llama 3.1 模型群后的两个月内&#xff0c;包括 405B - 第一个开放的前沿级人工智能模型在内&#xff0c;它们所产生的影响令我们兴奋不已。 虽然这些模型非常强大&#xff0c;但我们也认识到&#xff0c;使用它们进行构建需要大量的计算资源和专业知识。 我们也听到…

Meta首款多模态Llama 3.2开源:支持图像推理,还有可在手机上运行的版本 | LeetTalk Daily...

“LeetTalk Daily”&#xff0c;每日科技前沿&#xff0c;由LeetTools AI精心筛选&#xff0c;为您带来最新鲜、最具洞察力的科技新闻。 Meta最近推出的Llama Stack的发布标志着一个重要的里程碑。这一新技术的推出不仅为开发者提供了强大的多模态能力&#xff0c;还为企业和初…

安卓13设置删除网络和互联网选项 android13隐藏设置删除网络和互联网选项

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改4.1修改方法14.2修改方法25.编译6.彩蛋1.前言 有些客户不想让用户修改默认的网络配置,禁止用户进入里面调整网络相关的配置。 2.问题分析 像这个问题,我们有好几种方法去处理,这种需求一般…

IDEA Dependency Analyzer 分析 maven 项目包的依赖

一、场景分析 javax.validation 是我们 SpringMVC 常用的数据校验框架。但是 javax.validation 是一个规范&#xff08;Java Bean Validation&#xff0c;简称 JSR 380&#xff09;&#xff0c;它并没有具体的实现&#xff0c;它的常用实现&#xff0c;是hibernate-validator。…

匿名管道 Linux

管道 首先自己要用用户层缓冲区&#xff0c;还得把用户层缓冲区拷贝到管道里&#xff0c;&#xff08;从键盘里输入数据到用户层缓冲区里面&#xff09;&#xff0c;然后用户层缓冲区通过系统调用&#xff08;write&#xff09;写到管道里&#xff0c;然后再通过read系统调用&…

[leetcode] 70. 爬楼梯

文章目录 题目描述解题方法动态规划java代码复杂度分析 题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1…

城市轨道交通网络客流大数据可视化分析系统----以某市交通网络客流数据为例

1 引言 1.1研究背景、目的与意义 1.1.1研究背景 城市轨道交通系统是现代城市的重要交通方式之一&#xff0c;随着城市化进程的加速和人口增长&#xff0c;轨道交通系统的客流量不断增加。因此&#xff0c;轨道交通部门和相关企业需要对客流数据进行实时监测和分析&#xff0…

BERT训练之数据集处理(代码实现)

目录 1读取文件数据 2.生成下一句预测任务的数据 3.预测下一个句子 4.生成遮蔽语言模型任务的数据 5.从词元中得到遮掩的数据 6.将文本转化为预训练数据集 7.封装函数类 8.调用 import os import random import torch import dltools 1读取文件数据 def _read_wiki(data_d…

可视化是工业互联网的核心技术之一,都有哪些应用场景?

一、工业互联网是什么&#xff0c;发展的来胧去脉 工业互联网是指利用互联网技术和物联网技术&#xff0c;将工业生产中的各种设备、机器、传感器等进行互联互通&#xff0c;实现信息的实时采集、传输和分析&#xff0c;从而实现生产过程的智能化、自动化和高效化。 工业互联网…