Spring中事务的隔离级别和传播机制

2024-06-12 06:36

本文主要是介绍Spring中事务的隔离级别和传播机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇博客中讲解了关于事务的两种使用方式包括@Transactional的详解。

@Transactional 注解当中的三个常⻅属性:
1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

关于第二点和第三点还没有讲解完,这一篇博客来讲解关于事务的隔离级别和传播机制。


1. 事务的隔离级别

事务有4大特性(ACID),原子性、持久性、一致性和隔离性,具体概念如下:

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

持久性:务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatableread)和串行化(Serializable)。

其中隔离性在我之前的博客中也有讲解,建议去看看:
MySQL事务的四种隔离级别详解-CSDN博客文章浏览阅读1k次,点赞33次,收藏20次。MySQL为我们提供了不同的“隔离级别”,这样就方便使用者控制隔离级别的高低/并发程度的高低/执行效率的高低/准确性的高低。因为MySQL服务器是要给多个客户端来使用的,那么此时多个客户端之间就会同时发起事务,尤其是发起的多个事务在操作同一个数据库的同一个表的时候,这样由于“多线程”的操作就很容易引发一些“意料之外的”麻烦。如果隔离性越高,就意味着事务之间的并发程度越低,执行效率越慢,但是数据的准确性是越高的。如果隔离性越低,就意味着事务时间的并发程度越高,执行效率越快,但是数据的准确性是越低的。https://blog.csdn.net/qq_45875349/article/details/136722597我们为什么要设置隔离级别?

设置事务的隔离级别是⽤来保障多个并发事务执⾏更可控,更符合操作者预期的。

2. Spring中设置事务的隔离级别

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置,具体操作如下图所示:
在学习Spring中的事务隔离级别前,先回顾一下刚才我的博客中提到的MySQL中的隔离级别:
MySQL 事务隔离级别有 4 种
  1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
  2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据。因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
  3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同一事务多次查询只的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读(Phantom Read)。
  4. SERIALIZABLE:序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突、从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

 在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:

select @@global.tx_isolation,@@tx_isolation;
Spring 事务隔离级别有 5 种
⽽ Spring 中事务隔离级别包含以下 5 种:
  • Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  • Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  • Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  • Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  • Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。
从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个。
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置:

3. Spring事务传播机制

3.1 什么是事务的传播机制

事务传播机制就是: 多个事务方法存在调用关系时, 事务是如何在这些方法间进⾏传播的。
比如有两个⽅法A, B都被 @Transactional 修饰, A方法调用B⽅法, A方法运行时, 会开启⼀个事务. 当A调用B时, B方法本⾝也有事务, 此时B方法运行时, 是加入A的事务, 还是创建⼀个新的事务呢?
这个就涉及到了事务的传播机制!
⽐如公司流程管理
执⾏任务之前, 需要先写执⾏⽂档, 任务执⾏结束, 再写总结汇报
此时A部⻔有⼀项⼯作, 需要B部⻔的⽀援, 此时B部⻔是直接使⽤A部⻔的⽂档, 还是新建⼀个⽂档呢?

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题  

⽽事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题:

3.2 事务的传播机制有哪些

@Transactional 注解支持事务传播机制的设置,通过 propagation 属性来指定传播行为。

Spring 事务传播机制有以下 7 种:
  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5. Propagation.NOT SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION REQUIRED。

3.3 Spring事务传播机制演示

3.3.1 REQUIRED(默认值)

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

比如现在我们有两个银行账户A001和A002,进行转账交易。一个转账的业务可分为两个步骤:

1.A001从自己的账户扣款;

2.A002的账户加上A001扣的款。

如果有任何一步骤失败了,都应该全部回滚。因为如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。两步操作肯定是在同一事务中。

controller代码:

package com.example.transactiondemo.controller;import com.example.transactiondemo.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/bank")
public class BankController {@Autowiredprivate BankService bankService;@PostMapping("/transfer")@Transactional(propagation = Propagation.REQUIRED)public String transfer(@RequestParam String fromAccount, @RequestParam String toAccount, @RequestParam double amount) {//从账户A001扣钱bankService.transfer1(fromAccount, amount);//给账户A002加钱bankService.transfer2(toAccount, amount);return "Transfer successful";}
}

service代码:

package com.example.transactiondemo.service;import com.example.transactiondemo.entity.Account;
import com.example.transactiondemo.mapper.AccountMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;@Service
public class BankService {@Resourceprivate AccountMapper accountMapper;@Transactional(propagation = Propagation.REQUIRED)public void transfer1(String fromAccountNumber, double amount) {Account fromAccount = accountMapper.findByAccountNumber(fromAccountNumber);if (fromAccount == null ) {throw new IllegalArgumentException("Invalid account number");}if (fromAccount.getBalance() < amount) {throw new IllegalArgumentException("Insufficient balance in account: " + fromAccountNumber);}fromAccount.setBalance(fromAccount.getBalance() - amount);accountMapper.updateAccount(fromAccount);}@Transactional(propagation = Propagation.REQUIRED)public void transfer2(String toAccountNumber, double amount) {Account toAccount = accountMapper.findByAccountNumber(toAccountNumber);if (toAccount == null) {throw new IllegalArgumentException("Invalid account number");}toAccount.setBalance(toAccount.getBalance() + amount);accountMapper.updateAccount(toAccount);// 手动引入异常来测试事务回滚if (amount > 500) {throw new RuntimeException("Transfer amount exceeds limit, transaction will be rolled back");}}
}

注意这个手动加入的异常是伪代码,没有实际意义,为了演示效果。

Mapper代码:

package com.example.transactiondemo.mapper;import com.example.transactiondemo.entity.Account;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;@Mapper
public interface AccountMapper {Account findByAccountNumber(@Param("accountNumber") String accountNumber);void updateAccount(Account account);
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.transactiondemo.mapper.AccountMapper"><select id="findByAccountNumber" resultType="com.example.transactiondemo.entity.Account">SELECT id, account_number AS accountNumber, balanceFROM accountWHERE account_number = #{accountNumber}</select><update id="updateAccount">UPDATE accountSET balance = #{balance}WHERE id = #{id}</update>
</mapper>

使用POSTMAN测试:

第一次转账100应该成功,没有触发异常都正常提交:

第二次转账600应该失败,触发了异常(手动),数据库中没有任何变化:

因为,上述操作的执行流程大概是:

其他事务传播机制的使用都大同小异,主要还是根据不同的场景来觉得使用什么类型的传播机制。


总 结

1. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级 别. Spring 中的事务隔离级别有 5 种
2. 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的 事务传播级别有 7 种, 重点关注 REQUIRED (默认值) 和 REQUIRES_NEW

这篇关于Spring中事务的隔离级别和传播机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1053449

相关文章

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus

22.手绘Spring DI运行时序图

1.依赖注入发生的时间 当Spring loC容器完成了 Bean定义资源的定位、载入和解析注册以后,loC容器中已经管理类Bean 定义的相关数据,但是此时loC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况 发生: 、用户第一次调用getBean()方法时,loC容器触发依赖注入。 、当用户在配置文件中将<bean>元素配置了 lazy-init二false属性,即让