Spring事务失效常见的五种方式及其解决方案【源码篇】

news2024/12/26 13:17:47

一、前言

在Web 开发中,Spring 框架已经成为了众多开发者的首选。Spring 的声明式事务管理是其中最重要的特性之一,它可以帮助我们简化业务逻辑的复杂度,并且确保在出现异常情况时数据的一致性。

事务失效情况很常见,但我们只要注意,就可以避免事情发生!在本文中,我将详细地介绍 Spring 声明式事务的源码实现和事务失效常见的五种情况,并给出有效的解决方案。

其实我们常说的事务失效是声明式事务(@Transactional)的失效,本文也是从声明式事务来进行演示的!

通过本文的学习,你将掌握如何正确地使用 Spring 的事务管理,减少生产事故。

一定要保持数据一致性

二、@Transactional注解参数解读

我们拿出几个经常使用的参数来简单介绍一下:

  • propagation:指定事务的传播行为。其取值包括 REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER 和 NESTED 等。默认为 REQUIRED
    其中,REQUIRED 表示如果当前已经存在一个事务,则加入该事务,否则新建一个事务;而 REQUIRES_NEW 表示新建一个独立的事务,如果当前已经存在事务,则挂起当前事务。后面就不一一说了,大家可以自行百度哈!
  • isolation:指定事务的隔离级别。其取值包括 DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE 等。默认为 DEFAULT。
    其中,DEFAULT 表示采用数据库的默认隔离级别.
  • timeout:指定事务的超时时间,单位为秒。默认为 -1,表示不设置超时时间。如果在规定时间内事务还未完成,则抛出 TransactionTimedOutException 异常。
  • readOnly:指定事务是否只读,即是否允许修改数据。默认为 false,表示可以进行数据修改操作。如果将其设置为 true,则表示该事务仅能进行数据查询操作,不能进行数据修改操作,这样可以提高并发性能。
  • rollbackFor:指定哪些异常需要回滚事务。其取值为一个 Class 数组,其中每个元素表示一个异常类型。默认为空,表示只有抛出 RuntimeException 或 Error 类型的异常时才回滚事务。
  • noRollbackFor:指定哪些异常不需要回滚事务。其取值为一个 Class 数组,其中每个元素表示一个异常类型。默认为空,表示抛出任何异常都回滚事务。

三、声明式事务源码实现

声明式事务实现类为:TransactionInterceptor ,下面我们来一起看看这个类!

源码版本为Springboot2.7.1

public class TransactionInterceptor extends TransactionAspectSupport 
	implements MethodInterceptor, Serializable{}

TransactionInterceptor UML图:

在这里插入图片描述

声明式事务主要是通过AOP实现,主要包括以下几个节点:

  1. 启动时扫描@Transactional注解:在启动时,Spring Boot会扫描所有使用了@Transactional注解的方法,并将其封装成TransactionAnnotationParser对象。

  2. AOP 来实现事务管理的核心类依然是 TransactionInterceptor。TransactionInterceptor 是一个拦截器,用于拦截使用了 @Transactional 注解的方法

  3. 将TransactionInterceptor织入到目标方法中:在AOP编程中,使用AspectJ编写切面类,通过@Around注解将TransactionInterceptor织入到目标方法中

  4. 在目标方法执行前创建事务:在目标方法执行前,TransactionInterceptor会调用PlatformTransactionManager创建一个新的事务,并将其纳入到当前线程的事务上下文中。

  5. 执行目标方法:在目标方法执行时,如果发生异常,则将事务状态标记为ROLLBACK_ONLY;否则,将事务状态标记为COMMIT

  6. 提交或回滚事务:在目标方法执行完成后,TransactionInterceptor会根据事务状态(COMMIT或ROLLBACK_ONLY)来决定是否提交或回滚事务。

源码:

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
	// Work out the target class: may be {@code null}.
	// The TransactionAttributeSource should be passed the target class
	// as well as the method, which may be from an interface.
	Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

	// Adapt to TransactionAspectSupport's invokeWithinTransaction...
	return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
		@Override
		@Nullable
		public Object proceedWithInvocation() throws Throwable {
			return invocation.proceed();
		}
		@Override
		public Object getTarget() {
			return invocation.getThis();
		}
		@Override
		public Object[] getArguments() {
			return invocation.getArguments();
		}
	});
}

下面是核心处理方法,把不太重要的代码忽略了,留下每一步的节点。

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
	final InvocationCallback invocation) throws Throwable {
	// 获取事务属性
	final TransactionManager tm = determineTransactionManager(txAttr);
	// 准备事务
	TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
	// 执行目标方法
	Object retVal = invocation.proceedWithInvocation();
	 // 回滚事务
	completeTransactionAfterThrowing(txInfo, ex);
	// 提交事务
	commitTransactionAfterReturning(txInfo);
}		

如果还想看编程式事务实现的源码的可以看一下小编的这篇文章:

Spring的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战

四、五种失效和解决方案

在这里插入图片描述

下面我们从几个情况来给大家展示失效场景并给出解决方案

1. 类没有被 Spring 管理

public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        userDao.addUser(user);
    }
}

如上代码所示,UserServiceImpl 类没有被声明为 Spring Bean,因此其中的 addUser() 方法无法受到 Spring 事务管理的保护。
我们使用Spring,要把类交给Spring进行管理,不然是无法生效!

解决方案: 交给spring进行管理bean,在类上添加:@Service

2. 方法不是public修饰

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional(rollbackFor = Exception.class)
    protected void addUser(User user) {
        userDao.addUser(user);
    }
}

我们上面说了声明式事务是基于AOP实现的,AOP是通过代理模式实现的,即为目标对象生成一个代理对象,当调用代理对象的方法时,会自动添加事务的控制代码。
在这种情况下,如果事务注释所在的方法不是public的,则无法生成代理对象,因此事务代码将无法添加到方法执行前后,导致事务失效。

其实这种情况还是不经常这么使用,我们基本都是使用接口和实现大部分都是public修饰的!

解决方案: 使用public来修饰方法

3. 异常被捕获并处理了

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addUser(User user) {
        try {
            userDao.addUser(user);
        } catch (Exception e) {
            // 处理异常,但没有抛出或重新抛出异常
            log.error("add user error", e);
        }
    }
}

如上代码所示,如果 userDao.addUser() 方法抛出异常,但是在 UserServiceImpl.addUser() 中被捕获并处理了,事务检测不到有异常抛出,那么事务不会回滚。

解决方案: catch 处理完成后,在重新把异常在抛出去:throw e;

4. 同一个类中,方法内部调用

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void addUser(User user) {
        doAddUser(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        userDao.addUser(user);
    }
}

Spring使用代理来实现事务控制,但是这种方法直接调用了this对象的方法,则无法通过代理来拦截该方法调用,从而使得事务失效。

解决方案:

推荐使用有两种:

  • 使用ApplicationContext来获取当前bean对象来调用doAddUser方法
  • addUser方法加上@Transactional(rollbackFor = Exception.class)

网上还有一些使用AopContext.currentProxy()拿到代理对象的、自己注入自己的、抽到单独的bean里的
这里小编不是很推荐!

方法一完整展示:

如果觉得Service里注入ApplicationContext 不优雅,可以抽到单独的工具bean里!

@Service
public class UserServiceImpl implements UserService {

	@Autowired
    private UserDao userDao;
    @Autowired
	private ApplicationContext applicationContext;

    @Override
    public void addUser(User user) {
    	UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class);
        userService.doAddUser(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doAddUser(User user) {
        userDao.addUser(user);
    }
}

5. MySQL存储引警不支持事务

MyISAM 存储引擎是 MySQL 的一种存储引擎,它是 MySQL 5.1 版本之前的默认存储引擎,它是不支持事务的。从 MySQL 5.5 版本开始,InnoDB 成为了 MySQL 的默认存储引擎。我们想使用也可以切换到MyISAM引擎。

解决方案: 把mysql换到5.5以上使用InnoDB 存储引擎

补充使用MyISAM 方式:

  • 表从 InnoDB 引擎转换为 MyISAM 引擎:

    使用 ALTER TABLE 命令来更改表的引擎类型

ALTER TABLE table_name ENGINE = MyISAM;
  • 默认的存储引擎设置为 MyISAM,
    可以在 MySQL 配置文件中设置 default-storage-engine 参数。
default-storage-engine=MyISAM
  • 创建表时指定MyISAM 引擎
    要将表的引擎类型设置为 MyISAM,请在 CREATE TABLE 语句中包含 ENGINE = MyISAM 子句
CREATE TABLE table_name (
    column1 datatype,
    column2 datatype,
    ...
) ENGINE = MyISAM;

五、总结

本文总结了Spring 声明式事务的源码实现、五种常见的事务失效情况,并提供了相应的解决方案。

当然还有很多情况:被final修饰、多线程调用、传播行为使用不当、抛的异常不对应等等

理解 Spring 事务机制的,深入了解 Spring 事务的内部原理。同时,在使用声明式事务的过程中,我们也可以针对自己的业务场景进行定制化的配置,比如指定特定的事务传播机制、设置超时时间等,这些都有助于更好地应对复杂的业务场景和代码需求。这样才能真正地提高系统的可维护性、可扩展性和稳定性。

当然还有一些大事务,这样就需要编程式事务出厂了!
想了解编程式事务的可以看一下小编的这篇文章:

Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战


如果对你有帮助,还请动一下您的发财小手,关注一下公众号哈!!谢谢您的关注!!文章首发看!!!

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

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

相关文章

对计算机方向科研工作者的建议 - 易智编译EaseEditing

如果你正在进行计算机科学研究&#xff0c;以下是一些建议可以帮助你&#xff1a; 确定研究兴趣&#xff1a; 选择一个你感兴趣的研究领域或问题&#xff0c;并确保你对该领域有足够的知识基础和热情。 深入学习&#xff1a; 通过阅读学术论文、参加研讨会和课程&#xff0c;…

有意思的CVE-2022-0337复现

前言 前两天在刷tw&#xff0c;看到了个比较有意思的一个CVE漏洞&#xff0c;价值奖励是10000美&#x1f52a;&#xff0c;比较好奇的是价值10000美&#x1f52a;的漏洞是什么样子的[苦涩]&#xff0c;漏洞利用就是需要在浏览器中进行用户交互才能触发该漏洞&#xff0c;但由于…

vr船舶装配模拟驾驶平台直观形象呈现操作流程和原理

船舶由成千上万种零件构成&#xff0c;需要众多的工业部门共同合作才能够完成这一庞大工程。除特有的船体建造技术外&#xff0c;造船还涉及到机械、电气、冶金、建筑、化学以至工艺美术等各个领域&#xff0c;因此船舶建造是一项劳动密集型、技术密集型和资金密集型的产业。 船…

【OpenAI】DALL·E 2,让我来带你认识一下这位来自AI界的艺术家

个人主页&#xff1a;【&#x1f60a;个人主页】 文章目录 前言什么是DALL-E 2 &#xff1f;介绍的怎么厉害&#xff0c;它又能干啥呢&#xff1f;基本功能新功能编辑变体功能 总结 前言 DALL-E 2 是一种基于语言的人工智能图像生成器&#xff0c;可以根据文本提示创建高质量的…

【学习日记2023.5.22】 之 套餐模块完善

4. 功能模块完善之套餐模块 4.1 新增套餐 4.1.1 需求分析与设计 产品原型 后台系统中可以管理套餐信息&#xff0c;通过 新增功能来添加一个新的套餐&#xff0c;在添加套餐时需要添加套餐对应菜品的信息&#xff0c;并且需要上传套餐图片。 新增套餐原型&#xff1a; 当填…

PMP-项目经理的角色

一、项目经理的作用 项目经理在领导团队达成项目目标方面发挥着至关重要的作用。一般来说&#xff0c;项目经理从项目启动时就开始参与项目直至项目结束&#xff0c;在这个过程中&#xff0c;项目经理发挥着对项目的规划、协调、控制等作用&#xff0c;确保与产品保持一致&…

用 Python 进行办公自动化都需要学习什么知识呢?

Python 自动化办公&#xff0c;无外乎就是 excel&#xff0c;ppt&#xff0c;word&#xff0c;再加上数据分析、爬虫等技能 下面我就来逐一介绍各项技能的基本使用 基本知识 1. Python基础知识&#xff1a; 包括语法、变量、数据类型、条件语句、循环语句、函数等基本概念和用…

代码随想录训练营Day48|● 198.打家劫舍 ● 213.打家劫舍II ● 337.打家劫舍III

目录 学习目标 学习内容 198.打家劫舍 213.打家劫舍II 337.打家劫舍III 学习目标 198.打家劫舍 213.打家劫舍II 337.打家劫舍III 学习内容 198.打家劫舍 198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/house-robber/ class Soluti…

Hexo博客查图片并调整位置大小

Hexo博客查图片并调整位置&大小 &#x1f308;Description&#xff1a; ​ Markdown写文章插入图片&#xff0c;在hexo博客中&#xff0c;图片的大小似乎没有按照个markdown的语法生效&#xff0c;本文将解决此问题。 现有问题描述 obsidian中插入图片的语法&#xff1a; !…

当四款AI大模型遇上考公真题,谁被难倒了?

在当今社会&#xff0c;人工智能&#xff08;AI&#xff09;正以不可思议的速度发展&#xff0c;并在各个领域崭露头角&#xff0c;给人们的生活和工作带来许多便利。AI大模型被誉为人类“第二大脑”&#xff0c;成为人们学习、生活、工作的 “智能助手”。 公务员考试在我国教…

firewalld防火墙

firewalld防火墙 1&#xff1a;firewalld概述 firewalld防火墙是Centos7系统默认的防火墙管理工具&#xff0c;取代了之前的iptables防火墙&#xff0c;也是工作在网络层&#xff0c;属于包过滤防火墙。firewalld和iptables都是用来管理防火墙的工具&#xff08;属于用户态&a…

如何使用ChatGPT对论文进行润色

本文提供两种基于chatGPT的润色方式&#xff1a; &#xff08;1&#xff09;在chatGPT中利用editGPT插件润色 &#xff08;2&#xff09;chatGPT对话框引导chatGPT按照具体的意见进行润色。 1. 安装editGPT插件 问&#xff1a;为什么安装 editGPT&#xff1f; 答&#xff1a;…

STM32手柄PS2

PS2手柄介绍 PS2手柄由手柄与接收器两部分组成&#xff0c;手柄主要负责发送按键信息&#xff1b;接收器与单片机&#xff08;也可叫做主机&#xff09;相连&#xff0c;用于接收手柄发来的信息&#xff0c;并传递给单片机&#xff0c;单片机也可通过接收器&#xff0c;向手柄…

提示词工程师入门 百度文心Prompt课之十大技巧(适用所有AI大模型)

Promot知识 大模型基本原理 给模型输入什么数据&#xff0c;模型就会尝试学习什么内容Prompt十个技巧 三大类 迭代法 1、定基础 优先保证任务生成主体能够生成出我们想要的内容细节形式 在给出任务生成主体的情况下&#xff0c;模型生成效果较差&#xff0c;可增加细节词也无济…

百度API实现logo商标识别接口介绍

作者介绍 严松&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;机器人抓取检测 电子邮件&#xff1a;2448052777qq.com 王泽宇&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生&#xff0c;张…

建设一站式DevOps平台,腾讯云研发效能提升实践

本文作者&#xff1a;张渝 导语 | 近年来&#xff0c;研发效能提升越来越受到业界重视&#xff0c;许多厂商都在不断探索研发效能提升之路&#xff0c;从而实现研发效率和质量的持续优化&#xff0c;以应对日趋复杂的产品开发。那么腾讯云的研发效能相关工作是如何开展和落地的…

【遥感图像】目标检测系列.1

目录 Unsupervised Domain Adaptation for Cloud Detection Based on Grouped Features Alignment and Entropy Minimization, TGRS2022 Semi-Supervised Cloud Detection in Satellite Images by Considering the Domain Shift Problem, RS2022 CoF-Net: A Progressive Coa…

深度学习笔记之递归网络(四)铺垫:Softmax函数的反向传播过程

深度学习笔记之递归网络——铺垫&#xff1a;Softmax的反向传播过程 引言总结&#xff1a;递归神经网络的前馈计算过程场景构建前馈计算描述 铺垫&#xff1a; Softmax \text{Softmax} Softmax的反向传播过程场景构建 Softmax \text{Softmax} Softmax反向传播过程 引言 上一节…

OpenSIPS 3.1 负载均衡 MRCP 服务器的实现

文章目录 1. 方案设计2. 实现方式2.1 FreeSWITCH 的配置2.2 OpenSIPS 3.1 的配置2.2.1 OpenSIPS 保存 MRCP 服务器地址2.2.2 OpenSIPS 脚本开发 2.3 实现效果 1. 方案设计 FreeSWITCH 通过 unimrcp 模块来对接 MRCP 服务器&#xff0c;该模块在启动时会根据 mrcp profile 配置…

【Java|golang】1080. 根到叶路径上的不足节点--dfs

给你二叉树的根节点 root 和一个整数 limit &#xff0c;请你同时删除树中所有 不足节点 &#xff0c;并返回最终二叉树的根节点。 假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit&#xff0c;则该节点被称之为 不足节点 &#xff0c;需要被删…