Spring @Transactional事务传播行为详解

目录

一、无事务情况

二、有事务情况

REQUIRED

SUPPORTS

MANDATORY

REQUIRES_NEW

NOT_SUPPORTED

NEVER

NESTED


Spring的事务传播机制用于控制在多个事务方法相互调用时事务的行为。

在复杂的业务场景中,多个事务方法之间的调用可能会导致事务的一致性,如出现数据丢失、重复提交等问题,使用事务传播机制可以避免这些问题的发生,保证事务的一致性和完整性。

Spring的事务规定了7种事务的传播级别,默认的传播机制是REQUIRED

一、无事务情况

我们先看一段代码:

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}


public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}


public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

当我们运行testPropagationTrans方法,发现数据库是这样的

当saveChildren方法中有异常,也不会做事务回滚,其实也正常,因为我们也没加事务。

二、有事务情况

注:我们为了好理解,把testPropagationTrans叫做父方法,saveParent和saveChildren叫做子方法。

REQUIRED

**REQUIRED:**如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行。

**举例:**领导没饭吃,我有钱,我会自己买了自己吃;领导有的吃,会分给你一起吃。

比如我们代码如下;

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.REQUIRED)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

这样的结果很好理解,我在子方法有事务,当然只管我自己的回滚,并不管其它的,正所谓我有钱我自己买饭吃,别人我不管。

但是当你把@Transactional(propagation = Propagation.REQUIRED)放在父方法上,那么数据库里将会什么数据都没有,因为父方法有事务,子方法会加入此事务,相当于都包在里面了,所以有异常则全部回滚。

SUPPORTS

**SUPPORTS:**有事务则加入事务,没有事务则普通执行。

**举例:**领导没饭吃,我也没饭吃;领导有饭吃,我也有饭吃。

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.SUPPORTS)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

很明显,如果父方法没有事务,那就相当于都没事务,就正常运行了。

但如果父方法有@Transactional(propagation = Propagation.REQUIRED)注解,则加入父方法事务。

MANDATORY

**MANDATORY:**强制有事务,没有事务则报异常。

**举例:**领导必须管饭,不管饭我就抛出异常。

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.MANDATORY)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

这样系统直接抛出异常,因为父方法没有事务。

REQUIRES_NEW

**REQUIRES_NEW:**每次执行新开一个事务,如果当前存在事务,则把当前事务挂起。

**举例:**领导管饭,但我偏不要,我自己买了自己吃,如果领导不管饭,我自己也有钱买饭。

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

结果说明,子方法自己创建了一个新的事务自己用。

如果我父方法上加了事务,这时数据库会发现什么数据都没有。

但我们再考虑一种情况,代码如下:

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
	int a = 1 / 0;
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
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);
}

我们把异常代码放在父方法中,这时数据库结果发现:

这个结果是如何造成的呢?

其实当执行代码时,子方法创建了一个新的事务,并把父方法的事务挂起,所以也就是说,子事务是子事务,父事务是父事务,这是两个事务。

所以当父方法有异常时,直接回滚saveParent方法,并不影响子事务的操作。

NOT_SUPPORTED

**NOT_SUPPORTED:**如果当前有事务,则把事务挂起,自己不使用事务去运行数据库操作。

**举例:**领导管饭,分一点给你,但你太忙了,放一边不吃。

java 复制代码
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

看的出来,压根没使用事务。

当我在父方法上加上事务。

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

这个结果说明子方法没有事务,而且还把父事务挂起,所以父有事务,导致saveParent方法可以回滚。

NEVER

**NEVER:**如果当前有事务存在,则抛出异常。

**举例:**领导管饭,我不想吃,我抛出异常。

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NEVER)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

代码直接报错异常,因为never不允许你(父)有事务。

NESTED

**NESTED:**如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚;如果当前没有事务,则同REQUIRED。但是如果主事务提交,则会携带子事务一起提交。

如果主事务回滚,则子事务一起回滚,相反,子事务异常,则父事务可以回滚或不回滚。

**举例:**领导决策不对,老板怪罪,领导带着小弟一同受罪。小弟出了差错,领导可以推卸责任,也可以承担责任。

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
	int a = 1 / 0;
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NESTED)
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);
}

结果数据库什么数据都没有。因为父有事务,当父方法有异常,里面的嵌套事务要被回滚。

但如果子事务发生异常呢?

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	stuService.saveChildren();
}

public void saveParent() {
	Stu stu = new Stu();
	stu.setName("parent");
	stu.setAge(30);
	stuMapper.insert(stu);
}

@Transactional(propagation = Propagation.NESTED)
public void saveChildren() {
	saveChild1();
	int a = 1 / 0;
	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);
}

结果数据库还是什么数据都没有,这是因为子事务的异常会影响到父方法。但这个是可选的,我们可以给父方法调用加异常,这样就不会影响父方法事务了,如下:

java 复制代码
@Transactional(propagation = Propagation.REQUIRES)
public void testPropagationTrans() {
	stuService.saveParent();
	try {
		stuService.saveChildren();
	}catch (Exception e) {
		e.printStackTrace();
	}
}
相关推荐
一丝晨光几秒前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
无名指的等待71224 分钟前
SpringBoot中使用ElasticSearch
java·spring boot·后端
Tatakai2544 分钟前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
武子康1 小时前
大数据-133 - ClickHouse 基础概述 全面了解
java·大数据·分布式·clickhouse·flink·spark
.生产的驴1 小时前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
Code哈哈笑1 小时前
【C++ 学习】多态的基础和原理(10)
java·c++·学习
chushiyunen1 小时前
redisController工具类
java
A_cot1 小时前
Redis 的三个并发问题及解决方案(面试题)
java·开发语言·数据库·redis·mybatis
刘某某.2 小时前
使用OpenFeign在不同微服务之间传递用户信息时失败
java·微服务·架构
alden_ygq2 小时前
GCP容器镜像仓库使用
java·开发语言