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

文章目录

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python web开发之WSGI/uwsgi/uWSGI详解

1. 三者的定义 WSGI是一种通信协议。uwsgi是一种传输协议。uWSGI是实现了uwsgi和WSGI两种协议的Web服务器。 2.三者的使用场景 WSGI,全称 Web Server Gateway Interface,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接…

MySQL之索引(入门级讲解)

目录 一.索引的概念 1.1索引的简介 1.2.索引的优缺点 二.MySQL索引语法 2.1查看索引 2.2创建索引 2.2.1 创建表时创建索引 2.2.2存在的表上创建索引 2.3删除索引 三.索引的数据结构 3.1Btree索引 3.2Hash索引 3.4Hash索引和Btree索引的对比 🎁个…

文本预处理——文本张量表示方法

目录 文本张量表示one-hot编码word2vecword embedding 文本张量表示 one-hot编码 word2vec word embedding

代码随想录算法训练营第二十一天 | 读PDF复习环节1

读PDF复习环节1 本博客的内容只是做一个大概的记录,整个PDF看下来,内容上是不如代码随想录网站上的文章全面的,并且PDF中有些地方的描述,是很让我疑惑的,在困扰我很久后,无意间发现,其网站上的讲…

使用rknn-toolkit2把YOLOV5部署到OK3588上

使用rknn-toolkit2把YOLOV5部署到OK3588上 虚拟环境搭建软件包安装在PC机上运行yolov5目标检测 虚拟环境搭建 首先在PC的ubuntu系统安装虚拟环境: 我的服务器是ubuntu18.04版本,所以安装python3.6 conda create -n ok3588 python3.6 需要键盘输入y&…

Python 算法基础篇:插入排序和希尔排序

Python 算法基础篇:插入排序和希尔排序 引言 1. 插入排序算法概述2. 插入排序算法实现实例1:插入排序 3. 希尔排序算法概述4. 希尔排序算法实现实例2:希尔排序 5. 插入排序与希尔排序的对比总结 引言 插入排序和希尔排序是两种常用的排序算法…

Day 61-62 决策树(ID3)

代码: package dl;import java.io.FileReader; import java.util.Arrays; import weka.core.*;/*** The ID3 decision tree inductive algorithm.*/ public class ID3 {/*** The data.*/Instances dataset;/*** Is this dataset pure (only one label)?*/boolean …

结构型模式 - 适配器模式

概述 如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供…

springboot+vue开发后台增删改查

效果图 前端代码【User.vue】 <template><div class"data-container"><!--添加 start--><div class"data-header"><el-button round click"addHander" size"large" type"primary"><el-ic…

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测 目录 区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归多输入单输出区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 区间预测 | MATLAB实现QRBiLSTM…

uview中的常用的框

第一步&#xff1a; 先下载 uview UI 框架 详见 项目 引入 uView_vue引入uview_qq_2524963996的博客-CSDN博客【代码】 项目 引入 uView。_vue引入uviewhttps://blog.csdn.net/qq_44161703/article/details/131168976?spm1001.2014.3001.5501 第二步&#xff1a; 可以直接…

解锁潜力,驭数赋能:大数据与云计算的强强联合

随着数字化时代的来临&#xff0c;大数据和云计算已成为信息技术领域的两大热门话题。大数据指的是以海量、高速、多样化的数据为基础&#xff0c;通过分析和挖掘来获得有价值的信息和洞察。而云计算则是一种基于网络的计算模式&#xff0c;通过将数据和应用程序存储在云端服务…

【前端动画】科技感扫描效果 css动画animation

扫描出现动画 参考了网友写的二维码扫描 <template><div><div class"scan-content"><img style"width: 2rem;height: 2rem;" src"../../assets/images/eye.png" alt"" /><div class"line">…

Express 框架的基本操作

目录 1、应用生成器 2、基本路由 2.1、在跟路由下配置 GET请求&#xff0c;返回对应相应内容。 2.2、在跟路由下配置 POST请求&#xff0c;返回对应相应内容。 2.3、在跟路由下配置 PUT请求&#xff0c;返回对应相应内容。 2.4、在根路由下配置DELETE请求&#xff0c;返回对…

<Java物联网> 从主动到被动:Java中的BACnet设备属性查询

目录 BACnet 使用软件 资源 模拟器 使用Java主动查 引入maven 创建网络对象 获取远程设备 获取设备属性 使用DeviceEventAdapter订阅 初始化本地BACnet设备和IP网络配置&#xff1a; 启动本地设备和添加监听器&#xff1a; 搜寻远程设备&#xff1a; 发送订阅COV报…

mybatis事物是如何和spring事物整合的

目录 1、mybatis事物管理器 2、SpringManagedTransactionFactory如何处理事物 3、spring事物如何设置connection连接到threadLocal 1、mybatis事物管理器 mybatis事物抽象接口类&#xff1a;Transaction。该接口定义了事物基本方法和获取数据库连接方法 该类有三个实现类Jd…

基于Java+SpringBoot+Vue前后端分离旅游网站详细设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

pytorch工具——使用pytorch构建一个分类器

目录 分类器任务和数据介绍训练分类器的步骤在GPU上训练模型 分类器任务和数据介绍 训练分类器的步骤 #1 import torch import torchvision import torchvision.transforms as transformstransformtransforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.…

docker数据网络管理

数据管理 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;。 1&#xff0e;数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可将宿主机的目录挂…