IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring拾遗(四)——Spring中的事务传播机制 -> 正文阅读

[Java知识库]Spring拾遗(四)——Spring中的事务传播机制

前言

针对spring中的事务传播机制,这个是面试的高频问题,但是往往有时候缺乏实例支撑,很难记住相关内容,这篇博客在实例的基础上,总结一下spring的事务传播机制。

准备工作

通常spring的事务注解是用在方法上,如下所示
在这里插入图片描述

1、相关数据SQL

准备一个数据库,其中有一张数据表,SQL如下

DROP TABLE IF EXISTS `stu`;

CREATE TABLE `stu` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `age` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1221 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2、准备一个基于springboot的项目,并引入测试

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

3、业务代码准备

引入mybatis啥的,这就不介绍了,各位看官百度即可。

服务类的接口和实现

/**
 * autor:liman
 * createtime:2022/4/2
 * comment:基本的CRUD测试接口
 */
public interface StuService {

    //StuService中的两个接口
    //1.saveParent保存parent类的数据
    public void saveParent();
    //2.saveChildren保存child类的数据
    public void saveChildren();

}

/**
 * autor:liman
 * createtime:2022/4/2
 * comment:
 */
@Service("stuService")
public class StuServiceImpl implements StuService {

    //saveParent知乎保存一个Stu数据
    public void saveParent() {
        Stu stu = new Stu();
        stu.setName("parent");
        stu.setAge(19);
        stuMapper.insert(stu);
    }
    //saveChildren会保存两个Child数据
    public void saveChildren() {
        saveChild1();
        saveChild2();
    }

    public void saveChild1() {
        Stu stu1 = new Stu();
        stu1.setName("child-1");
        stu1.setAge(11);
        stuMapper.insert(stu1);
    }
    public void saveChild2() {
        Stu stu2 = new Stu();
        stu2.setName("child-2");
        stu2.setAge(22);
        stuMapper.insert(stu2);
    }
}

用于测试事务的服务类和接口

//TestTransService接口
public interface TestTransService {
    public void testPropagationTrans();
}

//TestTransService接口的实现类
@Service("testTransService")
public class TestTransServiceImpl implements TestTransService {

    @Autowired
    private StuService stuService;

    //外层调用stuService服务类的方法,主要测试这个方法的事务,和stuService中两个方法的事务传播机制,可以称这个方法为父方法,其中调用的目标方法为子方法
    @Override
    public void testPropagationTrans() {
        stuService.saveParent();
        stuService.saveChildren();
    }
}

测试类代码

/**
 * autor:liman
 * createtime:2022/4/3
 * comment: 测试事务
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FoodieApiApplication.class)
public class TransTest {

    @Autowired
    private TestTransService testTransService;

    @Test
    public void myTest(){
        testTransService.testPropagationTrans();
    }

}

至此准备工作完成,可以基于这个简单的实例,来总结一下spring中事务的传播机制是个啥。在目前没有事务的时候,我们运行实例,数据库中会正常存入如下数据
在这里插入图片描述
如果在saveChildren方法中人为构造一个异常

public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

运行会抛异常,同时child-2数据无法存入
在这里插入图片描述

child-2无法存入
在这里插入图片描述

以上的简单实例,会贯穿整篇博客。为了数据显示清晰,建议每次运行实例前,清空数据表中的数据。

spring中的几种事务传播机制

所谓的事务的传播,简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。

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

spring中总共定义了七中事务传播行为,主要是7个枚举值,具体如下
在这里插入图片描述

REQUIRED

REQUIRED的传播行为是spring中默认的传播行为。按照源码中的注释,表示的是当前方法支持当前事务,如果当前不存在事务,则会创建一个事务。直接通过实例来说明吧。

1、父方法开启事务,子方法不开启事务,且存在异常

//父方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法未开启事务
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

在运行抛出异常之后,会发现数据库中没有任何数据存入,这个是因为子方法中存在异常,而导致的父方法的事务回滚,如果使用try-catch将子方法异常处理掉,则会有相关数据保存成功。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    //catch掉子方法的异常之后,父方法并不会执行失败,而这个时候子方法又没有开启事务,故而父方法的事务是成功的,会有相关数据成功保存
    try {
        stuService.saveChildren();
    }catch (Exception e){
          //e.printStackTrace();
    }
}

在这里插入图片描述

因此在实例中,我们重点需要关注的,是子方法的事务传播机制的枚举值

2、父方法无需事务,子方法开启事务,但存在异常

//父方法无需事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

这种情况下,父方法中的数据会保存成功,但是子方法会因为事务回滚未存储任何数据。
在这里插入图片描述

因为对于子方法来说,子方法的执行需要事务,父方法没有事务,子方法就自己创建了一个事务

3、父方法开启事务,子方法也开启事务,且子方法存在异常

//父方法存在事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

这个时候,由于父方法存在事务,而子方法需要事务,子方法的传播机制配置是REQUIRED,这个时候子方法会和父方法共用一个事务(二者是同一个事务),由于子方法执行出现异常,导致整个事务回滚,因此没有任何数据存入成功

SUPPORTS

关于SUPPORTS,源码中的注释是:支持当前事务,如果不存在则以非事务方式执行。从字面意思来理解,就是当前被Transaction注解修饰的方法,对事务是支持的态度,但是如果调用当前方法的父方法没有事务,则当前方法不会按照事务的模式来运行

1、父方法无事务,子方法的事务传播行为为SUPPORT,且存在异常

//父方法无需事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

这个时候,父方法和子方法的运行结果,与没有事务的时候一样。

2、父方法开始事务,子方法的事务传播行为为SUPPORT,且存在异常

//父方法无需事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

由于父方法存在事务,子方法在被父方法调用的时候,存在异常,事务会回滚,这个时候没有任何数据存入成功。

在父方法中catch处理掉子方法的异常,依旧没有任何数据存入成功

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    try {
        stuService.saveChildren();
    }catch (Exception e){
        //e.printStackTrace();
        return;
    }
}

MANDATORY

MANDATORY的中文意思是强制的。从源码的注释中得知,标示当前方法是支持事务的,但是如果当前方法没有在事务环境下运行,则会抛出异常。

1、父方法无事务,子方法的事务传播属性配置为MANDATORY,子方法无异常

//父方法无需事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

这个时候运行的时候会报错
在这里插入图片描述

异常提示也很清楚,当前方法事务传播行为配置为MANDATORY,但程序没有提供任何事务的运行环境,故而子方法抛错。但是这个时候,父方法是没有事务的额,故而数据库中会保存成功一条数据
在这里插入图片描述

2、父方法开启事务,子方法的事务传播属性配置为MANDATORY,子方法无异常

//父方法无需事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

所有数据正常存入

2、父方法开启事务,子方法的事务传播属性配置为MANDATORY,但子方法存在异常

//父方法存在事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

子方法的异常,导致事务回滚,无任何数据存入。即使父方法中catch处理了异常,依旧无法存入任何数据。

REQUIRES_NEW

从源码中的注释,可以得知,REQUIRES_NEW表示当前方法的逻辑会在一个新的事务中运行,但如果调用这个方法的父方法存在事务则当前方法会挂起父方法的事务。

1、父方法无事务,子方法配置为REQUIRES_NEW,且子方法存在异常

//父方法无事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

这个时候,**子方法会单独开启一个事务,但是由于本身存在异常,会导致回滚因此子方法不会保存任何数据,但是父方法由于没有事务,因此会保存一条数据。**从这一点可以看出,子方法的事务和父方法的事务是完全的两个事务

2、父方法开启事务(REQUIRED),子方法配置为REQUIRES_NEW,且子方法存在异常

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

正常来讲,由于子方法的事务和父方法的事务是完全不同的两个事务,子方法的回滚应该不影响父方法,但是由于子方法是抛出异常,会导致父方法也会出现异常,因此这个实例中,不会有任何数据存入如果在父方法中catch处理子方法抛出的异常,则会存入一条parent的数据,这个就不贴图说明了,跑下实例即可。

3、父方法开启事务,但存在异常,子方法配置为REQUIRES_NEW,且子方法无异常

//父方法REQUIRED,但存在异常
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
    int a = 1 / 0;
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

运行结果:子方法的数据存入成功,父方法的数据存入失败。这一点更加说明了子方法的事务和父方法的事务并不是同一个。子方法执行的时候挂起了父方法的事务,子方法执行完成之后,在执行父方法,父方法因为异常回滚。
在这里插入图片描述

为了更好的理解REQUIRES_NEW和REQUIRED的区别,这里在加一个简单的实例

4、父方法开启事务(REQUIRED),但存在异常,子方法配置为REQUIRED,且子方法无异常

//父方法REQUIRED,但存在异常
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
    int a = 1 / 0;
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

这个时候,子方法和父方法,没有任何一个数据保存成功,因为REQUIRED的传播机制下,子方法和父方法用的是同一个事务。

NOT_SUPPORTED

从字面意思可以理解,标示当前方法的运行不支持事务,从源码的注解中可以得知,标示目标方法以非事务的方式执行,如果当前存在事务则会挂起当前事务。

1、父方法无事务,子方法配置为NOT_SUPPORTED,且存在异常

//父方法无事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

运行结果和父方法与子方法都没有事务的时候一样

2、父方法存在事务,子方法配置为NOT_SUPPORTED,且存在异常

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

运行结果
在这里插入图片描述

父方法会因为子方法的异常而回滚无法存入数据,而子方法的运行本身不支持事务,在方法在运行完成之后,会存入一条数据,子方法在运行的时候会挂起父方法的事务。

如果在上面实例的基础上,通过catch处理子方法的异常,则父方法的数据又能正常存入了

NEVER

当前方法无论如何都无法在事务中运行,如果存在当前事务,则会抛出异常。

1、父方法存在事务,子方法配置为NEVER,且存在异常

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

运行结果:无任何数据存入,这个时候运行会抛出异常,异常信息如下。
在这里插入图片描述

由于抛出了异常,子方法由于在调用之前就抛出了异常,因此第一笔数据都无法存入。父方法没有进行异常处理,故而也没有数据存入。当然,如果父方法catch了子方法的异常,则会顺利存入parent的数据。

2、父方法关闭事务,子方法配置为NEVER,且存在异常

//父方法无事务
@Override
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
    saveChild1();
    int a = 1 / 0;
    saveChild2();
}

这个时候和没有事务的时候运行结果一样,这里不再贴出了

NESTED

NESTED的中文意思为内嵌的,嵌套。从源码的注释中,可以看到,表示如果当前方法的运行存在事务,则当前方法会以一个内嵌的事务嵌入到当前事务中,如果当前的运行环境没有事务,则等同于REQUIRED。

1、父方法存在事务,且存在异常,子方法配置为NESTED,不存在异常

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
    int a = 1/0;
}

//子方法开启事务
@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
    saveChild1();
    saveChild2();
}

运行的结果是没有任何数据存入,因为子方法是以内嵌的事务嵌入到父方法的事务中,父方法的事务回滚,子方法的事务也得回滚。

2、父方法存在事务,无异常,子方法配置为NESTED,存在异常

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    stuService.saveChildren();
}

//子方法开启事务
@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
    saveChild1();
    int a = 1/0;
    saveChild2();
}

依旧存储失败,因为子方法内嵌的事务出现异常回滚,导致父方法的事务也回滚,但是这并不表示子方法和父方法的事务是同一个,这里如果在父方法中catch处理掉子方法的异常,父方法的数据依旧会存入成功,主方法的事务并不会受到影响,这个也是NETESTED区别于REQUIRED的一点。

//父方法REQUIRED
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testPropagationTrans() {
    stuService.saveParent();
    try {
        stuService.saveChildren();
    }catch (Exception e){
        e.printStackTrace();
        return;
    }
}

//子方法开启事务
@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
    saveChild1();
    int a = 1/0;
    saveChild2();
}

父方法中通过catch处理掉子方法的异常,依旧能顺利存入数据
在这里插入图片描述

小结

在很多实例的基础上,梳理了一下spring中事务传播机制的问题,这里梳理一下

传播机制含义异常回滚情况
REQUIRED当前方法的运行需要事务。
如果调用当前方法的父方法存在事务,则无需新建事务。
如果调用当前方法的父方法不存在事务,则当前方法会独立创建一个新的事务。
如果子方法因异常回滚,则父方法的事务也会回滚。
如果父方法存在事务,则子方法和父方法共用同一个事务,这个时候即使在父方法中catch处理子方法的异常,父方法的事务依旧会回滚
SUPPORTS当前方法的运行仅仅对事务持支持态度,如果调用该方法的父方法存在事务,则当前方法就在事务环境中运行,如果父方法中不存在事务,则当前方法也不会按照事务的方式运行如果父方法存在事务,则子方法和父方法共用同一个事务,这个时候即使在父方法中catch处理子方法的异常,父方法的事务依旧会回滚
MANDATORY当前方法的运行强制需要在事务的环境下运行,如果没有则直接抛出异常如果父方法存在事务,则子方法和父方法共用同一个事务,这个时候即使在父方法中catch处理子方法的异常,父方法的事务依旧会回滚
REQUIRES_NEW当前方法的逻辑会在一个新的事务中运行,但如果调用这个方法的父方法存在事务则当前方法会挂起父方法的事务。由于子方法和父方法用的不是同一个事务,因此如果父方法catch处理了子方法的异常,父方法的事务不会回滚
NOT_SUPPORTED表示当前方法以非事务的方式执行,如果当前存在事务则会挂起当前事务如果父方法中catch处理了子方法的异常,则子方法的异常并不会影响父方法事务的提交
NEVER当前方法无论如何都无法在事务中运行,如果存在当前事务,则会抛出异常父方法中catch处理了子方法的异常,则子方法的异常并不会影响父方法事务的提交
NESTED如果当前方法的运行存在事务,则当前方法会以一个内嵌的事务嵌入到当前事务中,如果当前的运行环境没有事务,则等同于REQUIRED。父方法事务的回滚会影响到子方法事务的回滚
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 11:57:15  更:2022-04-04 11:58:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:02:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码