【Spring】Spring 中事务的实现

news/2024/5/11 12:13:08

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

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

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)。

1.编程式事务(手动编写代码)

编程式事务有三个步骤:

  • 开启事务(获取事务)。
  • 提交事务。
  • 回滚事务。

SpringBoot 内置了两个对象:

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

实现代码如下:

package com.example.mybatisdemo.controller;import com.example.mybatisdemo.model.User;
import com.example.mybatisdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 手动提交事务*/
@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionController {@Autowiredprivate UserService userService;//获取数据库事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//数据库事务默认配置@Autowiredprivate TransactionDefinition transactionDefinition;@RequestMapping("/addUser")public Integer addUser(String username,String password){//获取一个事务TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);User user=new User(username,password);Integer result = userService.insert(user);log.info("影响行数:"+result);//回滚到transaction状态//dataSourceTransactionManager.rollback(transaction);//事务提交dataSourceTransactionManager.commit(transaction);return  result;}
}

2.声明式事务(利用注解)

声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务,具体实现代码如下:

/*** 使用注解提交事务*/
@Slf4j
@RequestMapping("/trans2")
@RestController
public class TransactionController2 {@Autowiredprivate UserService userService;@Transactional //事务注解//在遇到运行时异常(RuntimeException)和error才会回滚,非运行时异常不回滚@RequestMapping("/addUser")public Integer addUser(String username,String password){User user=new User(username,password);Integer result = userService.insert(user);log.info("影响行数:"+result);//发生异常时,事务会回滚//int a=10/0;return  result;}
}

补充:如果异常被捕获了,事务不会回滚,代码示例:

    @Transactional@RequestMapping("/addUser3")public Integer addUser3(String username,String password) throws Exception {User user=new User(username,password);Integer result = userService.insert(user);log.info("影响行数:"+result);try {int a=10/0;} catch (Exception e){e.printStackTrace();}return  result;}

2.1 @Transactional作用范围

@Transactional 可以⽤来修饰⽅法或类:

  • 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。

  • 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效

2.2 @Transactional参数说明

在这里插入图片描述

rollbackFornoRollbackFor示例代码:

    /*** 指定异常不回滚* @param username* @param password* @return*/@Transactional(noRollbackFor = ArithmeticException.class)@RequestMapping("/addUser")public Integer addUser1(String username,String password){User user=new User(username,password);Integer result = userService.insert(user);log.info("影响行数:"+result);//发生异常时,事务会回滚//int a=10/0;return  result;}/*** 指定异常回滚* @param username* @param password* @return*/@Transactional(rollbackFor = Exception.class)  //所有异常都回滚@RequestMapping("/addUser2")public Integer addUser2(String username,String password) throws Exception {User user=new User(username,password);Integer result = userService.insert(user);log.info("影响行数:"+result);throwException();return  result;}public void throwException() throws Exception{throw new IOException();}

2.3 @Transactional工作原理

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

在这里插入图片描述

3.Spring 中设置事务隔离级别

3.1 事务四大特性ACID

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

原⼦性(Atomicity,或称不可分割性):⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执⾏过程中发⽣错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。

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

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

隔离性(Isolation,⼜称独⽴性):数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化(Serializable)。

3.2 事务的隔离级别

事务的隔离级别有四种:

  1. READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,⽽未提交的数据可能会发⽣回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
  2. READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。(Oracle默认隔离级别)
  3. REPEATABLE READ:可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询的结果⼀致。但也会有新的问题,⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因)。明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这就叫幻读(Phantom Read)。(MySQL默认隔离级别)
  4. SERIALIZABLE:序列化,事务最⾼隔离级别,它会强制事务排序,使之不会发⽣冲突,从⽽解决了脏读、不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多。

在这里插入图片描述
● 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的。
● 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。
● 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。

3.2 Spring中设置事务的隔离级别

Spring 中事务隔离级别包含以下 5 种

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

从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个
Isolation.DEFAULT(以数据库的全局事务隔离级别为主)

Spring 中事务隔离级别只需要设置 @Transactional ⾥的 isolation 属性即可,具体实现代码如下:

@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {// 业务实现
}

4.Spring 中事务传播机制

4.1 事务的传播机制是什么

先来看一个示例:
在这里插入图片描述
此时有3个事务,A调用了B和C,如果C事务执行失败,B事务执行成功,那么B最终能否成功,A能否成功???

答案是在不同的事务传播机制中,结果是不同的。

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类:
在这里插入图片描述

5.Spring 中事务传播机制的使用

5.1 支持当前事务REQUIRED

正常情况演示:
先开启事务,插入用户,再插入日志:

    @Transactional@RequestMapping("/addUser")public boolean addUser(String username,String password){//插入用户表User user=new User(username,password);userService.insert(user);//插入日志表UserLog userLog=new UserLog(username);userLogService.insertLog(userLog);return true;}

UserService实现代码:

    @Transactional(propagation = Propagation.REQUIRED)public Integer insert(User user) {return userMapper.insert(user);}

UserLogService实现代码:

    @Transactional(propagation = Propagation.REQUIRED)public Integer insertLog(UserLog userLog){return userLogMapper.insertLog(userLog);}

执行结果:数据库数据成功插入。

错误情况演示:
先开启事务,插入用户,再插入日志(插入日志异常):

UserLogService实现代码(出现除0异常):

    @Transactional(propagation = Propagation.REQUIRED)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);int a=10/0;return result;}

执行结果:程序报错,数据库没有插入数据。

执行流程描述:

  1. UserService 中的保存⽅法正常执⾏完成。
  2. UserLogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚。

5.2 不支持当前事务REQUIRES_NEW

先开启事务,插入用户,再插入日志(插入日志异常):

    @Transactional@RequestMapping("/addUser")public boolean addUser(String username,String password){//插入用户表User user=new User(username,password);userService.insert(user);//插入日志表UserLog userLog=new UserLog(username);userLogService.insertLog(userLog);return true;}

UserService实现代码:

    @Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(User user) {return userMapper.insert(user);}

UserLogService实现代码:

    @Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);int a=10/0;return result;}

执行结果:用户表数据插入成功,日志表数据插入失败
原因:
在这里插入图片描述

5.3 不支持当前事务NEVER抛异常

先开启事务,插入用户,再插入日志(插入日志异常):

UserService实现代码:

    @Transactional(propagation = Propagation.NEVER)public Integer insert(User user) {return userMapper.insert(user);}

UserLogService实现代码:

    @Transactional(propagation = Propagation.NEVER)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);int a=10/0;return result;}

执行结果:程序报错,执行失败,数据库没有插入数据。

5.4 NESTED嵌套事务

先开启事务,插入用户,再插入日志(插入日志异常):

UserService实现代码:

    @Transactional(propagation = Propagation.NESTED)public Integer insert(User user) {return userMapper.insert(user);}

UserLogService实现代码:

    @Transactional(propagation = Propagation.NESTED)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);int a=10/0;return result;}

执行结果:执行失败,数据库没有插入数据。

代码①:现在修改一下UserLogService实现代码(加入回滚):

    @Transactional(propagation = Propagation.NESTED)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);try {int a = 10 / 0;}catch (Exception e){//设置回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}

执行结果:日志表插入成功,用户表插入失败
原因:在UserLogService加入部分回滚,用户表运行成功,日志表回滚了。

代码②:现在修改一下UserLogService实现代码(将NESTED改为REQUIRED):

    @Transactional(propagation = Propagation.REQUIRED)public Integer insertLog(UserLog userLog){Integer result=userLogMapper.insertLog(userLog);try {int a = 10 / 0;}catch (Exception e){//设置回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}

执行结果:数据表没有插入数据
原因:日志表回滚,用户表也回滚了

5.5 嵌套事务和加入事务的区别

由上面代码①和代码②可得:

嵌套事务(NESTED)和加⼊事务(REQUIRED )的区别:

  • 整个事务如果全部执⾏成功,⼆者的结果是⼀样的。
  • 如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;⽽嵌套事务会局部回滚,不会影响上⼀个⽅法中执⾏的结果。

http://www.mrgr.cn/p/81785766

相关文章

【hive】Install hive using mysql as hive metadata service

文章目录 一. Requirements二. Installing Hive from a Stable Release三. Running Hive四. Running Hive CLI五.Running HiveServer2 and Beeline1. 下载安装mysql2. 下载mysql驱动3. 配置hive-site.xml4. 初始化元数据库5. 通过beeline进行连接 一. Requirements Users are s…

php 生成连续递增的Excel列索引 可以控制多少列

今天遇到需要生成对应的下拉&#xff0c;下拉的类 需要PHP 输出一个数组 如 A、B、C、D 到Z 列后 Excel 的列就变成 AA 、AB、 AC 依次类推 查询得知 Excel 最大列数 16384 最大行数 1048576 下面演示3000列或行 <?php$idx [idx > 0];for ($i …

OpenCV4.3 Java 编程入门:透明度与抠图

1. 基础知识 JPG 格式图片有损压缩和不支持半透明&#xff0c;如果想在图片上添加透明通道&#xff0c;一定不要用 JPG 格式的图片&#xff1b;PNG&#xff1a;既支持3通道RGB图像&#xff0c;也支持4通道RGBA图像&#xff08;红色、绿色、蓝色和透明度&#xff09;&#xff1…

NOSQL之Redis配置及优化

目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 &#xff08;1&#xff09;具有极高的数据读写速度 &#xff08;2&#xff09;支持丰富的…

AI 绘画Stable Diffusion 研究(一)sd整合包v4.2 版本安装说明

部署包作者:秋葉aaaki 免责声明: 本安装包及启动器免费提供 无任何盈利目的 大家好&#xff0c;我是风雨无阻。众所周知&#xff0c;StableDiffusion 是非常强大的AI绘图工具&#xff0c;需要详细了解StableDiffusion的朋友&#xff0c;可查看我之前的这篇文章&#xff1a; 最…

9条建议告诉你如何正确处理PCB设计布线

一、关于PCB布线线宽 1、布线首先应满足工厂加工能力&#xff0c;首先向客户确认生产厂家&#xff0c;确认其生产能力&#xff0c;如图1所示。如客户无要求&#xff0c;线宽参考阻抗设计模板。 图1 PCB板厂线宽要求 2、阻抗模板&#xff0c;根据客户提供的板厚及层数要求&…

页面生成图片或PDF node-egg

没有特别的幸运&#xff0c;那么就特别的努力&#xff01;&#xff01;&#xff01; 中间件&#xff1a;页面生成图片 node-egg 涉及到技术node egg Puppeteer 解决文书智能生成多样化先看效果环境准备初始化项目 目录结构核心代码 完整代码https://gitee.com/hammer1010_ad…

[论文笔记] CLRerNet: Improving Confidence of Lane Detection with LaneIoU

Honda, Hiroto, and Yusuke Uchida. “CLRerNet: Improving Confidence of Lane Detection with LaneIoU.” arXiv preprint arXiv:2305.08366 (2023). 2023.05 出的一篇车道线检测的文章, 效果在CULane, CurveLanes SOTA 文章目录 简介LaneIoULineIoU存在问题为什么使用LaneIo…

06. 管理Docker容器数据

目录 1、前言 2、Docker实现数据管理的方式 2.1、数据卷&#xff08;Data Volumes&#xff09; 2.2、数据卷容器&#xff08;Data Volume Containers&#xff09; 3、简单示例 3.1、数据卷示例 3.2、数据卷容器示例 1、前言 在生产环境中使用 Docker&#xff0c;一方面…

记一次sql注入分析与绕过【一】

下面是来自今天的项目&#xff0c;简单记录一下 手工注入 加单引号sql报错 sql语句如下&#xff0c;可见参数id原本未被引号包裹 SELECT DISTINCT u.* FROM t_user u WHERE u.name like %1% and u.account like %1% and u.state ? order by id desc limit 0,20 多方尝试…

回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GRNN广义回归神经网络多输入单输出回归预测(多指标,多图)效果一览基本介绍程序设计参考资料效果一览 基本介绍 MATLAB实现GRNN广义回归神经网络多输入单输出回归…

Android 面试题 应用程序结构 九

&#x1f525; 核心应用程序 Activity五个状态&#x1f525; Starting-> running-> paused-> stopped-> killed 启动状态&#xff08;Starting&#xff09;&#xff1a;Activity的启动状态很短暂&#xff0c;当Activity启动后便会进入运行状态&#xff08;Running…

HTTP协议+GET/POST区别

1. web开发流程 &#xff08;1&#xff09; HTML、CSS、JS、图片等资源通过浏览器进行整合&#xff0c;最终渲染出所需画面。 &#xff08;2&#xff09;浏览器对Web服务器进行资源请求 浏览器通过url请求资源。【HTTP协议、URL&#xff1a;确定唯一的一个资源】 浏览器请求…

【Linux】进程轻松入门

目录 一&#xff0c; 冯* 诺依曼体系结构 1&#xff0c;存储结构 ​编辑 二&#xff0c; 操作系统 1&#xff0c;概念 2&#xff0c;设计OS的目的 3&#xff0c;定位 4&#xff0c;如何理解 "管理" 5&#xff0c; 总结 三&#xff0c;进程 1. 概念 那么…

保姆级秋招教程之简历篇

大家好&#xff0c;我是千寻哥&#xff0c;个人简历在程序员求职过程中扮演着至关重要的角色。 今天我将详细给大家介绍一下写简历的必备要素和布局&#xff0c;同时强调应避免的“坑”&#xff01; 希望能通过这些技巧&#xff0c;能帮助程序员打造一份出色的简历&#xff0c;…

Java代码连接RabbitMQ服务器

目录 1.添加依赖 2.生产者代码 3.消费者代码 4.效果 1.发送消息 2.消费消息 5.注意 1.添加依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.12.0</version></dependenc…

Rust 数据类型 之 结构体(Struct)

目录 结构体&#xff08;Struct&#xff09; 定义与声明 结构体定义 结构体实例 结构体分类 单元结构体&#xff08;Unit Struct&#xff09; 元组结构体&#xff08;Tuple Struct&#xff09; 具名结构体&#xff08;Named Struct&#xff09; 结构体嵌套 结构体方法…

ChatGPT+MidJourney 3分钟生成你的动画故事

chatgpt是真的火了&#xff0c;chatgpt产生了一个划时代的意义——自chatgpt起&#xff0c;AI是真的要落地了。 chatgpt能做的事情太多了&#xff0c;多到最初开发模型的程序员自己&#xff0c;也没法说得清楚chatgpt都能做啥&#xff0c;似乎只要你能想得到&#xff0c;它都有…

SSM整合

文章目录 Spring Spring MVCMyBatis整合目录结构引入Maven包web.xml文件导入spring_mvc.xml**spring_mvc.xml****中添加**json**返回编码**UTF-8**导入spring_config.xml文件导入mybatis.xml文件创建pojo对象创建Mapper.xml创建Service创建Controller Spring Spring MVCMyBatis…

Linux系统使用(超详细)

目录 Linux操作系统简介 Linux和windows区别 Linux常见命令 Linux目录结构 Linux命令提示符 常用命令 ls cd pwd touch cat echo mkdir rm cp mv vim vim的基本使用 grep netstat Linux面试题 Linux操作系统简介 Linux操作系统是和windows操作系统是并列…