Java事务失效

目录

  • 传送门
  • 一、概念
    • 1、事务的传播类型
    • 2、isolation
    • 3、@Transactionnal注解属性
  • 二、事务失效场景
    • 1、异常捕获
    • 2、异步处理
    • 3、final修饰事务方法
    • 4、非public
    • 5、@T范围小了
    • 6、不加@T或者事务传播用了NOT_SUPPORTED这种不支持事务
    • 7、数据库MyISAM不支持事务
    • 8、事务方法未被Spring管理
    • 9、异常类型不匹配(难点)
      • 案例一:
      • 案例二:

传送门

SpringMVC的源码解析(精品)
Spring6的源码解析(精品)
SpringBoot3框架(精品)
MyBatis框架(精品)
MyBatis-Plus
SpringDataJPA
SpringCloudNetflix
SpringCloudAlibaba(精品)
Shiro
SpringSecurity
java的LOG日志框架
Activiti(敬请期待)
JDK8新特性
JDK9新特性
JDK10新特性
JDK11新特性
JDK12新特性
JDK13新特性
JDK14新特性
JDK15新特性
JDK16新特性
JDK17新特性
JDK18新特性
JDK19新特性
JDK20新特性
JDK21新特性
其他技术文章传送门入口

一、概念

Spring针对Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事务 API,实现了一致的编程模型,而Spring的声明式事务功能更是提供了极其方便的事务配置方式,配合Spring Boot的自动配置,大多数Spring Boot项目只需要在方法上标记@Transactional注解,即可一键开启方法的事务性配置。

但是,事务如果没有被正确出,很有可能会导致事务的失效,带来意想不到的数据不一致问题,随后就是大量的人工接入查看和修复数据,该篇主要分享Spring事务在技术上的正确使用方式,避免因为事务处理不当导致业务逻辑产生大量偶发性BUG。

在分析事务失效的常见场景之前,我们先来了解一下:事务的传播类型 和 @Transactionnal 注解的不同属性的含义。

1、事务的传播类型

//如果有事务, 那么加入事务, 没有的话新建一个(默认)
@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)
//如果当前存在事务,则在嵌套事务内执行。嵌套事务可以看作是当前事务的一部分,但可以独立提交或回滚。
@Transactional(propagation = Propagation.NESTED)

2、isolation

该属性用于设置底层数据库的事务隔离级别,事务的隔离级别介绍:
// 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// 读取已提交数据(会出现不可重复读和幻读) Oracle默认
@Transactional(isolation = Isolation.READ_COMMITTED)
// 可重复读(会出现幻读) MySQL默认
@Transactional(isolation = Isolation.REPEATABLE_READ)
// 串行化
@Transactional(isolation = Isolation.SERIALIZABLE)

3、@Transactionnal注解属性

在这里插入图片描述

二、事务失效场景

1、异常捕获

异常被catch掉了,没有触发回滚

@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) {try {productMapper.updateProductStockById(num, productId);} catch (Exception e) {// 这里仅仅是捕获异常之后的打印(相当于程序吞掉了异常)log.error("Error updating product Stock: {}", e);}}
}

2、异步处理

有的也说是多线程处理,本质其实就是个异步,子线程处理逻辑和主线程没有关系了。

@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() {// 发送短信}
}

3、final修饰事务方法

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:

@Service
public class OrderServiceImpl {@Transactionalpublic final void cancel(OrderDTO orderDTO) {// 取消订单cancelOrder(orderDTO);}
}

OrderServiceImpl的cancel取消订单方法被final修饰符修饰,Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。

Tips: 如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。

4、非public

如果事务方式不是public修饰,此时Spring事务会失效,举个例子:

/**
* 商品业务实现层
*
* @author: austin
* @since: 2023/2/10 14:19
*/
@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事务会失效。

5、@T范围小了

A调用B,A方法没有@T,B方法有,则事务失效

@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事务中失效。

6、不加@T或者事务传播用了NOT_SUPPORTED这种不支持事务

@Service
public class OrderServiceImpl {@Transactional(propagation = Propagation.NEVER)public void cancelOrder(UserModel userModel) {// 取消订单cancelOrder(orderDTO);// 还原库存restoreProductStock(orderDTO.getProductId(), orderDTO.getProductCount());}
}

我们可以看到cancelOrder()方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

7、数据库MyISAM不支持事务

Spring事务生效的前提是连接的数据库支持事务,如果底层的数据库都不支持事务,则Spring事务肯定会失效的,例如:使用MySQL数据库,选用MyISAM存储引擎,因为MyISAM存储引擎本身不支持事务,因此事务毫无疑问会失效。

8、事务方法未被Spring管理

如果事务方法所在的类没有注册到Spring IOC容器中,也就是说,事务方法所在类并没有被Spring管理,则Spring事务会失效,举个例子:

/*** 商品业务实现层** @author: austin* @since: 2023/2/10 14:19*/
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中失效。

9、异常类型不匹配(难点)

在这里插入图片描述
从继承关系可知:Throwable是异常体系的根,它继承自Object。Throwable有两个体系:Error和Exception,
Error表示严重的错误,程序对此一般无能为力。
而Exception则是运行时的错误,它可以被捕获并处理。
都是类,Throwable也是类。

Exception又分为两大类:

  • RuntimeException以及它的子类;
  • 非RuntimeException(包括IOException、ReflectiveOperationException等等)

Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception
  • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类,这种异常称为UncheckedException

注意:编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。

案例一:

即使开发者在编写过程中,没有手动抛出异常;但是如果出现的异常不正确,Spring事务也不会回滚。

@Slf4j
@Service
public class UserService{@Transactionalpublic void add(User user) throws Exception {try {saveData(user);updateData(user);} catch(Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}}}

上述这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。因为Spring事务,默认情况下只会回滚RunTimeException,和Error(错误),对于普通的Exception(非运行时异常),它是不会回滚的。

案例二:

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:

@Slf4j
@Service
public class UserService {@Transactional(rollbackFor = BusinessException.class)public void add(User user) throws Exception {saveData(user);updateData(user);}
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。

即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

rollbackFor默认值为UncheckedException,包括了RuntimeException和Error. 当我们直接使用@Transactional不指定rollbackFor时,Exception及其子类都不会触发回滚。

所以,建议一般情况下,将该参数设置成:Exception或Throwable。

@Transactional(rollbackFor = Exception.class)

总结:能触发回滚的是RuntimeException及其子类和Error,不能触发的是非RuntimeException类外加一个特殊的Exception。

在这里插入图片描述

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

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

相关文章

Unity URP 曲面细分学习笔记

学百人时遇到了曲面着色器的内容&#xff0c;有点糊里糊涂&#xff0c;于是上知乎找到了两篇大佬的文章 Unity URP 曲面细分 和 Unity曲面细分笔记&#xff0c;本文只是自己做学习记录使用 1.曲面细分与镶嵌 曲面细分或细分曲面&#xff08;Subdivision surface&#xff09;是…

字节跳动发Seed-TTS语音合成模型,可模仿任意人的声音,效果逼真

前期我们介绍过很多语音合成的模型&#xff0c;比如ChatTTS&#xff0c;微软语音合成大模型等&#xff0c;随着大模型的不断进步&#xff0c;其合成的声音基本跟真人没有多大的区别。本期介绍的是字节跳动自家发布的语音合成模型Seed-TTS。 Seed-TTS 推理包含四个功能模块&…

无人机之热成像篇

一、定义 无人机热成像技术是指将热成像相机安装在无人机云台上&#xff0c;通过无人机的高空飞行能力和云台的稳定性&#xff0c;结合红外热成像技术对目标区域进行非接触式的温度测量和图像采集。该技术利用物体发出的红外辐射来生成图像&#xff0c;通过测量物体表面温度分布…

Leetcode JAVA刷刷站(8)字符串转换整数

一、题目概述 二、思路方向 要实现这个功能&#xff0c;我们可以遵循以下步骤来编写 myAtoi 函数&#xff1a; 去除前导空格&#xff1a;使用循环或字符串的 trim() 方法&#xff08;虽然直接操作字符串更高效的方式是使用循环&#xff09;。检查符号&#xff1a;记录第一个非…

nodejs 生成随机邮箱

首先安装依赖&#xff1a; npm install faker 示例代码&#xff1a; const faker require(faker); const fs require(node:fs) function generateRandomEmail(num){let str for (let i 0; i < num; i) {str faker.internet.email() &:focus:&;}fs.writeFil…

魔众文库系统v7.0.0版本推荐店铺功能,管理菜单逻辑优化

推荐店铺功能&#xff0c;管理菜单逻辑优化 [新功能] RandomImageProvider 逻辑升级重构&#xff0c;支持更丰富的随机图片生成 [新功能] 资源篮订单参数字段 [新功能] 首页推荐店铺功能&#xff0c;需要在后台 文库系统 → 文库店铺 开启推荐 [系统优化] Grid 快捷编辑请求…

告别DockerHub 镜像下载难题:掌握高效下载策略,畅享无缝开发体验

告别DockerHub 镜像下载难题:掌握高效下载策略,畅享无缝开发体验 1. 介绍 1.1 DockerHub简介 Docker Hub 是 Docker 提供的一项服务,用于与您的团队查找和共享容器映像。 它是世界上最大的容器映像存储库,其中包含一系列内容源,包括容器社区开发人员,开源项目和独立软…

【Kubernetes】Service 类型

Service 类型 1.NodePort2.ClusterlP3.LoadBalance4.ExternalName 在《Service 概念与实战》一文中&#xff0c;Service 的发布使用的是 NodePort 类型。除此之外&#xff0c;Service 的发布还支持 ClusterlP、LoadBalancer 和 ExternalName 这 3 种类型。 1.NodePort 在把 Se…

基于微信小程序的小区业主服务系统(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍&#xff1a;我是程序员阿龙&#xff…

SOMEIP_ETS_033:echoUINT8ArrayMinSize_too_short

测试目的&#xff1a; 验证DUT是否能够正确处理小于最小尺寸&#xff08;少于3个元素&#xff09;的UINT8数组参数&#xff0c;并返回相应的错误消息。 描述 本测试用例旨在检验DUT在接收到长度不足3个元素的UINT8数组参数时&#xff0c;是否能够返回错误消息MALFORMED_MESS…

【电路笔记】-L 型衰减器

L 型衰减器 文章目录 L 型衰减器1、概述2、等阻抗L型衰减器3、不等阻抗的 L型衰减器4、L型衰减器示例25、总结L型衰减器是一个简单的电阻分压器网络,可用作固定无源衰减器以降低信号幅度。 1、概述 就其基本形式而言,L 型衰减器只不过是一个非常简单的分压器网络,用于许多电…

数据结构实验:排序算法(附c++源码:冒泡、选择、希尔、快速、堆排序)

实验内容&#xff1a; 输入一组关键字序列&#xff0c;分别实现下列排序算法: 1.编写函数&#xff0c;实现简单选择排序、直接插入排序和冒泡排序算法。 2.编写函数&#xff0c;实现希尔排序算法。 3.编写函数&#xff0c;实现快速排序算法。 4.编写函数&#xff0c;实现堆…

入门 PyQt6 看过来(项目)26 在线购物-主页面

功能导航页面很简单&#xff0c;就几个按钮功能。效果如下图&#xff1a; 1 主界面 ​ 包含 “商品选购”、”下单结算“、”销售分析“四个按钮以及“功能导航”标题。 2 工程目录 首先先创建工程目录及子目录&#xff1a; ​ 3 代码 主窗口文件为Main.py&#xff0c;其…

字体识别验证码的介绍!

字体识别验证码 ​是一种安全机制&#xff0c;‌通过要求用户识别特定字体来验证用户的身份或防止自动化攻击。‌这种验证码通常包含一些经过特殊设计的字符&#xff0c;‌需要用户根据这些字符的特定样式&#xff08;‌如字体、‌字形等&#xff09;‌来进行识别和输入。‌字…

【日常开发】 java返回ECharts数据结构封装

java返回ECharts数据结构封装 一、前端页面示例图如下&#xff1a; 二、准备测试数据&#xff1a; 三、后端 格式封装代码&#xff1a; 四、最终结果&#xff1a; &#x1f388;边走、边悟&#x1f388;迟早会好 一、前端页面示例图如下&#xff1a; 二、准备测试数据&am…

LVS实战演练

目录 一.LVS简介 <1>.工作原理 <2>.相关术语 <3>.lvs集群的常用转发类型 二.部署NAT模式集群实验 <1>.实验环境 1.调度器 2.真实服务器 3.客户端 <2>.实验配置 1.VS中启用内核路由器功能 2.RS装上http服务 3.VS安装ipvsadm软件 4.…

删掉Elasticsearch6.x 的 .security-6索引会怎么样?

背景 玩了下 Elasticsearch 的认证&#xff0c;启动 ES 并添加认证后&#xff0c;看到索引列表额外多了一个 .security-6 。以为是没用的&#xff0c;手欠就给删掉了&#xff0c;然后 Elasticsearch 就访问不了了。 只好再重新部署&#xff0c;再看索引内容&#xff0c;发现这…

VMWare虚拟机磁盘扩容

文章目录 环境背景虚拟机磁盘扩容配置参考 环境 VMWare Workstation 17 ProRHEL 9.4 背景 一个RHEL虚拟机&#xff0c;其 /home 目录大小为30GB。 [ding192 ~]$ df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 4.0M 0…

Multisim 用LM358 运放模拟线性稳压器 - 运放输出饱和 - 前馈电容

就是拿运放搭一个可调的LDO 稳压器&#xff0c;类似下面这个功能框图里的感觉。本来应该非常简单&#xff0c;没什么好说的&#xff0c;没想到遇到了两个问题。 原理 - 理想运放 我用PNP 三极管Q2 作为输出&#xff0c;运放输出电压升高时&#xff0c;流过PNP 三极管BE 的电流变…

关于解决Qt配置clang format插件后打开Qt时报缺少pythonxxx.dll的问题

前言 原本安装过程中没有出现任何问题&#xff0c;但是当我退出Qt&#xff0c;再次打开Qt时报 虽然也不影响正常编程&#xff0c;但是架不住每次打开它都提示&#xff0c;于是准备探究下这个问题&#xff0c;并将其解决掉 第一步&#xff1a; 在官网下载:clang format&…