深入理解Spring事务传播机制-原理与实例说明

news2024/10/5 14:46:16

事务传播机制

简化记忆版本

  1. REQUIRED:有事务加入,没有事务创建,Spring默认
  2. MANDATORY:必须在事务中被调用,没有抛异常
  3. SUPPORTS:有事务加入,没有以非事务运行
  4. NOT_SUPPORTED:不需要事务,有事务则挂起,避免回滚。例如记日志,避免日志信息回滚
  5. REQUIRES_NEW:当前方法创建新事务运行,如果有事务则挂起,主要是为了控制敏感资源事务粒度,避免从时间锁,可以局部回滚
  6. NESTED:如果有事务,加入事务但是记录保存点,如果没有事务创建事务,主要作用局部回滚(通过savepoint实现),不挂起已有事务
  7. NEVER:该方法不应该在事务中运行,如果存在事务则抛异常

不同传播机制之间的区别

REQUIRED与MANDATORY:REQUIRED没有事务自己创建,MANDATOR没有事务异常。

传播机制区别
REQUIRED与MANDATORY方法没有事务,REQUIRED自己创建;而MANDATOR抛出异常
REQUIRED与REQUIRES_NEW方法在事务中执行,REQUIRED加入该事务;REQUIRES_NEW会挂起已有的事务,创建自己的事务
NESTED与REQUIRES_NEW方法在事务中执行NESTED加入该事务,并记录回滚点;REQUIRES_NEW挂起已有事务,创建新事务
NESTED与REQUIRED都是有事务加入,没有事务创建,但NESTED比REQUIRED多了保存回滚点
NOT_SUPPORTED与NEVER方法在事务中被调用NOT_SUPPORTED会挂起事务,而NEVER会抛出异常
MANDATORY与NEVER两者相反, 没有在事务中MANDATORY抛出异常,而有事务NEVER抛出异常
SUPPORTS与没有传播机制注解感觉没有区别,发现有区别的朋友可以补充一下

当SUPPORTS方法被NEVER、NOT_SUPPORTED、没有@Transactional注解这些方法调用,不会有事务,和不加注解效果一样。
当SUPPORTS方法被REQUIRED、MANDATORY、REQUIRES_NEW、NESTED这些方法调用时,加入事务,和不加注解效果也一样。

所以SUPPORTS应用场景是啥?

问题测试

REQUIRED与MANDATORY

@Service
public class ServiceA
{
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(){
        methodB();
    }
    
    @Transactional(propagation = Propagation.MANDATORY)
    public void methodB(){}
}

@Service
public class ServiceC
{
    @Resource
    private ServiceA serviceA;

    public void methodC(){
        // 创建一个事务执行方法A
        serviceA.methodA();
        // methodB是MANDATORY,但是methodC没有事务,所以直接抛出异常
        serviceA.methodB();
    }
}

NESTED回滚测试

@Service
public class ServiceA
{
    @Transactional(propagation = Propagation.NESTED)
    public void methodA(){
        // 数据库操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

@Service
public class ServiceB
{
    
    @Transactional(propagation = Propagation.NESTED)
    public void methodB(){
        // 数据库操作
    }
}


@Service
public class ServiceC
{
    @Resource
    private ServiceA serviceA;

    @Resource
    private ServiceB serviceB;

    @Transactional
    public void methodC(){
        serviceA.methodA();
        serviceB.methodB();
    }
}

上面的操作哪些会回滚?

答案是:
methodA会回滚
methodB不回滚

因为methodA、methodB是NESTED,记录了回滚点,所以只有methodA被回滚。

NESTED传播测试

做一点小变动,如果把methodA上的@Transactional(propagation = Propagation.NESTED)注解去掉呢?
放在methodC方法上会怎样?

@Service
public class ServiceA
{
    public void methodA(){
        // 数据库操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

@Service
public class ServiceB
{
    
    @Transactional(propagation = Propagation.NESTED)
    public void methodB(){
        // 数据库操作
    }
}


@Service
public class ServiceC
{
    @Resource
    private ServiceA serviceA;

    @Resource
    private ServiceB serviceB;

    @Transactional(propagation = Propagation.NESTED)
    public void methodC(){
        serviceA.methodA();
        serviceB.methodB();
    }
}

答案是:
methodA会回滚
methodB会回滚

因为methodA没有注解,就相当于使用的是methodC的事务,并且所以回滚点在methodC,
methodB也使用了methodC的事务,也设置了回滚点,但是在methodA中回滚,整个事务都回滚了。

如何只回滚methodB,不回滚methodA呢?

答案是:
将methodA的TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()放在方法放methodB中就可以。
当methodB是NESTED的时候,在其中回滚,因为有回滚点,所以只会回滚methodB中操作,不会影响methodC方法。

REQUIRES_NEW回滚测试

再来一点点小变动:
将methodB的NESTED换为REQUIRES_NEW会怎样?

@Service
public class ServiceA
{
    public void methodA(){
        // 数据库操作
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

@Service
public class ServiceB
{
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(){
        // 数据库操作
    }
}


@Service
public class ServiceC
{
    @Resource
    private ServiceA serviceA;

    @Resource
    private ServiceB serviceB;

    @Transactional(propagation = Propagation.NESTED)
    public void methodC(){
        serviceA.methodA();
        serviceB.methodB();
    }
}

答案是:
methodA会回滚
methodB不回滚

把methodB的NESTED换为REQUIRES_NEW就可以,因为REQUIRES_NEW为挂起原有事务,并创建新的事务,
所以methodC的事务不会影响到methodB的事务。

异常对不同传播机制回滚影响测试

再来一点点小变动:

将methodA中的手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
换为:throw new RuntimeException(“我要异常”);

会怎样呢?

@Service
public class ServiceA
{
    @Transactional(propagation = Propagation.NESTED)
    public void methodA(){
        // 数据库操作
        throw new RuntimeException("我要异常");
    }
}

@Service
public class ServiceB
{
    
    @Transactional(propagation = Propagation.NESTED)
    public void methodB(){
        // 数据库操作
    }
}


@Service
public class ServiceC
{
    @Resource
    private ServiceA serviceA;

    @Resource
    private ServiceB serviceB;

    @Transactional
    public void methodC(){
        serviceA.methodA();
        serviceB.methodB();
    }
}

答案是:
methodA会回滚
methodB会回滚

如果异常这一次把methodB的传播机制换NESTED为REQUIRES_NEW还能生效吗?

答案是:不能。

很难理解?

的确比较绕,其实很简单,因为异常了,根本就没有执行到methodB,就直接回滚了。

事务内部调用

再来点简单点的问题。

// 调用 1
public class ServiceA
{
    @Transactional
    public void methodA(){
        userMapper.insertSelective(UserTO.builder()
                .username("tim")
                .email("test@gmail.com")
                .build());
        methodB();
    }
    
    public void methodB(){
        eventLogMapper.insertSelective(EventLogTO.builder()
                .type((short) 1)
                .message("创建用户")
                .build());
    }
}

这个事务会生效吗?

答案是:会,很简单,在方法methodA中调用methodB,其实和把methodB的方法体放入methodA方法体中没有什么一样。

在这里插入图片描述

// 调用2
public class ServiceA
{
    public void methodA(){
        methodB();
    }
    
    @Transactional
    public void methodB(){}
}

这个事务会生效吗?

答案是:不能,很多朋友都知道,因为没有走代理,Spring的事务必须通过代理。

如何让它生效呢?

不要直接调用,通过Spring的方式来呗。

@EnableAspectJAutoProxy(exposeProxy = true)
ServiceA serviceA = (ServiceA)AopContext.currentProxy();
serviceA.methodB();

或者:

@Resource
private ApplicationContext applicationContext;
ServiceA serviceA = applicationContext.getBean(this.getClass());
serviceA.methodB();

或者:

@Service
public class ServiceA
{
    @Resource
    private ServiceB serviceB;

    public void methodA(){
        serviceB.methodB();
    }
}

@Service
public class ServiceB
{    
    @Transactional
    public void methodB(){}
}

SQL

CREATE TABLE user (
  id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  username varchar(30) DEFAULT NULL COMMENT '姓名',
  age smallint(5) unsigned DEFAULT NULL COMMENT '年龄',
  sex tinyint DEFAULT -1 COMMENT '性别(-1-保密,1-男,2-女)',
  email varchar(255) DEFAULT NULL COMMENT '邮箱',
  status tinyint DEFAULT 1 COMMENT '状态(-1-删除,1-正常)' ,
  create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

CREATE TABLE event_log (
  id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  message varchar(255) DEFAULT NULL COMMENT '信息',
  type smallint(5) unsigned DEFAULT NULL COMMENT '事件类型',
  create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='事件日志';

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

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

相关文章

java学习003

Java数组 Java 语言中提供的数组是用来存储固定大小的同类型元素,这一点和PHP语言的可变数组长度不同。 声明变量数组 首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法: dataType[] arrayRefVar; // 首选的方法 或 …

C/C++ 高效判断素数原理和代码实现

素数判断 素数是质数,指一个数的因数只有1和本身,1不是质数 所以需要在i [2 , x-1] 内判断x%i 是否是0,如果存在整除的,就不是质数 在数据量大时,我们采用更高效的方式,求[2,sqrt(x)]即可 对于一个数x,如…

华为产品测评官-开发者之声 - 华为云CodeArts真实体验感想

华为产品测评官-开发者之声 - 华为云CodeArts真实体验感想 我先是在6月17日参加了华为在深圳举办的开发者大会,后面看到群里发的"2023华为产品测评官-开发者之声"活动,简单看了一下体验活动的具体事情,感觉…

亚马逊卖家如何自己快速处理恶意差评

亚马逊卖家朋友自己遇到被恶意差评不要慌,可以通过大量买家号进行自己处理,方法如下: 1、点击差评下的report abuse按钮 如果是买家恶意差评,评价与实际不符合,商家可以在评价下找到report abuse,点击按钮…

手把手带你实现ChatGLM2-6B的P-Tuning微调

参考文献:chatglm2ptuning 注意问题1:AttributeError: ‘Seq2SeqTrainer’ object has no attribute is_deepspeed_enabl torch.distributed.elastic.multiprocessing.errors.ChildFailedError: 可能是版本太高,可以参考chatglm2的环境

使用Linux Deploy搭建服务器(三)Linux Deploy安装宝塔面板

宝塔面板是一个免费的服务器管理平台,可以在你的服务器上安装它,然后你就能很方便的管理你的服务器,以及搭建各种服务平台,在此基础上我们可以使用内网穿透,搭建出自己的Linux服务器。可以这样一个相对便宜的方式,进行Linux、网站搭建、web服务部署等一系列操作的学习。 …

实测结果公开:用户见证 StarRocks 存算分离优异性能!

StarRocks 在 3.0 版本正式引入了存算分离架构,从 shared-nothing 走向 shared-data,实现了架构上的重大升级。这一升级受到许多用户的高度期待,因为它不仅是企业降本增效的关键,也是 StarRocks 迈向云原生的必经之路。因此&#…

多模态系列论文--VLMO 详细解析

论文地址:VLMO: Unified Vision-Language Pre-Training with Mixture-of-Modality-Experts 论文代码:VLMO VLMO 1 研究动机2 本文贡献1:MOME模型2.1 模型结构2.2 损失函数2.3 结构优点2.4 结构缺点 3 本文贡献2:分阶段的训练策略4…

在DELL/HP server的UEFI mode下指定ISO安装Ubuntu

1.重启系统 在蓝色界面出现提示后选择F2进入 然后保存设置即可 下面是惠普server的iol5界面 输入f9进入system utilities 选择ISO 选择reset

从零开始设计RISC-V处理器——五级流水线之数据通路的设计

系列文章目录 (一)从零开始设计RISC-V处理器——指令系统 (二)从零开始设计RISC-V处理器——单周期处理器的设计 (三)从零开始设计RISC-V处理器——单周期处理器的仿真 (四)从零开始…

五十、Spring

1.Spring概述 1.1 Spring是什么 Spring是分层的 Java SE/EE应用 full-stack(全栈式) 轻量级开源框架。 提供了表现层 SpringMVC和持久层 Spring JDBC Template以及 业务层 事务管理等众多的企业级应用 技术,还能整合开源世界众多著名的第三方框架和类库&#xf…

javascript中使用class和prototype的区别

javascript中使用class和prototype的区别 本文将介绍在 JavaScript 何时使用class以及何时使用prototype。 prototype 首先先介绍一下prototype的概念,在Javascript中,所有的对象都从原型中继承属性和方法。 function Car(brand, vinNumber) {this.b…

实现 Rollup 插件alias 并使用vitest提高开发效率

本篇文章是对 实现 Rollup 插件 alias | 使用 TypeScript 实现库的基本流程 | 使用单元测试提高开发效率 的总结。其中涉及到开发一个组件库的诸多知识点。 实现一个经常用的 rollup 插件 alias 首先执行npm init命令初始化一个package.json文件,因为插件使用了ty…

【大模型】ChatGLM2-6B 快速使用

教程 Bilibili:清华开源ChatGLM2-6B安装使用 手把手教程,轻松掌握 代码:https://github.com/THUDM/ChatGLM2-6B 模型:https://huggingface.co/THUDM/chatglm2-6b、https://cloud.tsinghua.edu.cn/d/674208019e314311ab5c/?p%2Fc…

【Java从入门到大牛】面向对象进阶下篇

🔥 本文由 程序喵正在路上 原创,CSDN首发! 💖 系列专栏:Java从入门到大牛 🌠 首发时间:2023年7月19日 🦋 欢迎关注🖱点赞👍收藏🌟留言&#x1f43…

vue3后台管理系统封装的普通搜索框组件

1.普通搜索框效果 代码&#xff1a;SearchItem.vue <template><div class"searchBox" id"searchBox"><!-- <a-form ref"formRef" name"advanced_search" class"ant-advanced-search-form" :model"f…

【笔记MD】

https://editor.csdn.net/md/?not_checkout1&articleId131798584 这里写自定义目录标题 https://editor.csdn.net/md/?not_checkout1&articleId131798584欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入…

基于PaddleOCR与OpenVINO™的结构化输出Pipeline

飞桨&#xff08;PaddlePaddle&#xff09;是百度自主研发的中国首个开源开放、功能丰富的产业级深度学习平台&#xff0c;以百度多年的深度学习技术研究和业务应用为基础。飞桨深度学习平台集核心框架、基础模型库、端到端开发套件、丰富的工具组件于一体&#xff0c;还包括了…

QGIS绘制一张地图——创建和编辑绘制线要素、由线要素生成面要素、面要素的编辑

前言 我们以描绘北京市市区案例来演示这部分功能。步骤大致如下: 1、按照市区分区的分界线来绘制线要素。 2、根据所绘线要素生成面要素。 3、对生成的面要素做整理编辑。待绘制底图如图所示: 一、创建和编辑绘制线要素 1.1 创建线要素 我们点击新建Shapefile要素按钮,…

ES6——Iterator 和 for...of 循环

Iterator:遍历器 是一接口&#xff0c;为不同的数据结构提供统一的访问机制&#xff0c;只要当前数据结构部署了iterator接口&#xff0c;当前数据结构就可以遍历。 作用&#xff1a;1、为不同的数据结构&#xff0c;提供统一的访问机制 2、使当前数据结构的成员依次被访问 3…