关于Spring @Transactional事务传播机制详解

  • Spring事务传播机制
    • 1.什么是事务传播机制?
    • 2.Spring事务传播类型Propagation介绍
    • 3.具体案例
  • 总结

Spring事务传播机制

1.什么是事务传播机制?

举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

简单说就是,我们方法调用通常是,一个方法调用另外一个,而不同方法可以有不同的事务,所以传播机制就是指在多个方法,事务要如何传播。

2.Spring事务传播类型Propagation介绍

一共有七种传播类型

  • Propagation.REQUIRED
  • Propagation.SUPPORTS
  • Propagation.MANDATORY
  • Propagation.REQUIRED_NEW
  • Propagation.NOT_SUPPORTED
  • Propagation.NESTED
  • Propagation.NEVER

本文从案例结合解释一下不同传播类型下多个@Transactional方法会发生什么?在遇到异常情况下,不同传播机制会产生什么影响。

1. Propagation.REQUIRED

这是默认的传播机制,我们最常用的一种,也是@Transactional默认的一种

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

1

2

3

4

5

6

7

8

9

10

11

12

// 示例1:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.REQUIRED)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

简单来说就是,开启一个事务,上面的案例就是当main方法如果没开启事务,那么sub方法就会开启,如果main方法已经@Transactional开启了事务,sub方法就会加入外层方法的事务,所以上面方法执行在遇到异常时候会全部回滚

结果:

A、B、C全部无法插入。

1

2

3

4

5

6

7

8

9

10

11

// 示例2:

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.REQUIRED)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

结果:

A插入成功,BC开启新的事务,遇到异常回滚,B、C无法插入

2. Propagation.SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

1

2

3

4

5

6

7

8

9

10

11

// 示例3:

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.SUPPORTS)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

这个和REQUIRED很像,但是里层的sub方法事务取决于main方法,如果main方法有开启那么里面的就和外层事务一起,如果发生异常全部回滚。

结果:

A、B插入成功,C无法插入因为发生异常

3. Propagation.MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

1

2

3

4

5

6

7

8

9

10

11

// 示例4:

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.MANDATORY)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

这种情形的执行结果就是insertA存储成功,而insertB和insertC没有存储。b和c没有存储,并不是事务回滚的原因,而是因为main方法没有声明事务,在去执行sub方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以sub方法里的内容就完全没有执行。

结果:

A插入成功,B、C无法插入,方法抛出异常

那么当main方法有事务的情况下

1

2

3

4

5

6

7

8

9

10

11

12

// 示例5:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.MANDATORY)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

结果:

A、B、C全部无法插入,A、B回滚

4. Propagation.REQUIRED_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。

1

2

3

4

5

6

7

8

9

10

11

12

// 示例5:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

    throw RuntimeException;     //发生异常抛出

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void sub(){

    insertB();  //插入B

    insertC();  //调用C

因为sub方法会开启一个新的事务,所以main方法抛出的异常并不会影响sub方法的提交

结果:

A插入失败,B、C能插入成功

5. Propagation.NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务

1

2

3

4

5

6

7

8

9

10

11

12

// 示例6:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

示例6因为当main方法有事务的时候,就会挂起当前事务即main以事务运行,sub不以事务运行

所以最终结果:

A因为sub抛出异常事务回滚,插入失败,B因为不以事务运行插入成功,C因为遇到异常,后续不会执行,所以插入失败。

1

2

3

4

5

6

7

8

9

10

11

// 示例7:

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.NOT_SUPPORTED)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

示例7这种情况就是所有方法都不会以事务运行,A、B均能插入成功,C无法插入

6. Propagation.NEVER

不使用事务,如果当前事务存在,则抛出异常

1

2

3

4

5

6

7

8

9

10

11

// 示例7:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.NEVER)

public void sub(){

    insertB();  //插入B

    insertC();  //调用C

sub因为是Never所以是不会执行直接抛出错误,所以main的事务遇到异常直接回滚,所以A回滚无法插入,B、C不会插入。

7. Propagation.NESTED

如果当前事务存在,则在嵌套(父子)事务中执行,否则REQUIRED的操作一样(开启一个事务)

1

2

3

4

5

6

7

8

9

10

11

12

// 示例7:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    service.sub();   // 调用其他方法

    throw RuntimeException;     //发生异常抛出

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.NESTED)

public void sub(){

    insertB();  //插入B

    insertC();  //调用C

这个是最需要理解的一种传播机制,要理清楚嵌套(父子)事务,main的是父事务,sub是子事务,main发生异常全部都会回滚。

结果:

A、B、C全部回滚

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// 示例8:

@Transactional(propagation = Propagation.REQUIRED)

public void main(){

    insertA();  // 插入A

    try {

         service.sub();   // 调用其他方法

    } catch (Exception e) {

    }

    insertD();

}

// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用

@Transactional(propagation = Propagation.NESTED)

public void sub(){

    insertB();  //插入B

    throw RuntimeException;     //发生异常抛出

    insertC();  //调用C

示例8,子事务发生异常抛出,但父事务catch掉了,那么这个时候main方法就相当于正常执行没有发生异常,那么就只有子事务回滚。

结果:

A、D插入成功,B、C插入失败

  • REQUIRED
    • 内外同一个事务,任何一个地方抛出异常全部一起回滚。
  • REQUIRED_NEW
    • 内部开启一个新的事务,外部事务回滚并不会影响内部的事务,而如果内部事务抛出被catch也不会影响外部事务。

怎么样快速记忆,七个分四组,221这样记,两个一对互相类似

传播类型含义
group1Propagation.REQUIRED如果当前已有事务则加入当前事务,否则开启新的事务
group1Propagation.REQUIRED_NEW无论当前是否有事务都开启新的事务
group2Propagation.SUPPORTED如果当前事务存在就加入事务,否则以非事务运行
group2Propagation.NOT_SUPPORTED始终以非事务方式执行,如果当前存在事务,则挂起当前事务
group3Propagation.NEVER不使用事务,如果当前事务存在,则抛出异常
group3Propagation.MANDATORY当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
group4Propagation.NESTED父子(嵌套)事务,父回滚全回滚,子回滚不影响父事务

3.具体案例

单纯讲案例比较枯燥,会觉得工作中什么情况会使用到呢,这边就举一个例子来讲解一下。

在下单时候,我们最主要是写入订单、然后添加积分,最后记录日志

1

2

3

4

5

6

7

8

9

10

11

12

13

@Service

  public class OrderServiceImpl implements OrderService{

       @Transactional

       public void placeOrder(OrderDTO orderDTO){

           try {

               pointService.addPoint(Point point);

           } catch (Exception e) {

              // 记录错误信息

           }

           //省略...

       }

       //省略...

  }

1

2

3

4

5

6

7

8

9

10

11

12

13

@Service

public class PointServiceImpl implements PointService{

     @Transactional(propagation = Propagation.NESTED)

     public void addPoint(Point point){

         try {

             recordService.addRecord(Record record);

         } catch (Exception e) {

            //省略...

         }

         //省略...

     }

     //省略...

}

1

2

3

4

5

6

7

8

@Service

public class RecordServiceImpl implements RecordService{

     @Transactional(propagation = Propagation.NOT_SUPPORTED)

     public void addRecord(Record record){

         //省略...

     }

     //省略...

}

下单的操作不会影响添加积分的操作,所以我们使用NESTED,下单只要成功,添加积分可以成功或失败,失败的话就错误信息后续补偿。而记录日志我们可以有也可以没有,就可以设置为NOT_SUPPORTED不开启事务,使得事务的方法能尽可能的精简,避免一个很大的事务方法。

总结

本文讲解了Spring事务的七种传播机制,我们可以根据具体的类型,具体设置,避免事务的方法过于长,一个事务里面调用的库表越多,就越有可能造成死锁,所以我们要根据具体的需要拆分使用。

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

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

相关文章

微服务-OpenFeign-工程案例

Ribbon 前置知识 是NetFlix的开源项目,主要来提供关于客户端的负载均衡能力。从多个服务提供方,选取一个节点发起调用。 Feign:NetFlix,SpringCloud 的第一代LB(负载均衡)客户端工具包。 OpenFeign:SpringCloud自研&#xff0c…

什么是 NAS?

一、什么是 NAS? 在数字化时代,小型企业面临着日益增长的数据存储需求。为了应对这一挑战,网络附加存储(NAS)系统成为了许多企业的首选解决方案。NAS系统是一种连接到网络的存储设备,允许授权网络用户和异…

软件测试作业‖pytest+po+csv+html报告+cookie+selenium

软件测试作业‖pytestpocsvhtml报告cookieselenium 先用本地部署的系统试了下 或者UFT自动化测试里诺图书管理系统软件测试 # ,#测试报告# #性能测试#,#测试用例#, #自动化测试# Selenium 的 Web自动化测试基本要求和注意事项 1.请使用ch…

JRT控制打印机

本次测试打印机控制和纸张方向控制。 打印机状态 选择打印机 控制纸张 定义纸张 旋转纸张 不旋转纸张 A4

Oracle清理审计和监听垃圾文件脚本

Oracle用户删除审计文件自动化脚本 $(ORACLE) vi oracle_auto_trace_del.sh#!/bin/bash #ORACLE cd /opt/oracle/app/oracle/admin/SID(数据库实例名)/adump find . -type f -name "*.aud" -mtime 10 |xargs rm -rf$(ORACLE&#…

三、C语言中的分支与循环—goto语句 (10) (完)

在C语言中,goto语句允许程序无条件地跳转到同一函数内的标记位置。这个标记位置通过一个标签和冒号(:)来标示。goto语句可以用于从深层嵌套的循环或条件语句中直接跳出,或者跳过某些代码执行。尽管goto语句在某些情况下可以使程序逻辑变得清晰&#xff0…

C++中互斥量、锁有什么用?

文章目录 互斥量如何理解互斥量C 中互斥量的用法基本锁定和解锁使用 std::lock_guard 自动管理锁使用 std::unique_lock 获得更多控制 注意事项 几种不同类型的锁 创建一个C线程需要传入几个参数? 如何理解和使用C线程循环 C 类 函数 变量 进程 线程 C关于锁和互斥量…

Modbus 通信协议 二

Modbus 常用缩写 通用Modbus帧结构 -应用数据单元(ADU) Modbus数据模型 Modbus ADU 和 PDU 的长度 Modbus PDU结构 串行链路上的 Modbus 帧结构 Modbus 地址规则 ASCLL 模式 和 RTU 模式的比较 RTU 模式 RTU 模式位序列 帧格式 帧的标识与鉴别 CRC 循环冗…

openGauss学习笔记-183 openGauss 数据库运维-升级-升级操作

文章目录 openGauss学习笔记-183 openGauss 数据库运维-升级-升级操作183.1 就地升级和灰度升级操作步骤 openGauss学习笔记-183 openGauss 数据库运维-升级-升级操作 介绍就地升级、灰度升级和滚动升级的详细操作。 183.1 就地升级和灰度升级操作步骤 以root身份登录节点。 …

关于数据降维的几种方法

1.什么是数据降维? 数据降维是指将高维数据转化为低维数据的过程。在现实生活中,我们常常面临高维数据的问题,例如图像数据、文本数据、传感器数据等。高维数据在存储、处理和可视化方面都具有挑战性,而且可能导致过拟合问题。 …

如何使用LightsOut生成经过混淆处理的DLL

关于LightsOut LightsOut是一款功能强大的DLL生成工具,该工具可以帮助广大研究人员轻松生成经过混淆处理的DLL。该工具专为红队研究人员设计,生成的DLL可以在研究人员尝试绕过反病毒产品时禁用AMSI和ETW,从而更好地测试目标系统的安全性。 …

Gin 集成 prometheus 客户端实现注册和暴露指标

前言 当我们构建一个 Web 应用程序时,了解应用程序的性能和行为是非常重要的。Prometheus 是一个流行的开源监控系统,它提供了强大的指标收集和查询功能,可以帮助我们监控应用程序的各个方面。 在 Gin 中集成 Prometheus 可以让我们更方便地监…

Scrapy 1.3.0 使用简介

scrapy 1.3.0 python 2.7 创建一个项目: Before you startscraping, you will have to set up a new Scrapy project. Enter a directory whereyou’d like to store your code and run: scrapy startproject tutorial 然后就会得到一系列文件: 第一个爬…

使用Matplotlib绘制模拟上海城市气温变化图

模拟上海气温变化折线图 实现步骤 准备数据创建画布绘制图像显示图像 基本实现 示例代码: import matplotlib.pyplot as plt import random# 准备数据 x range(60) y_shanghai [random.uniform(15,18) for _ in x]# 创建画布 plt.figure(figure(20,8), dpi10…

Java 基础学习(十九)网络编程、反射

1 Socket编程 1.1 Socket编程概述 1.1.1 Socket简介 在网络编程中,Socket(套接字)是一种抽象概念,它用于在不同计算机之间进行通信。Socket可以看作是一种通信的端点,可以通过Socket与其他计算机上的程序进行数据传…

angular-tree-component组件中实现特定节点自动展开

核心API 都在 expandToNode这个函数中 HTML treeData的数据结构大概如下 [{"key": "3293040275","id": "law_category/3293040275","name": "嘿嘿嘿嘿","rank": 0,"parentKey": "0&q…

盛最多水的容器(力扣11题)

例题: 分析: 这道题给出了一个数组,数组里的元素可以看成每一个挡板,要找到哪两个挡板之间盛的水最多,返回盛水量的最大值。这其实是一个双指针问题。 我们可以先固定第一个挡板( i )和最后一个挡板( j )&#xff0c…

gitee创建仓库

描述 本文章记录了怎么在gitee上创建项目,以及使用vscode提代码到远程呢个仓库,如何创建一个新分支,并将新分支提交到远程仓库。 1、创建远程仓库 在创建远程仓库之前要先进行ssh密钥的设置 (1)打开黑窗口&#xff…

计算机丢失mfc110.dll的5种常用解决方法分享

丢失动态链接库文件(DLL)是比较常见的一种情况,其中之一就是“计算机丢失mfc110.dll”。这个问题通常是由于系统文件损坏或缺失引起的,给计算机的正常运行带来了困扰。为了解决这个问题,我总结了以下五种方法&#xff…

深入解析 迭代器

前言 问:什么是迭代器 ? 答:在C# 中,迭代器是一种设计模式,它允许一个类或集合(比如数组、列表或字典)的实例提供一种遍历其元素的方式。在C#2时引入的迭代器,来简化这一过程。 在C#中有少…