10 种 Spring事务失效场景
10 种 Spring事务失效场景
1.概述
Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional
注解,即可一键开启方法的事务性配置。
但是,事务如果没有被正确出,很有可能会导致事务的失效,带来意想不到的数据不一致问题,随后就是大量的人工接入查看和修复数据,该篇主要分享Spring事务在技术上的正确使用方式,避免因为事务处理不当导致业务逻辑产生大量偶发性BUG。
在分析事务失效的常见场景之前,我们先来了解一下:事务的传播类型 和 @Transactionnal
注解的不同属性的含义。
2.事务的传播类型
//如果有事务, 那么加入事务, 没有的话新建一个(默认)
@Transactional(propagation=Propagation.REQUIRED)
//容器不为这个方法开启事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
//不管是否存在事务, 都创建一个新的事务, 原来的挂起, 新的执行完毕, 继续执行老的事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
//必须在一个已有的事务中执行, 否则抛出异常
@Transactional(propagation=Propagation.MANDATORY)
//必须在一个没有的事务中执行, 否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
//如果其他bean调用这个方法, 在其他bean中声明事务, 那就用事务, 如果其他bean没有声明事务, 那就不用事务
@Transactional(propagation=Propagation.SUPPORTS)
3.isolation
该属性用于设置底层数据库的事务隔离级别,事务的隔离级别介绍:
// 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// 读取已提交数据(会出现不可重复读和幻读) Oracle默认
@Transactional(isolation = Isolation.READ_COMMITTED)
// 可重复读(会出现幻读) MySQL默认
@Transactional(isolation = Isolation.REPEATABLE_READ)
// 串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
4.@Transactionnal注解属性
@Transactional注解可以作用于接口、接口方法、类以及类方法上,它可以通过不同的参数来选择什么类型Exception异常下执行回滚或者不回滚操作。
5.Spring事务失效的场景
1. 事务方法未被Spring管理
如果事务方法所在的类没有注册到Spring IOC容器中,也就是说,事务方法所在类并没有被Spring管理,则Spring事务会失效,举个例子🌰:
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {@Autowiredprivate ProductMapper productMapper;@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void updateProductStockById(Integer stockCount, Long productId) {productMapper.updateProductStockById(stockCount, productId);}
}
ProductServiceImpl
实现类上没有添加@Service
注解,Product的实例也就没有被加载到Spring IOC容器,此时updateProductStockById()
方法的事务就会在Spring中失效。
2. 方法使用final类型修饰
有时候,某个方法不想被子类重写,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:
@Service
public class OrderServiceImpl {@Transactionalpublic final void cancel(OrderDTO orderDTO) {// 取消订单cancelOrder(orderDTO);}
}
OrderServiceImpl
的cancel取消订单方法被final修饰符修饰,Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。
3. 非public修饰的方法
如果事务方式不是public修饰,此时Spring事务会失效,举个例子🌰:
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {@Autowiredprivate ProductMapper productMapper;@Override@Transactional(propagation = Propagation.REQUIRES_NEW)private void updateProductStockById(Integer stockCount, String productId) {productMapper.updateProductStockById(stockCount, productId);}
}
虽然ProductServiceImpl
添加了@Service
注解,同时updateProductStockById()
方法上添加了@Transactional(propagation = Propagation.REQUIRES_NEW)
注解,但是由于事务方法updateProductStockById()
被 private 定义为方法内私有,同样Spring事务会失效。
4. 同一个类中的方法相互调用
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductMapper productMapper;@Overridepublic ResponseEntity submitOrder(Order order) {// 保存生成订单信息long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));order.setOrderNo("ORDER_" + orderNo);orderMapper.insert(order);// 扣减库存this.updateProductStockById(order.getProductId(), 1L);return new ResponseEntity(HttpStatus.OK);}@Transactional(propagation = Propagation.REQUIRES_NEW)public void updateProductStockById(Integer num, Long productId) {productMapper.updateProductStockById(num, productId);}
}
submitOrder()
方法和updateProductStockById()
方法都在OrderService类中,然而submitOrder()
方法没有添加事务注解,updateProductStockById()
方法虽然添加了事务注解,这种情况updateProductStockById()
会在Spring事务中失效。
5. 方法的事务传播类型不支持事务
如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务同样会在Spring中失效,举个例子:
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductMapper productMapper;@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public ResponseEntity submitOrder(Order order) {long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));order.setOrderNo("ORDER_" + orderNo);orderMapper.insert(order);// 扣减库存this.updateProductStockById(order.getProductId(), 1L);return new ResponseEntity(HttpStatus.OK);}/*** 扣减库存方法事务类型声明为NOT_SUPPORTED不支持事务的传播*/@Transactional(propagation = Propagation.NOT_SUPPORTED)public void updateProductStockById(Integer num, Long productId) {productMapper.updateProductStockById(num, productId);}
}
6.异常被内部catch,程序生吞异常
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductMapper productMapper;@Override@Transactionalpublic ResponseEntity submitOrder(Order order) {long orderNo = Math.abs(ThreadLocalRandom.current().nextLong(1000));order.setOrderNo("ORDER_" + orderNo);orderMapper.insert(order);// 扣减库存this.updateProductStockById(order.getProductId(), 1L);return new ResponseEntity(HttpStatus.OK);}/*** 扣减库存方法事务类型声明为NOT_SUPPORTED不支持事务的传播*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void updateProductStockById(Integer num, Long productId) {try {productMapper.updateProductStockById(num, productId);} catch (Exception e) {// 这里仅仅是捕获异常之后的打印(相当于程序吞掉了异常)log.error("Error updating product Stock: {}", e);}}
}
7. 数据库不支持事务
Spring事务生效的前提是连接的数据库支持事务,如果底层的数据库都不支持事务,则Spring事务肯定会失效的,例如🌰:使用MySQL数据库,选用MyISAM
存储引擎,因为MyISAM
存储引擎本身不支持事务,因此事务毫无疑问会失效。
8. 未配置开启事务
如果项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会生效,例如,如果你是Spring Boot项目,没有在SpringBoot项目中配置如下代码:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}
如果是以往的Spring MVC项目,如果没有配置下面的代码,Spring事务也不会生效,正常需要在applicationContext.xml文件中,手动配置事务相关参数,比如:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><!--配置要扫描的包--><context:component-scan base-package="com.llp.spring.tx.dao"/><context:component-scan base-package="com.llp.spring.tx.service"/><!--引入外部的jdbc.properties文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--配置数据源对象-DataSoruce--><bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource"><!--给数据源对象配置属性值--><property name="user" value="${jdbc.user}"/><property name="password" value="${jdbc.pwd}"/><property name="driverClass" value="${jdbc.driver}"/><property name="jdbcUrl" value="${jdbc.url}"/></bean><!--配置JdbcTemplate对象--><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"><!--给JdbcTemplate对象配置dataSource--><property name="dataSource" ref="dataSource"/></bean><!--配置事务管理器-对象1. DataSourceTransactionManager 这个对象是进行事务管理-debug源码2. 一定要配置数据源属性,这样指定该事务管理器 是对哪个数据源进行事务控制--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"><property name="dataSource" ref="dataSource"/></bean><!--配置启动基于注解的声明式事务管理功能--><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
9. 错误的传播特性
其实,我们在使用@Transactional
注解时,是可以指定propagation
参数的。
该参数的作用是指定事务的传播特性,目前Spring支持7种传播特性:
REQUIRED
如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。SUPPORTS
如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。MANDATORY
如果当前上下文中存在事务,否则抛出异常。REQUIRES_NEW
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。NOT_SUPPORTED
如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。NEVER
如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。NESTED
如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
如果我们在手动设置propagation
参数的时候,把传播特性设置错了,比如:
@Service
public class OrderServiceImpl {@Transactional(propagation = Propagation.NEVER)public void cancelOrder(UserModel userModel) {// 取消订单cancelOrder(orderDTO);// 还原库存restoreProductStock(orderDTO.getProductId(), orderDTO.getProductCount());}
}
我们可以看到cancelOrder()
方法的事务传播特性定义成了Propagation.NEVER
,这种类型的传播特性不支持事务,如果有事务则会抛异常。
10. 多线程调用
在实际项目开发中,多线程的使用场景还是挺多的。如果Spring事务用在多线程场景中使用不当,也会导致事务无法生效。
@Slf4j
@Service
public class OrderServiceImpl {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate MessageService messageService;@Transactionalpublic void orderCommit(orderModel orderModel) throws Exception {orderMapper.insertOrder(orderModel);new Thread(() -> {messageService.sendSms();}).start();}
}@Service
public class MessageService {@Transactionalpublic void sendSms() {// 发送短信}
}
通过示例,我们可以看到订单提交的事务方法orderCommit()
中,调用了发送短信的事务方法sendSms()
,但是发送短信的事务方法sendSms()
是另起了一个线程调用的。
这样会导致两个方法不在同一个线程中,从而是两个不同的事务。如果是sendSms()
方法中抛了异常,orderCommit()
方法也回滚是不可能的。
实际上,Spring的事务是通过ThreadLocal来保证线程安全的,事务和当前线程绑定,多个线程自然会让事务失效。
相关文章:

10 种 Spring事务失效场景
10 种 Spring事务失效场景 1.概述 Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动…...

重读《DOOM启世录》
许多游戏开发者都是网瘾少年,抱着对游戏的热爱进入游戏行业,在经历996的加班加点,买房的压力,浮躁同样跟随着我们,我们是否还热爱着自己的事业,我们不是天才,也成不了卡马克,但是我们…...

巧用性格上的差异来组建团队
你好,我是得物 App 交易平台及中间件平台的 Team Leader Alan。 组建团队过程中,你有没有遇到过类似的场景:团队中某些人之间总是互相不对付、气场不合,不管是日常沟通中还是方案对齐,总是会出现面红耳赤的场面。 从…...

Leetcode11. 盛最多水的容器
一、题目描述: 给定一个长度为 nnn 的整数数组 heightheightheight 。有 nnn 条垂线,第 iii 条线的两个端点是 (i,0)(i, 0)(i,0) 和 (i,height[i])(i, height[i])(i,height[i]) 。 找出其中的两条线,使得它们与 xxx 轴共同构成的容器可以容…...

Java笔记026-集合/数组、Collection接口、ArrayList、Vector、LinkedList
集合集合的理解和好处保存多个数据使用的是数组,分析数组的弊端数组1、长度开始必须指定,而且一旦指定,不能更改2、保存的必须为同一类型的元素3、使用数组进行增加/删除元素的示意代码-比较麻烦Person数组扩容示意代码Person[] pers new Pe…...

Hive学习——分桶抽样、侧视图与炸裂函数搭配、hive实现WordCount
目录 一、分桶抽样 1.抽取表中10%的数据 2.抽取表中30%的数据 3.取第一行 4.取第10行 5.数据块抽样 6.tablesample详解 二、UDTF——表生成函数 1.explode()——炸裂函数 2.posexpolde()——只能对array进行炸裂 3.inline()——炸裂结构体数组 三、UDTF与侧视图的搭…...

大数据算法
1. TOP K 算法 有10个⽂件,每个⽂件1G,每个⽂件的每⼀⾏存放的都是⽤户的 query,每个⽂件的 query 都可能重复。要求你按照 query 的频度排序。 方法1: 顺序读取10个⽂件,按照 hash(query)%10 的结果将 query 写⼊到…...

非暴力沟通读书笔记
浅读《非暴力沟通》,本书对于沟通的方式总结成了一个方法论,从13个章节去概述非暴力沟通的方法和重点。其中最重要的是非暴力沟通四要素,观察、感受、需要、请求。同时在沟通中注意观察,投入爱,重视倾听的力量…...

代码随想录【Day21】| 530. 二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236. 二叉树的最近公共祖先
530. 二叉搜索树的最小绝对差 题目链接 题目描述: 给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。 示例: 提示:树中至少有 2 个节点。 难点: 解答错误!仅考虑了…...

注意啦,面试通过后,别忘了教师资格证认定
所有要「教师资格证认定」教程的宝子们看过来面试合格的小伙伴都可以进行认定工作 . 认定时间 查询各省份认定公告,确定认定时间范围。以下是公告汇总网址(https://www.jszg.edu.cn/portal/qualification_cert/dynamics?id21691) 认定次数 每…...

【LeetCode】No.154. 寻找旋转排序数组中的最小值 II -- Java Version
题目链接:https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/ 1. 题目介绍(154. 寻找旋转排序数组中的最小值 II) 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后࿰…...

RestTemplate远程调用
我们现在项目中使用的RPC远程调用技术是Dubbo实际上除了Dubbo技术之外,还有很多远程调用的方法它们有些调用的思想都和Dubbo完全不同Dubbo是SpringCloudAlibaba提供的功能强大的RPC框架但是Dubbo功能也有限制,如果我们想调用的方法不是我们当前项目的组件或功能,甚至想调用的方…...

registerForActivityResult使用
目录 针对 activity 结果注册回调 启动 activity 以获取其结果 在单独的类中接收 activity 结果 测试 创建自定义协定 registerForActivityResult()是startActivityForResult()的替代,简化了数据回调的写法 启动另一个 activity&#x…...

工作中,python真的有用吗?
普通上班族学Python有用吗? 那么,我也在这里提出一个问题:Python究竟适不适合办公人士来学习,以及学了之后究竟能不能给我的工作来带质一般的飞跃? 以我的亲身经历为例,我可以很负责的告诉大家,…...

固态继电器控制电路
固态继电器控制电路 固态继电器(SSR)的种类和型号很多,因此其输入控制方法和控制电路也相应众多。固态继电器(SSR)的共同特点在于驱动电流或驱动电压小,即只需输入一个小信号即可控制SSR的开关。 如果需要…...

数仓、数据湖、湖仓一体、数据网格的探索与研究
第一代:数据仓库 定义 为解决数据库面对数据分析的不足,孕育出新一类产品数据仓库。数据仓库(Data Warehouse)是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策和信息的全局共享。 数…...

设计模式系列 - 备忘录模式
介绍&定义 备忘录模式,也叫快照(Snapshot)模式,英文翻译是 Memento Design Pattern。在 GoF 的《设计模式》一书中,备忘录模式是这么定义的: Captures and externalizes an object’s internal state…...

详细介绍React生命周期和diffing算法
事件处理 1.通过onXxx属性指定事件处理函数(注意大小写) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —— 为了更好的兼容性;React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ——为了的高效。 2.通过event.target得到发生事件的DOM…...

面向对象的特点
1、什么是对象对象的含义是指具体的某一个事物,即在现实生活中能够看得见摸得着的事物。在面向对象程序设计中,对象所指的是计算机系统中的某一个成分。在面向对象程序设计中,对象包含两个含义,其中一个是数据,另外一个…...

智慧校园平台源码 智慧教务 智慧电子班牌系统
系统介绍 智慧校园系统是通过信息化手段,实现对校园内各类资源的有效集成 整合和优化,实现资源的有效配置和充分利用,将校务管理过程的优化协调。为校园提供数字化教学、数字化学习、数字化科研和数字化管理。 致力于为家长和教师提供一个全方位、多层…...

Vue篇.03-组合式API [setup()]
单文件组件(1)<script setup><script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐启用该语法,需要在 <script> 代码块上添加 setup attribute, 里面的代码会被编译成组件 s…...

QHashIterator-官翻
QHashIterator Class template <typename Key, typename T> class QHashIterator QHashIterator 类为 QHash 和 QMultiHash 提供 Java 风格的常量迭代器。更多内容… 头文件:#include qmake:QT core 所有成员列表,包括继承的成员废弃的成员 公共成员函数…...

[qiankun]-部署后线上问题
[qiankun]-部署后线上问题微服务加载问题-现象1现象描述问题分析解决方案微服务加载问题-现象2现象描述问题分析微服务加载问题-现象3现象描述分析解决方案属于项目打包后,部署到服务器上,所遇到的部分问题 微服务加载问题-现象1 现象描述 项目部署实…...

位图数组 布隆过滤器
文章目录位图数组获取索引获取索引状态设置索引状态布隆过滤器特点大致原理位图数组 一个int类型的整数用4字节,也就是32个bit位来表示,将整数类型的数组转换成位图数组,那么存储长度将变为原来的32倍 arr[0] 表示0-31 arr[1] 表示32-63 //...获取索引…...

多线程Thread常用方法和状态
Thread类 及常见方法 1、常见构造方法 方法说明Thread()创建线程对象Thread(Runnable target)使用 Runnable 对象创建线程对象Thread(String name)创建线程对象,并命名Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名Thre…...

Codeforces Round #836 (Div. 2)
A SSeeeeiinngg DDoouubbllee 题意:告诉你一个字符串。若该串上每一位上的字母都可以出现两次,求回文串 思路:正向再反向输出s即可 #include <bits/stdc.h> #define lowbit(x) x&(-x) #define ios cin.sync_with_stdio(false)…...

Python学习之项目实践: 写一个MP3播放器
下面呢,是一个 Python MP3 播放器,它使用 pygame 模块来实现音乐播放功能: import pygame class MP3Player: """ MP3 播放器类 """ def __init__(self): pygame.mixer.init() def play(self, file_path): &quo…...

RocketMQTemplate 实现消息发送
代码托管于gitee:easy-rocketmq 文章目录一、前置工作二、消费者三、生产者1. 普通消息2. 过滤消息3. 同步消息4. 延时消息5. 批量消息6. 异步消息7. 单向消息8. 顺序消息9. 事务消息概要Demo源码解读一、前置工作 1、导入依赖 <dependency><groupId>…...

教师干货丨这5款微课必备提效神器,我要告诉全世界!
微课是一种短小精悍的视频教学形式,其设计和演示因特别简洁明了而被定义为“小而美”。由于只在几分钟时间内向学生传授所需知识,微课为学习者提供更多的选择机会和时间节约的便利,而这种趋势已经逐渐在新的社交媒体环境中显现出来。在制作微…...

timm使用swin-transformer
1.安装 pip install timm2.timm中有多少个预训练模型 #timm中有多少个预训练模型 model_pretrain_list timm.list_models(pretrainedTrue) print(len(model_pretrain_list), model_pretrain_list[:3])3加载swin模型一般准会出错 model_ft timm.create_model(swin_base_pat…...