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

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

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...