【Spring Boot】事务的隔离级别与事务的传播特性详解:如何在 Spring 中使用事务?不同隔离级别的区别?

news2024/11/28 8:47:04

文章目录

  • 1 事务
    • 1.1 事务简介与 mysql 中的事务使用
    • 1.2 Spring 编程式事务(手动操作)
    • 1.3 Spring 声明式事务(自动操作)
    • 1.4 @Transactional 的工作原理
  • 2 事务的隔离级别
    • 2.1 事务的四大特性及事务的隔离级别回顾
    • 2.2 Spring 事务的隔离级别及设置
  • 3 Spring 事务传播机制
    • 3.1 初探事务的传播机制
    • 3.2 Spring 事务传播机制的分类及设置
    • 3.3 嵌套事务(NESTED)和加入事务(REQUIRED )的区别
  • 写在最后


1 事务

1.1 事务简介与 mysql 中的事务使用

事务这个词在学习 MySQL 和多线程并发编程的时候,想必大家或多或少接触过。那么什么是事务呢?

事务是指一组操作作为一个不可分割的执行单元,要么全部成功执行,要么全部失败回滚。在数据库中,事务可以保证数据的一致性、完整性和稳定性,同时避免了数据的异常和不一致情况。常见的事务包括插入、更新、删除等数据库操作。事务的核心要素是ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

比如,常见的转账操作,以小明给小红转账100元为例,分为如下两个操作:

  1. 小明的账户 -100元;
  2. 小红的账户 +100元。

如果没有事务,第一步操作执行成功,而第二步执行失败,就会导致小明账户平白无故的扣款而小红账户没有收到款项的问题。因此,事务的存在是必要的,这一组操作要么全部执行成功,要么一起失败~
转账示意图

在 MySQL 中,事务有三个重要的操作,分别为:开启事务、提交事务、回滚事务,对应的操作命令如下:

-- 开启事务
start transaction;
-- 业务执行
...
-- 提交事务
commit;
-- 回滚事务
rollback;

1.2 Spring 编程式事务(手动操作)

与 MySQL 操作事务类似,Spring 手动操作事务也需要三个重要的操作:开启事务(获取事务)、提交事务、回滚事务。

SpringBoot 内置了两个对象:

  • DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的;
  • TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus

实现代码如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    // 事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public int add(UserInfo userInfo) {
        // 非空校验
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }
        // 1. 开始事务
        TransactionStatus transactionStatus =
                transactionManager.getTransaction(transactionDefinition);
        int result = userService.add(userInfo);
        System.out.println("添加: " + result);
//        // 2. 回滚事务
//        transactionManager.rollback(transactionStatus);
        // 3. 提交事务
        transactionManager.commit(transactionStatus);
        return result;
    }
}

从上述代码可以看出,虽然可以实现事务,但是操作很繁琐。因此,我们 常常使用另一种更简单的方式:基于注解的声明式事务。

1.3 Spring 声明式事务(自动操作)

相比手动操作事务来说,声明式事务非常简单,只需要在需要的方法上添加 @Transactional 注解,无需手动开启事务和提交事务。

示例代码如下:

@Transactional // 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
    // 非空校验
    if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
            || !StringUtils.hasLength(userInfo.getPassword())) {
        return 0;
    }
    int result = userService.add(userInfo);
    return result;
}

对于 @Transactional 的几点说明:

  1. 该注解可以加在方法或者类上,若加在类上,则说明该类的所有公共方法可以自动的开启和提交事务 ,无论修饰方法还是类,都只对 public 方法有效;
  2. 在方法执行前自动开启事务,在方法执行完毕(没有发生任何异常)自动提交事务。如果 在方法执行期间出现异常,会自动回滚事务。

附:@Transactional 的常见参数:

参数说明
propagation定义了事务方法被嵌套调用时,事务如何传播到被调用的方法。常见取值包括:
- REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- REQUIRES_NEW:每次调用方法时都会创建一个新的事务,如果存在当前事务,则将其挂起。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
isolation定义了事务并发执行时,事务之间的隔离程度。常见取值包括:
- DEFAULT(默认):使用数据库默认的隔离级别。
- READ_UNCOMMITTED:最低的隔离级别,事务可以读取未提交的数据。
- READ_COMMITTED:事务只能读取已提交的数据。
- REPEATABLE_READ:事务在整个过程中保持一致的读取视图,防止脏读和不可重复读。
- SERIALIZABLE:最高的隔离级别,事务串行执行,避免脏读、不可重复读和幻读。
timeout定义了事务执行的最长时间,单位为秒。默认值为-1,表示没有超时限制。
readOnly如果设置为true,表示事务只读,不会修改数据库的数据。默认值为false
rollbackFor触发事务回滚的异常类数组。当方法抛出指定的异常时,事务将回滚。
noRollbackFor不触发事务回滚的异常类数组。当方法抛出指定的异常时,事务将不会回滚。
rollbackForClassName触发事务回滚的异常类名数组。当方法抛出指定的异常时,事务将回滚。
noRollbackForClassName不触发事务回滚的异常类名数组。当方法抛出指定的异常时,事务将不会回滚。
value用于指定事务管理器的名称。如果应用程序中存在多个事务管理器,可以使用该参数指定要使用的事务管理器的名称。默认情况下,事务将使用默认的事务管理器。
transactionManager用于指定事务管理器的引用。可以直接将一个事务管理器对象传递给该参数,以指定要使用的事务管理器。默认情况下,事务将使用默认的事务管理器。

对于上述表格中的事务隔离级别需要重点掌握,具体后面详细说。

需要特别注意的是,如果方法中的异常被 try-catch 异常捕获处理后,则不会再进行事务的回滚。

当然,我们可以通过 throw 将异常抛出,使得事务能够正常自动回滚。但是这样子做,try-catch 还有意义吗?表情包

因此,对于这种情况,更偏向于使用另一种优雅的方式,进行手动回滚事务来解决~

如何在声明式事务中进行手动回滚事务?
使用代码进行手动回滚事务:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

示例代码如下:
代码示例

1.4 @Transactional 的工作原理

  1. 当调用被@Transactional注解标记的方法时,事务管理器会检查当前是否存在一个事务。如果存在事务,则该方法将在该事务的上下文中执行;如果不存在事务,则会创建一个新的事务。
  2. 在方法执行期间,如果发生了受检查异常(checked exception),事务管理器会捕获该异常,并根据配置的回滚规则决定是否回滚事务。如果异常被捕获并且需要回滚事务,则事务将被回滚,方法的执行将终止,并将异常传播给调用方。
  3. 如果方法成功执行并且没有抛出受检查异常,事务管理器将提交事务,将数据库中的更改持久化。
  4. 如果方法执行期间抛出了未受检查异常(unchecked exception)或错误(Error),事务管理器会将事务标记为回滚,并将异常传播给调用方。
  5. 如果方法执行期间没有抛出异常,但在方法内部调用了其他被@Transactional注解标记的方法,事务管理器将根据事务的传播行为决定如何处理这些方法。例如,如果传播行为设置为REQUIRED,则内部方法将加入当前事务;如果传播行为设置为REQUIRES_NEW,则内部方法将创建一个新的事务。

具体来看,@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。实现细节的执行流程如图所示:
实现细节


2 事务的隔离级别

2.1 事务的四大特性及事务的隔离级别回顾

事务有4 ⼤特性(ACID),原⼦性、⼀致性、隔离性和持久性:

  • 原⼦性: ⼀个事务中的所有操作,要么全部完成,要么全部不完成。若事务在执⾏过程中发⽣错误,会被回滚到事务开始前的状态。
  • ⼀致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。即写⼊的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。
  • 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 隔离性: 数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。

其中,对于隔离性有隔离级别可以设置。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化(Serializable)。

归根到底,事务隔离级别的设置是为了防止其它事务影响当前事务的一种策略。

在MySQL中,默认是可重复读(repeatable read)级别。以下是MySQL常见的事务隔离级别以及它们对脏读、不可重复读和幻读问题的解决情况的表格:

事务隔离级别脏读(Dirty Read)不可重复读(Non-repeatable Read)幻读(Phantom Read)
读未提交(Read Uncommitted)可能发生可能发生可能发生
读已提交(Read Committed)避免可能发生可能发生
可重复读(Repeatable Read)避免避免可能发生
串行化(Serializable)避免避免避免

对于脏读、不可重复读和幻读的解释:

  • 脏读(Dirty Read):指一个事务读取了另一个事务未提交的数据。如果一个事务可以读取未提交的数据,则会发生脏读。

  • 不可重复读(Non-repeatable Read):指在同一个事务中,多次读取同一行数据时,得到的结果不一致。这是因为在读取期间,另一个事务修改了该行数据。

  • 幻读(Phantom Read):指在同一个事务中,多次查询同一个范围的数据时,得到的结果集不一致。这是因为在查询期间,另一个事务插入或删除了符合查询条件的数据。

每个隔离级别对这些问题的解决情况如下:

  • 读未提交(Read Uncommitted):允许脏读、不可重复读和幻读。一个事务可以读取另一个事务未提交的数据。

  • 读已提交(Read Committed):避免脏读。一个事务只能读取已提交的数据。但是,可能发生不可重复读和幻读,因为在同一个事务中,另一个事务可能会修改数据。

  • 可重复读(Repeatable Read):避免脏读和不可重复读。在同一个事务中,多次读取同一行数据时,得到的结果是一致的。但是,可能发生幻读,因为在同一个事务中,另一个事务可能会插入或删除数据。

  • 串行化(Serializable):避免脏读、不可重复读和幻读。事务串行执行,保证了数据的一致性和完整性。

但隔离级别的提升会增加并发性能的开销,因为更高的隔离级别通常需要使用锁来实现。

在数据库中,可以使用如下语句来查询全局事务隔离级别和当前连接的事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;

事务隔离级别查询

2.2 Spring 事务的隔离级别及设置

在Spring框架中,事务的隔离级别可以使用@Transactional注解来设置。@Transactional注解可以应用在方法级别或类级别上,用于声明一个事务性方法或类。

Spring 框架支持以下五个事务隔离级别:

  1. DEFAULT(默认):使用底层数据库的默认隔离级别。对于大多数数据库来说,通常是READ_COMMITTED

  2. READ_UNCOMMITTED:读未提交。允许脏读、不可重复读和幻读。这是最低的隔离级别,一个事务可以读取另一个事务未提交的数据。

  3. READ_COMMITTED:读已提交。避免脏读。一个事务只能读取已提交的数据。但是,可能发生不可重复读和幻读,因为在同一个事务中,另一个事务可能会修改数据。

  4. REPEATABLE_READ:可重复读。避免脏读和不可重复读。在同一个事务中,多次读取同一行数据时,得到的结果是一致的。但是,可能发生幻读,因为在同一个事务中,另一个事务可能会插入或删除数据。

  5. SERIALIZABLE:串行化。避免脏读、不可重复读和幻读。事务串行执行,保证了数据的一致性和完整性,但是性能太低。

可以在@Transactional注解上使用isolation属性来设置事务的隔离级别。例如:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransactionalMethod() {
    // 事务性操作
}

需要注意的是,事务的隔离级别还受数据库本身支持的隔离级别的限制。如果数据库不支持某个特定的隔离级别,那么Spring框架将尽力使用最接近的隔离级别。


3 Spring 事务传播机制

3.1 初探事务的传播机制

事务的传播机制是用来定义事务在传播过程中的行为模式的一种机制。 Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法进行传递的。

对比事务的隔离级别来看,如果说事务的隔离级别是保证多个并发事务执行的可控性的(稳定性),则 事务的传播机制就是保证一个事务在多个调用方法间的可控性的(稳定性)。

事务的隔离级别解决的是多个并发事务调用数据库的问题:
事务隔离级别

事务的传播机制解决的是一个事务在多个节点(方法)中传递的问题:
事务的传播机制
比如,方法 A 正常执行,完成了事务。但是,方法 B 发生了错误。那么,方法 A 进行的事务操作是否要回滚呢?这就是事务的传播机制需要解决的问题~

3.2 Spring 事务传播机制的分类及设置

在Spring框架中,事务传播机制用于定义在多个事务性方法相互调用时,事务如何传播和交互的规则。Spring框架提供了七种不同的事务传播行为:

  1. REQUIRED(需要有):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为。

  2. SUPPORTS(可以有):如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。

  3. MANDATORY(强制有):如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  4. REQUIRES_NEW:创建一个新的事务,并挂起当前事务(如果存在)。新创建的事务与当前事务完全独立。

  5. NOT_SUPPORTED:以非事务方式执行,并且挂起当前事务(如果存在)。

  6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  7. NESTED:如果当前存在事务,则在嵌套事务中执行。嵌套事务是独立于当前事务的子事务,它可以独立地进行提交或回滚。如果当前没有事务,则创建一个新的事务。

这些事务传播行为可以通过@Transactional注解的propagation属性进行设置。例如:

@Transactional(propagation = Propagation.REQUIRED)
public void myTransactionalMethod() {
    // 事务性操作
}

需要注意的是,事务传播行为仅在方法之间的调用时才会生效,对于同一个方法内部的事务性操作,传播行为不会起作用。

如果将事务比作房子,以伴侣为例子理解(以下图片来自网络):
理解事务传播机制

3.3 嵌套事务(NESTED)和加入事务(REQUIRED )的区别

  1. 整个事务如果全部执行成功,二者的结果是一样的。
  2. 如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果。

写在最后

本文被 JavaEE编程之路 收录点击订阅专栏 , 持续更新中。
 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

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

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

相关文章

【Unity2D】相机移动以及设置相机边界

添加相机 添加相机时,首先需要在unity中添加 Cinemachine 包 第一次使用这个包时,需要在Package Manager中搜索并安装 安装Camera Mechine包后,添加2D Camera 设置跟随对象为Ruby (从Hierarchy中将Ruby拖动到Follow中&#xff0…

非线性质量弹簧阻尼器的神经网络仿真研究(Matlab代码Simulink仿真实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 🌈4 Matlab代码、Simulink仿真实现 💥1 概述 非线性质量弹簧阻尼器(Nonlinear Mass-Spring-Damper,NMSD)是一种常见的振动控制装置&#…

VS2017找不到QT头文件

一、我的电脑右键属性 - 》“高级系统设置” -》“环境变量” 增加环境变量Qt_INCLUDEPATH_ 值为QT的头文件目录 二、重启VS 发现波纹线不见了,证明设置环境变量后VS能识别到QT头文件了 原理是:vs导入qt项目附加包含目录继承值有Qt_INCLUDEPATH_

【视频的动态对比】

写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除! 文章目录 前言图像修复人脸与关键点检测修复图像修复视频 动态对比添加声音获取原视频音频融合声…

elementui全局给select option添加title属性

场景 有天边上的同事问了我一个问题,示例如下,有个数据特别长,导致下拉部分被横向撑大。希望在全局对所有的option进行处理,按照select的宽度,超出隐藏。 处理 方式一 第一眼看过去直接修改源码好了,修…

Mybatis基础模块-日志管理

文章目录 1. 适配器模式2. Log2.1 默认实现StdOutImpl2.2 Log4jImpl 3. LogFactory4. 解析配置和应用4.1 settings配置4.2 解析 5. jdbc日志5. 1 类图5.2 BaseJdbcLogger5.3 ConnectionLogger5.4 ConnectionLogger的具体应用 1. 适配器模式 适配器使接口不兼容的对象可以相互合…

用QFramework来重构 祖玛游戏

资料 Unity - 祖玛游戏 GitHub 说明 用QF一个场景就够了,在UIRoot下切换预制体达到面板切换。 但测试中当然要有一个直接跳到测试面板的 测试脚本,保留测试Scene(不然初学者也不知道怎么恢复测试Scene),所以全文按S…

SpringBoot整合Spring Security实现权限控制

文章目录 Spring Security介绍Spring Security案例1、快速搭建一个springboot工程2、导入SpringSecurity整合springboot工程3、认证3.1、登录流程校验3.2、入门案例的原理3.3、实现思路3.4、实现认证流程(自定义)3.5、正式实现3.5.1 实现数据库的校验3.5…

Linux 内核 ASoC 基本数据结构

Linux 内核 ASoC 框架建立了新的抽象,并通过一些中间层,将这些抽象接入 ALSA 音频框架。 Linux 内核 ASoC 设备驱动的结构如下图: Linux 内核 ASoC 设备驱动程序在 Linux 内核中扮演多个角色。 Linux 内核 ASoC 设备驱动程序在初始化阶段向…

MATLAB遗传算法求解带容量约束的物流配送选址问题实例

MATLAB遗传算法求解带容量约束的物流配送选址问题实例 作者:麦哥爱西芹 MATLAB遗传算法求解带容量约束物流配送中心选址问题代码实例 遗传算法编程问题实例: 在经度范围为(116, 118),纬度范围为(38, 40)的矩形区域内,散布着37个需…

第116天:免杀对抗-EDRSyscall-hookDLL反射注入白加黑隐写分离加载器

知识点 #知识点: 1、DLL劫持-自写&导入 2、DLL劫持-重写&分离 3、syscall-底层&项目#章节点: 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 …

常微分方程建模R包ecode(一)——构建常微分方程系统

常微分方程在诸多研究领域中有着广泛应用,本文希望向大家介绍笔者于近期开发的R包ecode,该包采用简洁易懂的语法帮助大家在R环境中构建常微分方程,并便利地调用R图形接口,研究常微分方程系统的相速矢量场、平衡点、稳定点等解析性…

基于linux下的高并发服务器开发(第二章)- 2.20 kill、raise、abort函数

03 / 信号的5种默认处理动作 当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”&#…

X86设备启动过程

文章目录 一、电源自检二、BIOS自检三、引导设备选择四、主引导记录4.1 0x7c0 五、加载操作系统 x86计算机启动过程,主要分为这几个阶段:电源自检、BIOS自检、引导设备的选择、主引导记录、加载操作系统。 一、电源自检 当我们按下开关键后,…

消息队列总结(3)- RabbitMQ Kafka RocketMQ高可用方案

目录 1. 什么是高可用? 1.1 常见的高可用方法 1.2 消息队列的高可用 2. RabbitMQ的高可用方案 2.1 镜像队列 2.2 消息生产的确认机制 2.3 消息的持久化 3. Kafka的高可用方案 3.1 消息备份 3.2 ISR & IEO & HW 3.3 消息生产的确认机制 4. Rocke…

在虚拟机中安装anaconda和pytorch

首先我用的是VMware&#xff0c;ubuntu16.04. 首先建议安装anaconda,登录官网Free Download | Anaconda 下载完成后&#xff0c;来到安装文件目录处&#xff0c;打开终端&#xff0c; 然后在终端输入bash <anaconda文件名> 然后就一直enter和yes到底&#xff0c;直到安…

【后端面经】微服务构架 (1-3) | 熔断:熔断-恢复-熔断-恢复,抖来抖去怎么办?

文章目录 一、前置知识1、什么是熔断?2、什么是限流?3、什么是降级?4、怎么判断微服务出现了问题?A、指标有哪些?B、阈值如何选择?C、超过阈值之后,要不要持续一段时间才触发熔断?5、服务恢复正常二、面试环节1、面试准备2、面试基本思路三、总结 在微服务构架中…

【OC总结 面向对象 + 内存管理 + runtime】

文章目录 前言面向对象1.1 一个NSObject对象占用多少内存&#xff1f;1.2 iOS的继承链 & 对象的指针指向了哪里&#xff1f;1.3 OC的类的信息存放在哪里&#xff1f;-isa指针1.4 isMemberOfClass & isKindOfClass Runtime1.4 讲一下OC的消息机制1.5 消息转发机制流程1.…

React 中 {} 的应用

列表渲染&#xff1a; 1.使用数组的 map 方法&#xff08;因为map 可以映射&#xff09; 2、列表要添加 key 属性&#xff0c;值要唯一 // 导入 // 用来获取 dom元素 import { createRoot } from "react-dom/client";// 内容 const contentArr [{ id: 1, name: &…

Spring Cloud【分布式配置中心(Spring Cloud Config 、Config配置总控中心搭建、Config配置读取规则)】(九)

目录 服务网关Gateway实现用户鉴权_网关全局过滤器加入JWT 鉴权 分布式配置中心_Spring Cloud Config 分布式配置中心_Config配置总控中心搭建 分布式配置中心_Config配置读取规则 服务网关Gateway实现用户鉴权_网关全局过滤器加入JWT 鉴权 配置跳过验证路由 org:my:jwt…