带你走进事务传播行为的世界,了解透每种传播行为的作用,具体用法
一、什么是事务传播行为?
事务传播行为(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数据回滚
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);
}
结果:两表数据都回滚了
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("子方法报错");
}
结果:两表数据都回滚了
总结
父方法无事务,子方法开启新事务
父方法有事务,子方法和父方法共用一个事务(无论父、子方法报错,整体回滚)
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("子方法报错");
}
结果:数据都插入成功
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("子方法报错");
}
结果:两表数据都回滚了
总结
如果当前不存在事务,就以非事务执行
如果当前存在事务,就加入该事务
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("子方法报错");
}
结果:两表数据都回滚了
总结
如果当前不存在事务,就抛出异常
如果当前存在事务,就加入该事务
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数据回滚
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数据插入成功
总结
无论当前存不存在事务,都创建新事务
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("子方法报错");
}
结果:数据都插入成功
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数据插入成功
总结
以非事务方式执行,如果当前存在事务,父方法以事务方式执行,子方法以非事务方式执行
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数据回滚
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("子方法报错");
}
结果:数据都回滚
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);
}
结果:数据都回滚
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数据回滚
总结
如果当前没有事务,则新开事务执行
如果当前存在事务,则在嵌套事务内执行
四、结论
1、NESTED和REQUIRES区别
区别在于:如果当前存在事务,子方法抛异常时
NESTED在父方法可以选择捕获子方法,父方法数据不会回滚;
REQUIRES无论捕不捕获,父方法数据都回滚
2、NESTED和REQUIRES_NEW区别
区别:如果当前存在事务,父方法抛异常时
NESTED数据回滚,REQUIRES也是如此
REQUIRES_NEW数据不回滚
3、七种传播行为总结
说明:加入该事务,指的是父、子方法共用一个事务(无论父、子方法报错,整体回滚)
REQUIRED
父方法无事务,子方法开启新事务
父方法有事务,就加入该事务
SUPPORTS
如果当前不存在事务,就以非事务执行
如果当前存在事务,就加入该事务
MANDATORY
如果当前不存在事务,就抛出异常
如果当前存在事务,就加入该事务
REQUIRES_NEW
无论当前存不存在事务,都创建新事务
NOT_SUPPORTED
以非事务方式执行,如果当前存在事务,父方法以事务方式执行,子方法以非事务方式执行
NEVER
以非事务方式执行,如果当前存在事务,则抛出异常
NESTED
如果当前没有事务,则新开事务执行
如果当前存在事务,则在嵌套事务内执行