Spring事务(声明式事务)(Spring的事务,Spring隔离级别,事务传播机制)
目录
一、什么是事务,为什么要用事务
二、Spring声明式事务
🍅 1、@Transactional的使用
🎈 事务回滚
🎈注意:异常被捕获,不会发生事务回滚
🍅 2、@Transactional 作⽤范围
🍅 3、@Transactional 参数说明
🍅 4、@Transactional的工作原理
三、事务的特性以及隔离级别(重要)
🍅 事务的特性(ACID)
🍅 Spring隔离级别
四、Spring事务的传播机制
🍅为什么需要事务传播机制
🍅 事务传播机制的种类
🎈Propagation.REQUIRED:
🎈 Propagation.SUPPORTS
🎈 Propagation.NEVER
🎈Propagation.NESTED
一、什么是事务,为什么要用事务
定义:事务就是将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败
为什么要用事务?举例如下:
转账分为两步:
第一步:A账户:-100¥
第二部:B账户:+100¥
如果没有事务,那么,第一步执行成功,第二部失败,就会导致100¥不见了,不知道转哪里去了。但是当有了事务,着两步操作要么一起成功要么一起失败,这样就可以解决这个问题
分类:Spring中的事务操作分为两类:
- 编程式事务(手动写代码操作事务,不常用,复杂)
- 声明式事务(利用注解自动开启和提交事务,常用,简便)
下面主要介绍声明式事务
二、Spring声明式事务
🍅 1、@Transactional的使用
🎈 事务回滚
声明式事务的使用很简单,只需要在方法上添加@Transactional注解就可以实现了
不需要手动开启事务和提交事务,进入方法时自动开启事务,方法执行完就会自动提交事务,如果中途发生了没有处理的异常就会自动回滚事务。
代码示例:
@Data
public class UserInfo {private int id;private String username;private String password;private String photo;private LocalDateTime createtime;private LocalDateTime updatetime;private int state;
}
@Mapper
public interface UserMapper {@Insert("insert into userinfo(username,password) values (#{username},#{password})")int add(UserInfo userInfo);
}
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public int add(UserInfo userInfo){return userMapper.add(userInfo);}
}
@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/add")@Transactionalpublic int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("huhu");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);System.out.println("result:" + result);int num = 10/0;
// 3.将结果给前端return result;}
}
执行以上代码:结果如下,发生算数异常

但是sql执行没有异常:

这时候我们查看以下数据库有没有该数据,有没有进行数据回滚?
图中并没有我添加的数据,由此可见,@Transactional注解进行了数据回滚。
🎈注意:异常被捕获,不会发生事务回滚
问题:当自己把程序捕获以后,代表着事务不会发现程序发生了异常,在这种情况下,事务不会发生回滚。
@RequestMapping("/add")@Transactionalpublic int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("huhu");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);System.out.println("result:" + result);try{int num = 10/0;}catch (Exception e){}
// 3.将结果给前端return result;}

解决方案1: 将异常继续抛出去(代理对象就能感知到异常,也能够回滚到事务)
@RequestMapping("/add")@Transactionalpublic int add(){ // 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("万叶");userInfo.setPassword("123"); // 2.调用service执行添加int result = userService.add(userInfo);System.out.println("result:" + result);try{int num = 10/0;}catch (Exception e){throw e;} // 3.将结果给前端return result;}新增的信息不在里面:万叶的信息
解决方案2:使用代码手动回滚事务
@RequestMapping("/add")@Transactionalpublic int add(){ // 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("万叶");userInfo.setPassword("123"); // 2.调用service执行添加int result = userService.add(userInfo);System.out.println("result:" + result);try{int num = 10/0;}catch (Exception e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();} // 3.将结果给前端return result;}
🍅 2、@Transactional 作⽤范围
@Transactional 可以用来修饰类或者方法(public)
- 修饰方法时,只能应用到public方法上面,否则不生效
- 修饰类时,表明该注解对该类的所有public方法都生效
🍅 3、@Transactional 参数说明
一般情况下,以下参数都是默认的

🍅 4、@Transactional的工作原理
@Transactional是基于AOP实现的,AOP又是基于动态代理实现的。
如果目标对象实现了接口,默认情况下就会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB的动态代理。
@Transactional在开始执行业务之前,通过代理先开始事务,在执行成功之后再提交事务。如果中途遇见异常,则回滚事务。
@Transactional实现思路预览:

@Transactional具体执行细节如下图所示:

三、事务的特性以及隔离级别(重要)
🎄 脏读:一个事务读取到了另一个事务修改的数据以后,后一个事务又进行了回滚操作,从而导致第一个事务读取到的数据是错误的。
🎄 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了
🎄 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务又新增了一部分数据。
🍅 事务的特性(ACID)
🎄 原子性(不可分割性)(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束中间某个环节。事务在执行过程中发生错误,就会回滚到事务开始前的状态,相当这个事务从来没有执行过
🎄 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,包含资料的精确度、串联性以及后续数据库可以自发性的完成预定的工作。
🎄 持久性(Isolation):事务处理结束以后,对数据的修改就是永久的,即便系统故障也不会丢失
🎄 隔离性(Durability):数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以放置多个事务并发执行时由于交叉执行导致数据的不一致。事务的隔离分为不同级别,包括读未提交,读提交,可重复读,串行化。
🍅 Spring隔离级别
Spring事务隔离级别有五种:
🎄 Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
🎄 Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
🎄 Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复度。
🎄 Isolation.REPEATABLE_READ:可重复读,解决了不可重复度,但是存在幻读。
🎄 Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但是性能太低。
默认情况下,是以SQL的事务隔离级别为主的(Isolation.DEFAULT)。
但是当Sping设置了事务隔离级别以后,就会以Spring的事务隔离级别为主。以下就是以SERIALIZABLE为主的。
@RequestMapping("/add")@Transactional(isolation = Isolation.SERIALIZABLE)public int add(){
}
事务的隔离级别保证了多个并发事务执行的可控性。

四、Spring事务的传播机制
定义:事务的传播机制定义了多个包含事务的方法,相互调用时,事务是如何在这些方法间进行传递的。(规定多个事务在相互调用时,事务的执行行为)
🍅为什么需要事务传播机制
事务传播机制是保证一个事务在多个调用方法间的可控性的(稳定性)
事务的传播机制解决的是一个事务在多个节点(方法)中传递的问题:
🍅 事务传播机制的种类
🎄 Propagation.REQUIRED:默认的事务传播级别,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
🎄 Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
🎄 Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
🎄 Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立互不干扰。
🎄 Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
🎄 Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
🎄 Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED
🎈Propagation.REQUIRED:
默认的事务传播级别,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
示例:
UserService:
@Transactionalpublic int add(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("add result -> "+result);insert(userInfo);return result;}@Transactionalpublic int insert(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("insert resullt -> "+ result);int num = 10 / 0;return result;}
Controller:
@RequestMapping("/add")@Transactional(propagation = Propagation.REQUIRED)public int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("万叶");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);return result;}
此时结果是,报错是算数异常,进行了事务回滚,数据库中没有添加任何数据

🎈 Propagation.SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
示例:
UserService
@Transactional(propagation = Propagation.SUPPORTS)public int add(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("add result -> "+result);insert(userInfo);return result;}@Transactional(propagation = Propagation.SUPPORTS)public int insert(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("insert resullt -> "+ result);int num = 10 / 0;return result;}
UserController:
@Transactional(propagation = Propagation.SUPPORTS)public int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("万叶");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);return result;}
当前调用链不存在事务,结果是数据库中添加了两条数据,并且报错是算数异常,但是并没有进行数据回滚

🎈 Propagation.NEVER
以非事务方式运行,如果当前存在事务,则抛出异常

UserController:
@RequestMapping("/add")
//调用链存在事务@Transactional(propagation = Propagation.REQUIRED)public int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("万叶");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);return result;}
USerService:
@Transactional(propagation = Propagation.NEVER)public int add(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("add result -> "+result);insert(userInfo);return result;}@Transactional(propagation = Propagation.NEVER)public int insert(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("insert resullt -> "+ result);int num = 10 / 0;return result;}

说明程序在发现有事务以后,就没有运行了,不存在事务回滚,而是发现事务以后,程序就没有运行,而是直接抛出异常。
🎈Propagation.NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED

示例:
UserService:
注意:为什么要try-catch
如果不try-catch就会报错,报错就会使得整个程序进行回滚,这里为了使得满足NESTED的情况,就进行了try-catch,就能使得回滚到事务保存点。
@Transactional(propagation = Propagation.NESTED)public int add(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("add result -> "+result);return result;}@Transactional(propagation = Propagation.NESTED)public int insert(UserInfo userInfo){int result = userMapper.add(userInfo);System.out.println("insert resullt -> "+ result);try{int num = 10 / 0;}catch (Exception e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
UserController:
@RequestMapping("/add")@Transactional(propagation = Propagation.REQUIRED)public int add(){
// 1.非空判断UserInfo userInfo = new UserInfo();userInfo.setUsername("影");userInfo.setPassword("123");
// 2.调用service执行添加int result = userService.add(userInfo);userService.insert(userInfo);return result;}
结果如下:

相关文章:
Spring事务(声明式事务)(Spring的事务,Spring隔离级别,事务传播机制)
目录 一、什么是事务,为什么要用事务 二、Spring声明式事务 🍅 1、Transactional的使用 🎈 事务回滚 🎈注意:异常被捕获,不会发生事务回滚 🍅 2、Transactional 作⽤范围 🍅 …...
Linux运维面试题(四)之Linux服务管理
Linux运维面试题(四)之Linux服务管理 4.1 SSHSSH的登录验证方式SSH的登陆端口(默认22)和监听设置(/etc/ssh/sshd_config)SSH的登录用户限制(/etc/ssh/sshd_config PermitRootLogin)SSH的登录超时设置(/etc/…...
ChatGPT能否撰写科研论文?
ChatGPT,这款被许多人誉为语言处理领域的“黑马”,究竟能否应用于撰写科研论文?近期,以色列理工学院生物学家兼数据科学家Roy Kishony带领的团队,针对这一问题进行了系列研究,其结果已在《Nature》杂志上发…...
2023 电赛 E 题 K210方案
第一章:K210 介绍 K210芯片是一款基于RISC-V架构的嵌入式人工智能芯片,具备低功耗、高性能的特点。它拥有强大的图像处理和机器学习能力,适用于边缘计算设备和物联网应用。为了方便开发者,K210芯片提供了丰富的外设接口ÿ…...
网络知识介绍
一、TCP 传输控制协议,Transmission Control Protocol。 面向广域网的通信协议,跨域多个网络通信时,为两个通信端点之间提供一条具有如下特点的通信方式: 基于流、面向连接、可靠通信方式、网络状况不佳时尽量降低系统由于重传带…...
MapStruct设置全局的ComponentModel
在mapStruct上边,如果我们要切换成非默认的组件模式,常常要在Mapper注释中添加componentModel "spring",如果类太多的了的话,非常麻烦,有没有更好的方式呢,有的,可以在pom中添加一个…...
LinearAlgebraMIT_6_ColumnSpaceAndNullSpace
这节课的两个重点是column space列空间和null space零空间。 x.1 pre-multiply/left multiply and post-multiply/right multiply 对于pre-multiply/left multiply左乘和post-multiply/right multiply右乘,如果用英文的pre-和post-是比较容易理解的, A…...
出版物经营许可办理 出版物许可地址变更 出版物零售延期
一、出版物零售单位设立所需材料 1、申请书 2、营业执照 3、租赁合同 4、主要负责人身 份证 5、出版物经营许可申请表 二、办理出版物经营许可证所要符合的条件 1、有确定的企业名称和经营范围; 2、有出版物业务的经营场地; 3、有出版物业务的组织机构和发行人员。 三、…...
【LeetCode每日一题】——807.保持城市天际线
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 中等 三【题目编号】 1572.矩阵对角线元素的和 四【题目描述】 给你一…...
JavaScript--Date(日期)对象
介绍和说明 创建一个Date对象并获取当前日期和时间: 使用new Date()语句可以创建一个表示当前日期和时间的Date对象。它将使用客户端设备上的当前日期和时间。例如:const currentDate new Date(); 获取特定日期的年、月、日、小时、分钟、秒࿱…...
一文讲清多线程与多线程同步
1 多线程 1.1 线程的概念 十多年前,主流观点主张在可能的情况下优先选择多进程而非多线程,如今,多线程编程已经成为编程领域的事实标准。多线程技术在很大程度上改善了程序的性能和响应能力,使其能够更加高效地利用系统资源&…...
《Java-SE-第二十六章》之线程池
前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…...
【数据库】将excel数据导入mysql数据库
环境:Windows10 mysql8以上 将你要导入的excel表另存为txt格式 打开txt格式文件,删除表头行并另存为并更改编码方式(由于与数据库的编码不同,会导致导入报错) 通过命令行登录数据库 winr cmd进入 进入装mysql的目录位…...
无涯教程-Lua - repeat...until 语句函数
与 for 和 while 循环(它们在循环顶部测试循环条件)不同,Lua编程中的 repeat ... until 循环语言在循环的底部检查其条件。 repeat ... until 循环与while循环相似,不同之处在于,保证do ... while循环至少执行一次。 repeat...until loop - …...
环形链表 LeetCode热题100
题目 给你一个链表的头节点 head ,判断链表中是否有环。 思路 快慢指针。开始快指针在慢指针前面,当快指针等于慢指针时说明有环,如果快指针指向null时说明无环。 代码 /*** Definition for singly-linked list.* struct ListNode {* …...
使用python将每组两行数据合并一行
1、使用场景 将有规律的每组(一组2行)的单数行和双数行合并为一行,以空格分割。 比如使用pssh批量得出的结果,想让ip行和结果行合并为一行(前提如上所述) [rootk8s-master1 tmp]# pssh -h iplist -i hostname [1] 18:12:42 [SU…...
14-1_Qt 5.9 C++开发指南_网络编程及主机信息查询_HostInfo
Qt 网络模块提供了用于编写 TCP/IP 客户端和服务器端程序的各种类,如用于 TCP 通信的QTcpSocket 和 QTcpServer,用于 UDP 通信的 QUdpSocket,还有用于实现 HTTP、FTP 等普通网络协议的高级类如 QNetworkRequest,QNetworkReply 和Q…...
【iOS】通知原理
我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。 通知的主要流程 通知全…...
创建邮件服务器(小微企业)
这里写自定义目录标题 目的硬件选型:软件选型:coremail (商业版本)postfixumail免费开源版本新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适…...
android app控制ros机器人四(调整界面布局)
半吊子改安卓,记录页面布局调整: 在ros-mobile基础上顶端增加一行,用于显示app名称和logo图像;修改标签页。 添加文字简单,但是替换图标长知识了,开始只是简单的把mipmap各个文件夹下的图片进行替换&…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...




