Spring 事务(事务、声明式事务@Transactional、事务隔离级别、事务传播机制)

目录

1.事务的定义

2.Spring中事务的实现

2.1 MySQL中的事务使用

2.2 Spring中编程事务的实现

2.3 Spring中声明式事务

2.3.1 声明式事务的实现 @Transactional

2.3.2 @Transactional 作用域

2.3.3 @Transactional 参数说明

2.3.4 注意事项

(1)解决方法1(将异常抛出)

(2)解决方法2(使用代码手动回滚事务)

2.3.5 @Transactional 工作原理

3. 事务隔离级别

3.1 事务特性

3.2 Spring 中设置事务隔离级别

MySQL 事务隔离级别

Spring 事务隔离级别(5种)

注意事项:

4. Spring事务传播机制

4.1 事务传播机制是什么

4.2 为什么需要事务传播机制

4.3 事务传播机制有哪些

 4.4 Spring 事务传播机制使用

4.4.1 支持当前事务(REQUIRED 默认)

4.4.2 嵌套事务(NESTED)

4.4.5 嵌套事务和REQUIRED事务的区别


1.事务的定义

事务定义:将一组操作封装成一个执行单元(封装到一起),要么一起成功,要么一起失败

那么我们为什么要用事务呢?

比方说银行的转账操作

  1. A账户转账100:-100
  2. B账户接收100:+100

如果没有事务,在A账户转账成功后,出现了失误和问题,B账户并没有加100,那么A账户便无故损失了100,如果使用了事务,当出现了失误和问题后便会立刻回滚给A账户,即要么一起成功,要么一起失败。


2.Spring中事务的实现

Spring中的事务操作分为两种:

  1. 编程式事务(通过代码的方式实现事务)
  2. 声明式事务(通过注释的方式实现声明事务)

2.1 MySQL中的事务使用

MySQL 中事务有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:

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

2.2 Spring中编程事务的实现

Spring 手动操作事务和上述的 MySQL 操作事务类似,他也是有三个操作步骤:

  1. 开启事务(获取事务)
  2. 提交事务
  3. 回滚事务
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将
TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus,实现代码如下:
@RestController
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate DataSourceTransactionManager transactionManager;@Autowiredprivate TransactionDefinition transactionDefinition;// 在此方法中使用编程式的事物@RequestMapping("/add")public int add(UserInfo userInfo) {// 非空效验【验证用户名和密码不为空】if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) {return 0;}// 开启事务(获取事务)TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);int result = userService.add(userInfo);System.out.println("add 受影响的行数:" + result);// 提交事务transactionManager.commit(transactionStatus);
//        // 回滚事务
//        transactionManager.rollback(transactionStatus);return result;}
}

因为回滚了事务,所以数据库中不会有wangwu这个用户

从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。


2.3 Spring中声明式事务

2.3.1 声明式事务的实现 @Transactional

声明式事务的实现,只需要在方法上添加 @Transactional 注解就可以实现,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完全会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

// 声明式事务(自动提交)
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo) {if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) {return 0;}int result = userService.add(userInfo);System.out.println("add2 受影响的行数:" + result);int num = 10/0;return result;
}

 我们可以看到在发生异常后事务会自动回滚,所有我们在数据库中无法找寻到添加数据,

发现数据库中无新增数据,这说明@Transactional生效了

2.3.2 @Transactional 作用域

@Transactional  可以用来修饰方法和类

  • 修饰方法时:注意只能应用在 public 方法上,否则不生效
  • 修饰类时:表明该注释对类中的所有 public 方法生效

2.3.3 @Transactional 参数说明

参数作用
value当你配置多个事务管理器时,可以使用该属性指定选择用哪个事务管理器
transactionManager同上
propagation事务的传播行为,默认值为 Propagation.REQUIRED
isolation事务的隔离级别,默认值为 Isolation.DEFAULT
timeout事务的超时时间,默认值为-1,如果超过该时间限制但事务还没完成,则自动回滚事务
readOnly指定事务是否为只读事务,默认值为 false,为了忽略那些不需要事务的方法,比如读取数据可以设置 read-only 为 true
rolibackFor用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
rolibackForClassName同上
noRolibackFor抛出指定的异常类型,不回滚事务,也可以指定多个异常类型
noRollbackForClassName同上

2.3.4 注意事项

@Transactional 在异常被捕获的情况下,不会进行事务自动回滚

@Transactional
@RequestMapping("/add2")
public int add3(UserInfo userInfo) {if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) {return 0;}int result = userService.add(userInfo);System.out.println("add2 受影响的行数:" + result);try {int num = 10/0;} catch (Exception e) {}return result;
}

这样是把异常打印出来了,且数据库中也添加进去了,但是出现了异常应该回滚才对的!

(1)解决方法1(将异常抛出)

2)解决方法2(使用代码手动回滚事务)

手动回滚事务,在⽅法中使⽤ TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码

    @Transactional@RequestMapping("/add3")public int add3(UserInfo userInfo) {if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())|| !StringUtils.hasLength(userInfo.getPassword())) {return 0;}int result = userService.add(userInfo);System.out.println("add2 受影响的行数:" + result);try {int num = 10/0;} catch (Exception e) {//手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}

2.3.5 @Transactional 工作原理

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

@Transactional 实现思路:

 

@Transactional 具体执行细节:

 


3. 事务隔离级别

3.1 事务特性

事务有四大特性(ACID):原子性,持久性,一致性,隔离性

  • 原子性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态就像这个 事务从来没有执行过⼀样。
  • 持久性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串联性以及后续数据库可以自发性地完成预定的工作
  • 一致性:事务处理结束后,对数据的修改就是永久的。即使系统故障也不会丢失
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由交叉执行而导致数据的不一致。

而这 4 种特性中,只有隔离性(隔离级别)是可以设置的


3.2 Spring 中设置事务隔离级别

在设置事务隔离级别前我们想一下,为什么要设置事务隔离级别呢?

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

这个可控表示的是,比如疫情的时候,有确诊、密接、次密接等针对不同的人群,采取不同的隔离级别,这种方式与事务的隔离级别类似,都是让某种行为操作变的 更可控事务的隔离级别就是为了防止,其他事务影响当前事务执行的一种策略

MySQL 事务隔离级别

  1. 读未提交(READ UNCOMMITTED):事务A在读取数据的时候读取到了事务B尚未提交的数据,但是在过一会后事务B对A读取到的数据进行回滚了,那么A读取的数据就是脏读,读未提交侧重于查询,既然有了脏读,那么也肯定有不可重复读和幻读的问题
  2. 读已提交(READ COMMITTED):针对上面脏读的问题来解决的,事务A读到了事务B已经提交的数据,然后过了一会事务B将提交的数据进行修改了,此时事务A又读了一次B的数据,发现两次读到读到数据不一样,这个就叫不可重复读的问题,读已提交侧重的是修改,还是存在不可重复读和幻读的问题
  3. 可重复读(REPEATABLE READ) :针对的是上面不可重复读的问题,事务A此时查询数据发现表中只有一条数据,然后事务B又过来拆台了,事务B又插入了一条数据,事务A再次查询发现,哎!我出现幻觉了吗,刚刚不是只有一条数据么,现在咋又变成了两条数据了,是出现幻觉了吗,这个问题就叫 幻读,可重复读侧重的是添加和删除,只存在幻读的问题了
  4. 串行化(SERIALIZABLE):事务最高的隔离级别,解决了脏读、不可重复读、幻读的问题,但这个级别执行效率低
事务隔离级别脏读不可重复读幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED)
可重复读(REPEATABLE READ)
串行化(SERIALIZABLE)

Spring 事务隔离级别(5种)

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。

注意事项:

  1. 当 Spring 中设置了事务隔离级别和连接的数据库(MySQL)事务隔离级别发送冲突的时候,以Spring为准
  2. Spring 中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础

4. Spring事务传播机制

4.1 事务传播机制是什么

Spring 事务传播机制:多个事务在相互调用时,事务是如何传递的

4.2 为什么需要事务传播机制

事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性的)

而事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题,如下图所示:

 

4.3 事务传播机制有哪些

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
     
以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:

 

 4.4 Spring 事务传播机制使用

4.4.1 支持当前事务(REQUIRED 默认)

UserController:

@Autowiredprivate UserService userService;
@Autowiredprivate LogService logService;@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);if (result>0){//日志logService.add();}/*try {int num = 10 / 0;} catch (Exception e) {System.out.println(e.getMessage());*//*throw e;*//*TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}*/return result;}

UserService:

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer add(UserInfo userInfo) {int result = userMapper.add(userInfo);System.out.println("用户添加:" + result);return result;}}

LogService :

@RestController
public class LogService {//捣乱,设置一个算数异常@Transactional(propagation = Propagation.REQUIRED)public int add() {int num = 10 / 0;return 1;}
}

测试REQUIRED我们可以看到logservice中是有错误的,我们先添加了userService.add(userinfo)事务,当我们加入了log service事务出问题了,虽然之前已经添加成功了,但是应该也是回滚,如果都回滚那么就是ok的即add2没有添加数据库那么说明符合预期,当数据成功添加到数据库是就代表出错了

 发现回滚了,这个就是加入事务,将自身变成整体的一部分,自身出错了那么整体就会出问题

执行流程描述:

  1. UserService中的保存方法正常执行
  2. LogService 保存日志程序报错,因为使用的是 Controller 中的事务,所以整个事务回滚。
  3. 数据库中没有插入任何数据,也就是步骤1中的用户插入方法也回滚了。

4.4.2 嵌套事务(NESTED)

UserService:

@Controller
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactional(propagation = Propagation.NESTED)public Integer add(Userinfo userinfo){int result = userMapper.add(userinfo);System.out.println("用户添加:" + result);return result;}
}

UserController:

 @Transactional(propagation = Propagation.NESTED)// 声明式事务(自动提交)@RequestMapping("/add2")public Integer add2(Userinfo userinfo){// 非空校验if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername())|| !StringUtils.hasLength(userinfo.getPassword())) {return 0;}int result = userService.add(userinfo);if (result > 0){logService.add();}//        System.out.println("add2:" + result);return result;}

LogService:

@Service
public class LogService {@Transactional(propagation = Propagation.NESTED)
//    @Transactional = @Transactional(propagation = Propagation.REQUIRED)
//    @Transactional默认的是Propagation.REQUIREDpublic int add(){int num = 10/0;return 1;}
}

最终执行结果,用户表和日志表都没有添加任何数据。

发现嵌套事务也是没有问题的

但是当我们去让其手动回滚时:

LogService:

@Service
public class LogService {@Transactional(propagation = Propagation.NESTED)
//    @Transactional = @Transactional(propagation = Propagation.REQUIRED)
//    @Transactional默认的是Propagation.REQUIREDpublic int add(){try {int num = 10/0;} catch (Exception e) {TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return 1;}
}

 

没有报错且添加成功没有回滚

这就是嵌套事务的特点,相当于logservice是临时工,出事了开了就行了,不会影响到整体


 

4.4.5 嵌套事务和REQUIRED事务的区别

  1. 整个事务如果全部执行成功,二者的结果是⼀样的。

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

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

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

相关文章

前端行级元素和块级元素的基本区别

块级元素和行内元素的基本区别是&#xff0c; 行内元素可以与其他行内元素并排&#xff1b;块级元素独占一行&#xff0c;不能与其他任何元素并列&#xff1b; 下面看一下&#xff1b; <!DOCTYPE html> <html> <head> <meta charset"utf-8"&…

2023 CCPC 华为云计算挑战赛 D-塔

首先先来看第一轮的 假如有n个,每轮那k个 他们的高度的可能性分别为 n 1/C(n,k) n1 C(n-(k-11),1)/C(n,k) n2 C(n-(k-21),2)/C(n,k) ni C(n-(k-i1,i)/C(n,k) 通过概率和高度算出第一轮增加的期望 然后乘上m轮增加的高度加上初始高度&#xff0c;就是总共增加的高度 下面是…

Linux系统之iptables应用SNAT与DNAT

Linux系统之iptables应用SNAT与DNAT SNAT1、SNAT的原理介绍1.1SNAT的应用环境1.2SNAT的原理 1.3SNAT转化的前提条件 2、开启SNAT2.1临时打开2.2永久打开 3、SNAT的转换3.1固定的公网IP地址3.2非固定的公网IP地址(共享动态IP地址) DNAT1、DNAT的原理介绍1.1应用环境1.2DNAT原理1…

RISC-V(1)——RISC-V是什么,有什么用

目录 1. RISC-V是什么 2. RISC-V指令集 3. RISC-V特权架构 4. RiscV的寄存器描述 5. 指令 5.1 算数运算—add/sub/addi/mul/div/rem 5.2 逻辑运算—and/andi/or/ori/xor/xori 5.3 位移运算—sll/slli/srl/srli/sra/srai 5.4 数据传输—lb/lh/lw/lbu/lhu/lwu/sb/sh/sw …

Hadoop入门机安装hadoop

0目录 1.Hadoop入门 2.linux安装hadoop 1.Hadoop入门 定义 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。充分利用集群的威力进行高速运算和存储。 优势 高可靠性&#xff1a;Hadoop底层维护多…

Docker搭建elasticsearch+kibana测试

最近需要做大数据画像&#xff0c;所以先简单搭建一个eskibana学习使用&#xff0c;记录一下搭建过程和遇到的问题以及解决办法 1.拉取es和kibana镜像 在拉取镜像之前先搜索一下 elasticsearch发现是存在elasticsearch镜像的&#xff0c;我一般习惯性拉取最新镜像&#xff0c…

验证码识别DLL ,滑块识别SDK,OCR图片转文字,机器视觉找物品

验证码识别DLL ,滑块识别SDK 你们用过哪些OCR提取文字&#xff0c;识图DLL&#xff0c;比如Opencv,Labview机器视觉找物品之类&#xff1f;

2023国赛数学建模思路 - 案例:随机森林

文章目录 1 什么是随机森林&#xff1f;2 随机深林构造流程3 随机森林的优缺点3.1 优点3.2 缺点 4 随机深林算法实现 建模资料 ## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林&#xff…

STM32CubeMX配置STM32G0 Standby模式停止IWDG(HAL库开发)

1.打开STM32CubeMX选择好对应的芯片&#xff0c;打开IWDG 2.打开串口1进行调试 3.配置好时钟 4.写好项目名称&#xff0c;选好开发环境&#xff0c;最后获取代码。 5.打开工程&#xff0c;点击魔术棒&#xff0c;勾选Use Micro LIB 6.修改main.c #include "main.h"…

如何批量加密PDF文件并设置不同密码 - 批量PDF加密工具使用教程

如果你正在寻找一种方法来批量加密和保护你的PDF文件&#xff0c;批量PDF加密工具是一个不错的选择。 它是一个体积小巧但功能强大的Windows工具软件&#xff0c;能够批量给多个PDF文件加密和限制&#xff0c;包括设置打印限制、禁止文字复制&#xff0c;并增加独立的打开密码。…

jenkins Linux如何修改jenkins 默认的工作空间workspace

由于jenkins默认存放数据的目录是/var/lib/jenkins&#xff0c;一般这个var目录的磁盘空间很小的&#xff0c;就几十G,所以需要修改jenkins的默认工作空间workspace 环境 jenkins使用yum安装的 centos 7 正题 1 查看jenkins安装路径 [rootlocalhost jenkins_old_data]# rpm…

4.5 TCP优化

TCP 三次握手的性能提升 三次握手的过程在一个 HTTP 请求的平均时间占比 10% 以上&#xff0c;所以要正确使用三次握手的中参数&#xff0c;需要先用netstat命令查看是哪个握手阶段出了问题&#xff0c;主动发起连接的客户端优化相对简单些&#xff0c;而服务端需要监听端口&a…