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();
	}
}
相关推荐
TheITSea几秒前
云服务器宝塔安装静态网页 WordPress、VuePress流程记录
java·服务器·数据库
AuroraI'ncoding8 分钟前
SpringMVC接收请求参数
java
Clarify31 分钟前
docker部署go游戏服务器(进阶版)
后端
九圣残炎33 分钟前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
IT书架1 小时前
golang面试题
开发语言·后端·golang
天天扭码1 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
程序猿小柒1 小时前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
机器之心2 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
不爱学习的YY酱2 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
丁总学Java2 小时前
Maven项目打包,com.sun.tools.javac.processing
java·maven