Spring事务传播行为实战

时间:2022-10-28 18:07:54

一、什么是事务传播行为?

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。

例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

二、事务传播行为类型

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。
事务传播行为是Spring框架独有的事务增强特性,这是Spring为我们提供的强大的工具箱,使用事务传播行为可以为我们的开发工作提供许多便利。

两大类

  • 支持事务的传播
  • 不支持事物的传播

七小种

  • REQUIRED:(支持事务)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务(Spring默认)
  • SUPPORTS:(支持事务)如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行
  • MANDATORY:(支持事务)如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常
  • REQUIRES_NEW:(支持事务)创建新事务,无论当前存不存在事务,都创建新事务
  • NOT_SUPPORTED:(不支持事务)如果当前存在事务,就把当前事务挂起
  • NEVER:(不支持事务)以非事务方式执行,如果当前存在事务,则抛出异常
  • NESTED:(支持事务)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就创建一个新事务

三、事务传播行为实战

**说明:**父方法插入表ks_a、子方法插入表ks_b
表结构:

CREATE TABLE `ks_a` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='测试A';

CREATE TABLE `ks_b` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `age` tinyint(4) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='测试B';

1、REQUIRED

1.1、父方法无事务,子方法开启事务,子方法报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.REQUIRED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:ks_a数据插入成功,ks_b数据回滚
Spring事务传播行为实战
Spring事务传播行为实战

1.2、父方法开启事务,子方法开启事务,父方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insert(ksB);
		throw new RuntimeException("主方法报错");
	}
	@Transactional(propagation = Propagation.REQUIRED)
	public void insert(KsB ksB) {
		ksBDao.insert(ksB);
	}

结果:两表数据都回滚了
Spring事务传播行为实战
Spring事务传播行为实战

1.3、父方法开启事务,子方法开启事务,子方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.REQUIRED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:两表数据都回滚了
Spring事务传播行为实战
Spring事务传播行为实战

总结

父方法无事务,子方法开启新事务
父方法有事务,子方法和父方法共用一个事务(无论父、子方法报错,整体回滚)

2、SUPPORTS

2.1、父方法无事务,子方法开启事务,子方法报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.SUPPORTS)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:数据都插入成功
Spring事务传播行为实战
Spring事务传播行为实战

2.2、父方法开启事务,子方法开启事务,子方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.SUPPORTS)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:两表数据都回滚了
Spring事务传播行为实战
Spring事务传播行为实战

总结

如果当前不存在事务,就以非事务执行
如果当前存在事务,就加入该事务

3、MANDATORY

3.1、父方法无事务,子方法开启事务,子方法报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.MANDATORY)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’

3.2、父方法开启事务,子方法开启事务,子方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.MANDATORY)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:两表数据都回滚了
Spring事务传播行为实战
Spring事务传播行为实战

总结

如果当前不存在事务,就抛出异常
如果当前存在事务,就加入该事务

4、REQUIRES_NEW

4.1、父方法无事务,子方法开启事务,子方法都报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:ks_a数据插入成功,ks_b数据回滚
Spring事务传播行为实战
Spring事务传播行为实战

4.2、父方法开启事务,子方法开启事务,父方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insert(ksB);
		throw new RuntimeException("父方法报错");
	}
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void insert(KsB ksB) {
		ksBDao.insert(ksB);
	}

结果:ks_a数据回滚,ks_b数据插入成功
Spring事务传播行为实战
Spring事务传播行为实战

总结

无论当前存不存在事务,都创建新事务

5、NOT_SUPPORTED

5.1、父方法无事务,子方法开启事务,子方法报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:数据都插入成功
Spring事务传播行为实战
Spring事务传播行为实战

5.2、父方法开启事务,子方法开启事务,子方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:ks_a数据回滚,ks_b数据插入成功
Spring事务传播行为实战
Spring事务传播行为实战

总结

以非事务方式执行,如果当前存在事务,父方法以事务方式执行,子方法以非事务方式执行

6、NEVER

父方法开启事务,子方法开启事务

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insert(ksB);
	}
	@Transactional(propagation = Propagation.NEVER)
	public void insert(KsB ksB) {
		ksBDao.insert(ksB);
	}

结果:org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’

总结

以非事务方式执行,如果当前存在事务,则抛出异常

7、NESTED

7.1、父方法无事务,子方法开启事务,子方法报错

	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Transactional(propagation = Propagation.NESTED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:ks_a数据插入成功,ks_b数据回滚
Spring事务传播行为实战
Spring事务传播行为实战

7.2、父方法开启事务,子方法开启事务,子方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insertError(ksB);
	}
	@Override
	@Transactional(propagation = Propagation.NESTED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:数据都回滚
Spring事务传播行为实战
Spring事务传播行为实战

7.3、父方法开启事务,子方法开启事务,父方法报错

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		KsB ksB = new KsB();
		ksB.setAge(10);
		ksBService.insert(ksB);
		throw new RuntimeException("主方法报错");
	}
	@Transactional(propagation = Propagation.NESTED)
	public void insert(KsB ksB) {
		ksBDao.insert(ksB);
	}

结果:数据都回滚
Spring事务传播行为实战
Spring事务传播行为实战

7.4、父方法开启事务,子方法开启事务,子方法报错,父方法并捕获

	@Transactional
	public void add() {
		KsA ksA = new KsA();
		ksA.setName("林");
		ksAService.insert(ksA);
		try {
			KsB ksB = new KsB();
			ksB.setAge(10);
			ksBService.insertError(ksB);
		} catch (Exception e) {
			//dosomething
		}
	}
	@Transactional(propagation = Propagation.NESTED)
	public void insertError(KsB ksB) {
		ksBDao.insert(ksB);
		throw new RuntimeException("子方法报错");
	}

结果:ks_a数据插入成功,ks_b数据回滚
Spring事务传播行为实战
Spring事务传播行为实战

总结

如果当前没有事务,则新开事务执行
如果当前存在事务,则在嵌套事务内执行

四、结论

1、NESTED和REQUIRES区别

区别在于:如果当前存在事务,子方法抛异常时
NESTED在父方法可以选择捕获子方法,父方法数据不会回滚;
REQUIRES无论捕不捕获,父方法数据都回滚

2、NESTED和REQUIRES_NEW区别

区别:如果当前存在事务,父方法抛异常时
NESTED数据回滚,REQUIRES也是如此
REQUIRES_NEW数据不回滚

3、七种传播行为总结

说明:加入该事务,指的是父、子方法共用一个事务(无论父、子方法报错,整体回滚)

REQUIRED

父方法无事务,子方法开启新事务
父方法有事务,就加入该事务

SUPPORTS

如果当前不存在事务,就以非事务执行
如果当前存在事务,就加入该事务

MANDATORY

如果当前不存在事务,就抛出异常
如果当前存在事务,就加入该事务

REQUIRES_NEW

无论当前存不存在事务,都创建新事务

NOT_SUPPORTED

以非事务方式执行,如果当前存在事务,父方法以事务方式执行,子方法以非事务方式执行

NEVER

以非事务方式执行,如果当前存在事务,则抛出异常

NESTED

如果当前没有事务,则新开事务执行
如果当前存在事务,则在嵌套事务内执行