Spring事务和事务传播机制

目录

一. Spring 中事务的实现

1. 编程式事务

2. 声明式事务

 3. @Transaction 参数的设置

4. @Transaction 的隔离级别

5. @Transaction 的工作原理 

二. Spring 事务传播机制

七种事务传播机制

 支持当前事务

 不支持当前事务

 嵌套事务


一. Spring 中事务的实现

1. 编程式事务

编程式事务,主要就是三个步骤:

1. 开启事务;

2. 提交事务;

3. 回滚事务;

在 SpringBoot 中内置了两个对象:DataSourceTransactionManager 用来开启事务,提交事务和回滚事务,TransactionDefinition 是事务的属性,在开启事务的时候,就需要传入此对象,从而获得一个事务 TransactionStatus;


@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;//    编程式事务     要引入两个内置的对象
//    整个事务的操作,是需要到这个事务管理器对象的:DataSourceTransactionManager@Autowiredprivate DataSourceTransactionManager transactionManager;@Autowired
//    事务定义器,通过这个来设置事务的一些东西,要得到一个事务,就必须传递此参数private TransactionDefinition transactionDefinition;@RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) {return 0;}
//          开启事务TransactionStatus transactionStatus = null;int result = 0;try {
//            开启事务transactionStatus = transactionManager.getTransaction(transactionDefinition);//            业务操作,删除用户result = userService.del(id);System.out.println("删除:" + result);
//           正常情况下,提交事务transactionManager.commit(transactionStatus);} catch (Exception e) {
//           如果出现异常了,回滚事务, 或者事务还没创建,就不用回滚if (transactionStatus != null) {transactionManager.rollback(transactionStatus);}}return result;}
}

2. 声明式事务

相比于编程式事务,声明式事务就方便得多了,只需要在方法或类上添加注解 @Transaction

不需要手动开启和提交事务,进入方法就自动开启事务,方法执行完就自动提交事务,如果中途发生没有处理的异常,就会自动回滚事务;

需要注意的是:

@Transactional 修饰方法的时候,只能修饰在 public 方法上;

修饰类的时候,表示该注解对类中的所有 public 方法生效; 

@RestController
@RequestMapping("/user2")
public class UserController2 {@Autowiredprivate UserService userService;@RequestMapping("/del")@Transactional      // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务public int del(Integer id){if (id == null || id <= 0){return 0;}int result = userService.del(id);System.out.println("删除:"+result);int num = 10 / 0;        // 制造算数异常                  return result;}}

 此时因为在运行过程中,出现了算数异常,所以事务会进行回滚,页面会以 500 的形式返回;

但如果对于异常进行 try/catch 处理的时候,情况就不一样了,如果是我们自己去抓取了异常,没有让异常抛出,此时就相当于是我们自己把异常消化了,外部监测不到,就不会回滚事务了,而是正常提交事务; 

对此就有两种解决方法:

1. 将异常抛出,让框架感知到异常,当框架感知到异常之后,就会自动进行回滚;

2. 手动进行回滚事务;

       try {int num = 10 / 0;}catch (Exception e){
//            e.printStackTrace();          // 不回滚
//            throw e;                      // 回滚
//            手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}

 3. @Transaction 参数的设置

4. @Transaction 的隔离级别

在前面的文章中,有说到事务的四大特性:原子性,一致性,持久性,隔离性。在隔离性中,也有五种隔离级别:

1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主;
2. Isolation.READ_UNCOMMITTED:读未提交;
3. Isolation.READ_COMMITTED:读已提交;
4. Isolation.REPEATABLE_READ:可重复读;
5. Isolation.SERIALIZABLE:串行化;
此处的细节内容可以见前面的文章。

5. @Transaction 的工作原理 

@Transaction 是基于 AOP 来实现的,AOP 又是使用动态代理来实现的,如果目标对象实现了接口, 默认情况下会采用 JDK 的动态代理,如果目标对象没有实现接口,会采用 CGLIB 动态代理;

@Transaction 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务,如果中途遇到异常,则回滚事务;

二. Spring 事务传播机制

Spring 事务传播机制定义了多个包含了事务的方法,相互调用的时候,事务是如何在这些方法间进行传递的。

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

 

事务传播机制解决: 

七种事务传播机制

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。

也可以进行整体划分:

此处写一个 添加用户和添加日志 的案例进行演示:

首先在 Controller 层中的 add 方法进行 @Transaction 注解添加,然后调用 Service 层的 add 方法,Service 层的 add 方法也添加了 @Transaction 注解,然后调用 logService.add() 方法进行日志打印,logService.add() 方法也添加了 @Transaction 注解;

( 此处为了方便分析,用 Controller 调用了 Service,后 Service 再次调用 Service 层的方法)

 支持当前事务

@RestController
@RequestMapping("/user3")
public class UserController3 {@Autowiredprivate UserService userService;@RequestMapping("/add")@Transactional(propagation = Propagation.REQUIRED)public int add(String username,String password){if (null == username || null == password ||username.equals("") || password.equals("")) return 0;UserInfo user = new UserInfo();user.setUsername(username);user.setPassword(password);int result = userService.add(user);
//        外部事务的回滚,不会报错误
//        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)public int add(UserInfo userInfo){
//        给用户表添加用户信息int addUserResult = userMapper.add(userInfo);System.out.println("添加用户结果:"+addUserResult);//        添加日志信息LogInfo logInfo = new LogInfo();logInfo.setMessage("添加用户信息");logService.add(logInfo);return addUserResult;}
}
@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRED)public int add(LogInfo log){int result = logMapper.add(log);System.out.println("添加日志:" + result);
//        设置回滚操作    内部事务的回滚会报错误TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}

此时的事务传播机制属于 支持当前事务 , 也就是可以将三个事务看为是一个事务,同时提交,同时回滚,在程序中,前两个事务都正常运行,然后最后一个事务进行回滚,此时整个事务都会进行回滚,也就是既没有添加用户,也没有添加日志。

此处需要注意:内部事务的回滚会以报错误的形式出现,显示 500 页面,外部事务的回滚则不会,上述是内部事务的回滚,所以显示的页面是:

 不支持当前事务

将 LogService 中的 add 方法,也就是日志添加的方法,隔离级别设为 REQUIRES_NEW 。

此时该事务就与前面两个事务没有任何联系,不在一个调用链上的事务,各自执行相互不干扰。独自进行提交事务,回滚事务。

@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public int add(LogInfo log){int result = logMapper.add(log);System.out.println("添加日志:" + result);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}

此时在该方法中进行回滚操作,外部事务都不回滚,看一下执行结果:日志记录没有添加,而用户信息添加了;

需要补充的是:在 不支持当前事务 的机制中,虽然两个事务不在一个调用链上,但如果内层事务出现了异常,而没有及时处理,让外层的 Controller 和 Service 都感知到了,会导致整个程序报错,整个调用链都是500,虽然都是独自的业务,但是感知到异常的时候,都是会进行回滚的;

按照上述代码,如果在 LogService 中 (或者 UserService )的 add 方法加入 int num = 100/0 语句形成算数异常,此时内层事务对异常的未处理会导致外层也受影响,页面显示 500,日志 和 用户信息 都无法添加成功; 

但如果是外层事务的异常未处理,也会造成 500 页面显示,但不会影响到内层事务的业务执行;

按照上述代码,如果在 UserController3 中的 add 方法加入 int num = 100/0 语句形成算数异常,此时日志可以正常添加,而用户信息无法添加,因为异常的出现而回滚事务了,页面显示为 500;

 嵌套事务

嵌套事务,在进行回滚的时候,就只会影响到自己的内层事务,外层事务是影响不到的,也就是不会回滚嵌套之前的事务;

在上述代码中,LogService 中的事务,就算是 UserService 的内层事务,所以 UserService 中的事务设置为 嵌套事务 的时候,进行回滚就只会影响到 LogService 中的事务,会一起回滚,而最外层的 UserController 事务不会被影响到。

得到的结果也就是:用户信息添加失败,日志信息添加失败;

如果是对最里层事务 LogService 设置为 嵌套事务,回滚发生在 LogService 中,那么此时用户信息是可以添加成功的,但日志信息添加失败;

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.NESTED)public int add(UserInfo userInfo){
//        给用户表添加用户信息int addUserResult = userMapper.add(userInfo);System.out.println("添加用户结果:"+addUserResult);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//        添加日志信息LogInfo logInfo = new LogInfo();logInfo.setMessage("添加用户信息");logService.add(logInfo);return addUserResult;}
}

嵌套事务之所以能实现部分事务的回滚,是因为事务中有一个保存点的概念,嵌套事务进入之后就相当于新建了一个保存点,而回滚的时候,只会回滚到保存点,因此之前的事务是不受影响的。

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

1. 如果事务全部执行成功,二者的结果是一样的;

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

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

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

相关文章

mmdetection基于 PyTorch 的目标检测开源工具箱 入门教程

安装环境 MMDetection 支持在 Linux&#xff0c;Windows 和 macOS 上运行。它需要 Python 3.7 以上&#xff0c;CUDA 9.2 以上和 PyTorch 1.8 及其以上。 1、安装依赖 步骤 0. 从官方网站下载并安装 Miniconda。 步骤 1. 创建并激活一个 conda 环境。 conda create --name…

Docker是什么?详谈它的框架、使用场景、优势

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、什么是 Docker&#xff1f; 二、Docker 的架构 1、Docker客户端 2、Docker守护进程 3、Docker镜像 4、Docker容器 5、Docker…

【业务功能篇76】微服务网关路由predicates断言条件-filters路由转换地址-跨域问题-多级目录树化层级设计-mybatisPlus逻辑删除

业务开发-基础业务-分类管理 启动renren-fast如果出现如下错误 -Djps.track.ap.dependenciesfalse 添加相关配置即可 分类管理 1.后端分类接口 JDK8特性&#xff1a;https://blog.csdn.net/qq_38526573/category_11113126.html 在后端服务中我们需要查询出所有的三级分类信…

汽车摩托车零部件出口管理ERP解决方案

近年来&#xff0c;随着全球经济的发展&#xff0c;人们对交通工具的需求增加&#xff0c;国内汽车、摩托车市场的不断扩大&#xff0c;以及国内制造技术的不断提高&#xff0c;中国汽车、摩托车零部件出口业务迎来了广阔的发展前景&#xff0c;带动了汽车配件和摩托车配件市场…

分布式下的session共享问题

首页我们确定在分布式的情况下session是不能共享的。 1.不同的服务&#xff0c;session不能共享&#xff0c;也就是微服务的情况下 2.同一服务在分布式情况&#xff0c;session同样不能共享&#xff0c;也会是分布式情况 分布式下session共享问题解决方案(域名相同) 1.session复…

IDEA常用插件之类Jar包搜索Maven Search

文章目录 IDEA常用插件之类Jar包搜索Maven Search说明安装插件使用方法1.搜索自己要搜的jar包2.根据类名搜索 IDEA常用插件之类Jar包搜索Maven Search 说明 它可以帮助用户快速查找和浏览Maven中央存储库中可用的依赖项和插件。它可以帮助用户更方便地管理项目依赖项。 安装…

idea 对JavaScript进行debug调试

文章目录 1.新增 JavaScript Debug 配置2.配置访问地址3.访问url. 打断点测试 前言 : 工作中接手别人的前端代码没有注释&#xff0c;看浏览器的network或者console切来切去&#xff0c;很麻烦&#xff0c;可以试试idea自带的javscript debug功能。 1.新增 JavaScript Debug 配…

『SEQ日志』在 .NET中快速集成轻量级的分布式日志平台

&#x1f4e3;读完这篇文章里你能收获到 如何在Docker中部署 SEQ&#xff1a;介绍了如何创建和运行 SEQ 容器&#xff0c;给出了详细的执行操作如何使用 NLog 接入 .NET Core 应用程序的日志&#xff1a;详细介绍了 NLog 和 NLog.Seq 来配置和记录日志的步骤日志记录示例&…

UE4/5Niagara粒子特效之Niagara_Particles官方案例:3.3->4.3

目录 3.3 Visibility Tag 左边的发射器&#xff1a; 发射器更新 粒子生成 粒子更新 右边的发射器 和左边发射器不同的地方 3.4 Texture Sampling 发射器更新 粒子生成 粒子更新 4.1Play Audio Per Particle 系统 第三个发射器 发射器更新 粒子生成 粒子更新 第二个…

1268. 搜索推荐系统

链接&#xff1a; 1268. 搜索推荐系统 题解&#xff1a; class Solution { public: struct Trie {Trie() {end false;next.resize(26, nullptr);}bool end;std::set<std::string> words;std::vector<Trie*> next; };void insert_trie(const std::string& w…

Android相机-HAL子系统

引言 应用框架要通过拍照预览摄像获得照片或者视频,就需要向相机子系统发出请求, 一个请求对应一组结果 一次可发起多个请求&#xff0c;并且提交请求是非阻塞的&#xff0c;始终按照接收的顺序以队列的形式先进先出地进行顺序处理 一个请求包含了拍摄和拍照配置的所有信息&…

【音视频处理】转编码H264 to H265,FFmpeg,代码分享讲解

大家好&#xff0c;欢迎来到停止重构的频道。 本期我们讨论音视频文件转编码&#xff0c;如将视频H264转H265等。 内容中所提及的代码都会放在GitHub&#xff0c;感兴趣的小伙伴可以到GitHub下载。 我们按这样的顺序展开讨论&#xff1a;​ 1、 编码的作用 2、 转编码的…

avue-ueditor中隐藏部分工具栏

项目中不需要那么多工具栏,只需要展示部分工具栏 <avue-ueditor v-model"content" v-bind"options" :customConfig"customConfig" :placeholder"placeholder"></avue-ueditor>//按需隐藏或者显示工具栏即可 props: {custo…

C语言基础之——操作符(上)

本篇文章&#xff0c;我们将展开讲解C语言中的各种常用操作符&#xff0c;帮助大家更容易的解决一些运算类问题。 这里提醒一下小伙伴们&#xff0c;本章知识会大量涉及到二进制序列&#xff0c;不清楚二进制序列的小伙伴&#xff0c;可以去阅读我的另一篇文章《数据在内存中的…

6、Spring_Junit与JdbcTemplate整合

Spring 整合 1.Spring 整合 Junit 1.1新建项目结构 1.2导入依赖 导入 junit 与 Spring 依赖 <!-- 添加 spring 依赖--> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version…

MySQL 8.0 多实例的配置应用

文章目录 同版本多实例配置部署、启动、连接 不同版本多实例配置初始化initialize-insecure 含义 启动 同版本多实例 配置 mkdir -p /data/330{7..9}/data chown -R mysql.mysql /data/* cat > /data/3307/my.cnf <<EOF [mysqld] usermysql basedir/usr/local/mysql …

Skywalking Kafka Tracing实现

背景 Skywalking默认场景下&#xff0c;Tracing对于消息队列的发送场景&#xff0c;无法将TraceId传递到下游消费者&#xff0c;但对于微服务场景下&#xff0c;是有大量消息队列的业务场景的&#xff0c;这显然无法满足业务预期。 解决方案 Skywalking的官方社区中&#xf…

深度学习入门(三):卷积神经网络(CNN)

引入 给定一张图片&#xff0c;计算机需要模型判断图里的东西是什么&#xff1f; &#xff08;car、truck、airplane、ship、horse&#xff09; 一、卷积神经网络整体架构 CONV&#xff1a;卷积计算层&#xff0c;线性乘积求和RELU&#xff1a;激励层&#xff0c;激活函数P…

Flutter性能揭秘之RepaintBoundary

作者&#xff1a;xuyisheng Flutter会在屏幕上绘制Widget。如果一个Widget的内容需要更新&#xff0c;那就只能重绘了。尽管如此&#xff0c;Flutter同样会重新绘制一些Widget&#xff0c;而这些Widget的内容仍有部分未被改变。这可能会影响应用程序的执行性能&#xff0c;有时…

16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及JDBC示例(4)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…