记一次Spring事务失效的发现与解决过程

news2025/1/17 1:09:59

一、事情起因是这样的

        首先,我们是使用Spring + mybatis 进行开发。

        某功能在测试环境看到报错日志, 但是数据库里面的数据发生了变化,没有回滚。

        执行数据库update 操作的方法上明确有 

@Transactional(rollbackFor = Exception.class)的注解。 

        假设,异常日志之前执行的sql是

update t1 set c1= 10 where id = 10 //原来 c1 = 5

        方法抛出异常后, 数据库里面确实被修改成了10 。 说明事务没有回滚,或者事务没有生效。

二、验证是事务失效还是回滚失败

        1、本地启动服务, 在update 执行的结束的地方和异常抛出之前,打断点, 执行玩update。使用Navicat查看, 数据已经被修改。 说明事务没生效或者数据库隔离级别为读未提交。

        2、检查数据库隔离级别:可重复读

        在可重复度的隔离级别下, 事务之间是是无法看到相互之间没有提交的数据的。所以确定当前问题是,事务失效。@Transactional(rollbackFor = Exception.class) , 没有生效。

        下面就开始了查看@Transactional在什么情况下会失效。

三、Spring Transactional 失效的可能原因

1、、失效场景集一:代理不生效

(1)将注解标注在接口方法上

@Transactional是支持标注在方法与类上的。一旦标注在接口上,对应接口实现类的代理方式如果是CGLIB,将通过生成子类的方式生成目标类的代理,将无法解析到@Transactional,从而事务失效。

这种错误我们还是犯得比较少的,基本上我们都会将注解标注在接口的实现类方法上,官方也不推荐这种。

(2)被final、static关键字修饰的类或方法

CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰后,无法继承父类与父类的方法。

(3)类方法内部调用

事务的管理是通过代理执行的方式生效的,如果是方法内部调用,将不会走代理逻辑,也就调用不到了。

2、失效场景集二:框架或底层不支持的功能

(1)非public修饰的方法

(2)多线程调用

(3)数据库本身不支持事务

比如Mysql的Myisam存储引擎是不支持事务的,只有innodb存储引擎才支持。

这个问题出现的概率极其小,因为Mysql5之后默认情况下是使用innodb存储引擎了。

但如果配置错误或者是历史项目,发现事务怎么配都不生效的时候,记得看看存储引擎本身是否支持事务。

(4)未开启事务

这个也是一个比较麻瓜的问题,在Springboot项目中已经不存在了,已经有DataSourceTransactionManagerAutoConfiguration默认开启了事务管理。

但是在MVC项目中还需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

3、失效场景集三:错误使用@Transactional

(1)错误的传播机制

(2)rollbackFor属性设置错误

默认情况下事务仅回滚运行时异常和Error,不回滚受检异常(例如IOException)。

因此如果方法中抛出了IO异常,默认情况下事务也会回滚失败。

我们可以通过指定@Transactional(rollbackFor = Exception.class)的方式进行全异常捕获。

(3)异常被内部catch

(4)嵌套事务

检查了以上问题, 在我们系统中不存在这些问题。 所以很苦恼。

四、查找事务失效的真正原因

首先捋一下思路:

在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务.

事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager 的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

1、进行一次Spring未生效事务管理的Debug

(1)、我们的测试代码

@Service
@Slf4j
public class TestSevice {

    @Autowired
    XXXDao xxxDao;

    @Transactional(rollbackFor = Exception.class)
    public void updateAData() {
        System.out.println("updateAData 方法执行开始");
        //这里进行数据库操作
        xxxDao.updateOne(XXX.builder().id("0002C2B4-D5AA-4596-B592-510185E4B115").type("8888").build());
        //throw异常
        throw  new RuntimeException("error");
        System.out.println("updateAData 方法执行完成");

    }
}

 我们期待能够正常回滚!

(2)我们打开 

org.springframework.transaction.interceptor.
TransactionInterceptor

在如下位置打个断点

一步步跟下去会发现在 

org.springframework.transaction.interceptor.

TransactionAspectSupport

发现 

PlatformTransactionManager 是 KafkaTransactionManager ~ WTF!!

先往下走走看。

进入completeTransactionAfterThrowing, 我们会发现。 最终进入到AbstractPlatformTransactionManager.rollback()

最终我们会发现,调用了KafkaTransactionManager(实现了 AbstractPlatformTransactionManager)的rollback。并没有对mysql 数据源做任何的rollback。

2、为什么会是Kafka的事务管理器呢?

反过来想一下?为什么AbstractPlatformTransactionManager 的四个实现里面, 只有Kafka的注册进去了呢?

gpt 查了下这四种事务管理器。大概如下。

CciLocalTransactionManager
  • CciLocalTransactionManager 通常用于与企业信息系统 (EIS) 的交互,如数据库、消息队列等。
  • 它是针对 Common Client Interface (CCI) 标准的事务管理器,用于管理在单个资源管理器范围内的本地事务。
  • 主要用于与符合 CCI 标准的资源适配器一起使用,确保在事务上下文中适当地协调对这些资源的操作。
JtaTransactionManager
  • JtaTransactionManager 是用于管理分布式事务的事务管理器之一。
  • 它是在 Java EE 环境下使用的,通常与 JTA(Java Transaction API)一起使用。
  • JTA 是 Java 平台的事务管理 API,用于管理分布式事务,允许跨多个资源管理器的事务管理。
  • JtaTransactionManager 通过 JTA 实现分布式事务的管理,它协调不同资源管理器之间的事务操作,确保事务的一致性和隔离性。
DataSourceTransactionManager
  • DataSourceTransactionManager 通常用于与关系型数据库 (RDBMS) 的交互。
  • 它是 Spring 框架中用于管理数据库事务的事务管理器之一。
  • 主要用于管理与 DataSource 关联的事务,DataSource 是用于获取数据库连接的接口。
  • DataSourceTransactionManager 通过 JDBC 连接管理事务,它可以管理多个连接,并将它们与 Spring 中的事务进行关联。
KafkaTransactionManager
  • KafkaTransactionManager 是用于管理 Kafka 消息系统中事务的事务管理器。
  • 它是在 Spring Kafka 框架中使用的,用于管理 Kafka 生产者和消费者之间的事务。
  • KafkaTransactionManager 实现了 Kafka 的事务性 API,允许在生产者和消费者之间实现端到端的事务性保证。
  • 它管理 Kafka 生产者和消费者之间的事务性操作,以确保消息的可靠传递和处理。

结论是:CciLocalTransactionManager  JtaTransactionManager 我们并没有使用, 也没有配置。 所以不会被注册进去。

那么我们看下DataSourceTransactionManager 在没有Kafka 的情况下, 是怎么注入到SpringBean管理器的

看到了这个 @ConditionalOnMissingBean注解。 在(IOC容器内)不存在PlatformTransactionManager的实现类的时候,才加载并注入DataSourceTransactionManager(PlatformTransactionManager的实现)。所以。 在这里, Spring 容器先加载了KafkaTransactionManager。 就不会再加载DataSourceTransactionManager。

3、So!问题是由于数据源事务管理器没有注入。

下面我们尝试一下,手动写一个配置类,将DataSourceTransactionManager 注入到容器内。

@Configuration
public class DatasourceTransactionConfig {
    private final DataSource dataSource;

    private final TransactionManagerCustomizers transactionManagerCustomizers;

    DatasourceTransactionConfig(DataSource dataSource,
                                ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        this.dataSource = dataSource;
        this.transactionManagerCustomizers = transactionManagerCustomizers.getIfAvailable();
    }

    @Bean
    @Primary  //配置默认事务管理器
    public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource);
        if (this.transactionManagerCustomizers != null) {
            this.transactionManagerCustomizers.customize(transactionManager);
        }
        return transactionManager;
    }

}

我们尝试一下,事物是否生效?

生效!完美! 

但是Kafka的事务怎么办呢?

我们再次debug进去吧

这里的确是DatasourceTransactionManager了。Kafka的去哪里了呢?

我们跳到上图的上一步

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

进入determineTransactionManager 发现, transactionManagerCache只有一个TransactionManager, 所以如果Kafka有事务需求, 一样会失效。

这可如何是好。

五、兼容Kafka和DataSource两个TransactionManager

可以尝试链式事务管理器。

        ChainedTransactionManager 是 Spring Data 项目中的一个事务管理器实现。可以向 ChainedTransactionManager 指定事务管理器列表,它将按照列表中事务管理器的顺序启动事务。在退出时(即方法退出时),事务将按照事务管理器列表的相反顺序提交。

        虽然这听起来是个合理的策略,但需要注意的一点是,ChainedTransactionManager 目前已被弃用,不推荐使用。弃用的原因见 大意是,人们通常希望 ChainedTransactionManager 是一颗神奇的银弹,可以解决所有事务问题,包括具有两阶段提交的分布式事务和其他问题,而这与事实相去甚远。

        ChainedTransactionManager 只能确保事务按特定顺序启动和提交。它不能保证任何事务同步,更不用说任何分布式事务协调了。假设你可以接受 ChainedTransactionManager 的限制,并希望按照我们的用例所要求的特定顺序执行事务。在这种情况下,使用该事务管理器是合理的,只要你记住你使用的是框架中一个已废弃的类即可。

public class TransactionConfig {
    private final DataSource dataSource;

    private final TransactionManagerCustomizers transactionManagerCustomizers;

    TransactionConfig(DataSource dataSource,
                      ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        this.dataSource = dataSource;
        this.transactionManagerCustomizers = transactionManagerCustomizers.getIfAvailable();
    }

    @Bean
    @Primary  //配置默认事务管理器
    public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource);
        if (this.transactionManagerCustomizers != null) {
            this.transactionManagerCustomizers.customize(transactionManager);
        }
        return transactionManager;
    }

    @Bean
    public ChainedKafkaTransactionManager chainedKafkaTransactionManager(DataSourceTransactionManager transactionManager,
                                                                         KafkaTransactionManager<?, ?> kafkaTransactionManager){
        return new ChainedKafkaTransactionManager<>(transactionManager, kafkaTransactionManager);
    }
}

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

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

相关文章

使用nuclei扩充自己的武器库-nuclei3 sdk开发指南

一、nuclei是什么 Nuclei 是一个快速、灵活、可扩展的开源漏洞扫描工具&#xff0c;用于发现 Web 应用程序中的安全问题。它允许安全研究人员和渗透测试人员轻松地定义和执行定制的漏洞扫描任务。 Nuclei 使用模板来定义漏洞扫描任务。这些模板是基于 YAML 格式的&#xff0c…

什么是MVC三层结构

1.MVC&#xff08;三层结构&#xff09; MVC&#xff08;Model-View-Controller&#xff09;是一种常见的软件设计模式&#xff0c;用于将应用程序的逻辑和界面分离成三个不同的组件。每个组件负责特定的任务&#xff0c;从而提高代码的可维护性和可扩展性。 以前的模式。 遇到…

kettle 9.4和Pentoho 9.4下载及安装方法简介

kettle 9.4和Pentoho 9.4下载及安装方法简介 下载地址&#xff1a; https://sourceforge.net/projects/pentaho/files/ 下载步骤&#xff1a; #------------- 一、点击选项卡&#xff1a;summary/ 二、点击第一行链接 https://www.hitachivantara.com/en-us/products/pentaho…

姿态旋转的哥氏定理以及速度微分的推导

姿态旋转中涉及到坐标系的转换&#xff0c;在有相对旋转的两个坐标系中观察一个向量的变化&#xff0c;用到了哥氏定理。 例如在i系中观察e系下的运动&#xff0c;则 哥氏定理的公式 wie是e相对于i的角运动 注意符号i在前e在后。 wie是e相对于i的角运动 注意符号i在前e在…

WRF模型教程(ububtu系统)-WPS(WRF Pre-Processing System)概述

一、WPS简介 WRF 预处理系统 (WRF Pre-Processing System&#xff0c;WPS) &#xff0c;集成了基于Fortran和C编写的程序&#xff0c;这些程序主要用于处理输入到real.exe的数据。WPS主要有三个程序和一些辅助程序。 二、各程序介绍 主要的程序为geogrid.exe、ungrib.exe、met…

一篇文章带你了解低保真与高保真原型图区别

在原型设计中&#xff0c;低保真原型和高保真原型的区别主要体现在细节和功能上。那么我们在设计原型的时候是用低保真原型还是高保真原型呢&#xff1f;两者最好的使用场景是什么&#xff1f;对此&#xff0c;即时设计做了相应的分析。让我们来看看&#xff01; 1、低保真原型…

[蓝桥杯]-最大的通过数-CPP-二分查找、前缀和

目录 一、题目描述&#xff1a; 二、整体思路&#xff1a; 三、代码&#xff1a; 一、题目描述&#xff1a; 二、整体思路&#xff1a; 首先要知道不是他们同时选择序号一样的关卡通关&#xff0c;而是两人同时进行两个入口闯关。就是说两条通道存在相同关卡编号的的关卡被通…

3冠达成!原知因制药荣获2024年度JMR消费者评选三大No.1

近日&#xff0c;日本调查机构&#xff08;JMR&#xff09;公布了2024年度公众对品牌形象的调查结果&#xff0c;除了人们熟知的资生堂、CPB、POLA、小林制药、DHC等品牌依然获得不错的口碑外&#xff0c;日本高端健康品牌原知因制药凭借优秀的产品实力及良好的社会口碑&#x…

Python新手必学:输入输出全攻略

初学Python编程时&#xff0c;其中一个最基础、也是最重要的部分就是如何处理输入和输出。无论是简单的用户输入&#xff0c;还是程序输出结果&#xff0c;都很重要。现在&#xff0c;我们将一起学习Python输入输出的各种技巧和方法&#xff0c;让你在编写代码时得心应手。 目…

HTML5:七天学会基础动画网页11

CSS3动画 CSS3过渡的基本用法: CSS3过渡是元素从一种样式逐渐改变为另一种样式的效果。 过渡属性-transition 值与说明 transition-property 必需&#xff0c;指定CSS属性的name&#xff0c;transition效果即哪个属性发生过渡。 transition-duration 必需&#xff0c;t…

YOLOv9改进策略:注意力机制 |通道注意力和空间注意力CBAM | GAM超越CBAM,不计成本提高精度

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a;通道注意力和空间注意力CBAM&#xff0c;全新注意力GAM&#xff1a;超越CBAM&#xff0c;不计成本提高精度 改进结构图如下&#xff1a; YOLOv9魔术师专栏 ☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️…

LeetCode2115. 从给定原材料中找到所有可以做出的菜

拓扑排序 题面 题目链接&#xff1a;2115. 从给定原材料中找到所有可以做出的菜 - 力扣&#xff08;LeetCode&#xff09; 你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。第 i 道菜的名字为 recipes[i] &#xff0c;如果你有它 所有…

openstack(T)启动实例状态为错误,如何解决

---基本服务得是正常的 ---1.在web界面看是什么错误 点击你的实例名称&#xff0c;在概况里面去查看 当时我的error &#xff1a;编码500 消息 No valid host was found. 错误原因 1&#xff1a;资源不足 2&#xff1a;未开启虚拟机cpu虚拟化 解决&#xff1a; 1.资源不…

Element-Plus: Select组件实现滚动分页加载

Element-Plus的select组件并没有自带滚动分页加载的功能&#xff0c;其虽然提供了自定义下拉菜单的底部的方式可以自定义上一页及下一页操作按钮的方式进行分页加载切换&#xff1a; 但如果不想通过点击分页按钮的方式&#xff0c;利用滚动触底进行下一页加载的话&#xff0c;…

Arrays对象数组排序 --java学习笔记

假设有四个学生对象&#xff1a; name:"小明",height:168.8,age:22 name:"小红",height:178.5,age:20 name:"小智",height:160.1,age:21 name:"小白",height:230.6,age:22 现在需要按身高对他们进行排序 对象进行排序&#xff0c;默…

19.ADC模数转换器知识点+AD单通道AD多通道应用程序示例

0. 江协科技/江科大-STM32标准库开发-各章节详细笔记-查阅传送门_江协科技stm32笔记-CSDN博客文章浏览阅读2.9k次&#xff0c;点赞44次&#xff0c;收藏128次。江协科技/江科大-STM32标准库开发-各章节详细笔记-传送门至各个章节笔记。基本上课程讲的每句都详细记录&#xff0c…

修改 MySQL update_time 默认值的坑

由于按规范需要对 update_time 字段需要对它做默认值的设置 现在有一个原始的表是这样的 CREATE TABLE test_up (id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 主键id,update_time datetime default null COMMENT 操作时间,PRIMARY KEY (id) ) ENGINEInnoDB DEF…

xss——pdfxss,mxss,uxss,flashxss

uxss&#xff08;但是需要很低的版本才可以使用&#xff09;&#xff08;但是csdn也会有过滤&#xff09; 浏览器插件漏洞 搜索这个跨站语句&#xff0c;然后用翻译功能&#xff0c;它会触发跨站、 测试的时候可以去不断的测试它的所有功能&#xff0c;看也没有触发跨站 flas…

C语言笔记:控制流

ACM金牌带你零基础直达C语言精通-课程资料 本笔记属于船说系列课程之一&#xff0c;课程链接&#xff1a;ACM金牌带你零基础直达C语言精通 你也可以选择购买『船说系列课程-年度会员』产品『船票』&#xff0c;畅享一年内无限制学习已上线的所有船说系列课程&#xff1a;船票购…

1335:【例2-4】连通块

【算法分析】 设数组vis&#xff0c;vis[i][j]表示(i,j)位置已经访问过。遍历地图中的每个位置&#xff0c;尝试从每个位置开始进行搜索。如果该位置不是0且没有访问过&#xff0c;那么访问该位置&#xff0c;并尝试从其上下左右四个位置开始搜索。在看一个新的位置时&#xff…