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();
	}
}
相关推荐
方圆想当图灵3 分钟前
缓存之美:万文详解 Caffeine 实现原理(下)
java·redis·缓存
栗豆包17 分钟前
w175基于springboot的图书管理系统的设计与实现
java·spring boot·后端·spring·tomcat
等一场春雨1 小时前
Java设计模式 十四 行为型模式 (Behavioral Patterns)
java·开发语言·设计模式
萧若岚1 小时前
Elixir语言的Web开发
开发语言·后端·golang
Channing Lewis1 小时前
flask实现重启后需要重新输入用户名而避免浏览器使用之前已经记录的用户名
后端·python·flask
Channing Lewis1 小时前
如何在 Flask 中实现用户认证?
后端·python·flask
酱学编程2 小时前
java中的单元测试的使用以及原理
java·单元测试·log4j
我的运维人生2 小时前
Java并发编程深度解析:从理论到实践
java·开发语言·python·运维开发·技术共享
一只爱吃“兔子”的“胡萝卜”2 小时前
2.Spring-AOP
java·后端·spring
HappyAcmen2 小时前
Java中List集合的面试试题及答案解析
java·面试·list