Spring 事务传播行为

在Spring @Transactional 声明式事务有一个项属性propagation事务传播行为,是一个Propagation 枚举类,有7种类型,对应不同使用场景。下面说下这些枚举代码含义,在结合代码加深理解,巩固学习。

Spring 属性 说明
REQUIRED 支持当前事务,如果不存在事务则创建一个新事务。这个propagation默认属性
SUPPORTS 支持当前事务,如果不存在事务则按照无事务执行
MANDATORY 支持当前事务,如果不存事务在则抛出异常
REQUIRES_NEW 创建一个新的事务,如果存在当前事务,则将它挂起
NOT_SUPPORTED 无事务执行,如果存在事务将它挂起
NEVER 无事务执行,如果存在当前事务则抛出异常
NESTED 如果当前事务存在,则在嵌套事务中执行,如果不存在事务就像REQUIRED效果

事务传播级别失效

先做一个小实验,写一个持久化的save方法,设置成禁用事务,用事务方法去调用它,看会发生什么效果。

1
2
3
4
5
6
7
8
9
10
@Transactional(propagation = Propagation.NEVER)
public void save(Users users){
usersRepository.save(users);
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
}

按照上面字段含义解析,save方法不支持事务,在当前事务运行会抛出异常。可是执行结果不是预料那样,没有抛出异常,数据写入到数据库了。并且我们可以在改一次save方法,直接抛出一个RuntimeTime异常,你会发现结果更加明显。

1
2
3
4
5
6
7
8
9
10
11
@Transactional(propagation = Propagation.NEVER)
public void save(Users users){
usersRepository.save(users);
throw new RuntimeException("4322");
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
}

你会发现save 方法 数据回滚了。
amBce1.jpg
在做多几次这种实验你就会发现,在同一个类中,最外层事务注解属性会覆盖方法内所有事务注解,比如你的外层设置默认事务传播行为,无论调用那种行为都会按照默认属性,外层没有设置事务,调用方法有事务也不会生效的。这个不难理解,实现Spring 事务一个基于AOP的代理类,它根据目标方法注解来增强方法,这个是运行时动态执行的,方法内其他方法没有代理类的功能增强,就相当于普通方法效果了。千万不要在同一个类里面测试事务传播行为,否则你永远都都得不到结论的。

进入方法验证

默认事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Transactional
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
throw new RuntimeException("33333");
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();

}

执行结果: users、employee写入失败,employee的方法抛出异常触发回滚,也会导致外层方法回滚。在同一个事务中,只要触发回滚,事务内所有数据操作都会回滚的。

支持事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.SUPPORTS)
public void save(){
Elimployee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();
throw new RuntimeException("3333");
}

执行效果: Usres和Elimployee 都进行回滚了。SUPPORTS会加入调用者事务中,和调用者事务是同一个事务,如果在事务中出现异常全部回滚。即使在employeeService.save() 抛出异常,效果一样的。

开启新事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
throw new RuntimeException("3333");
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
try {
employeeService.save();
}catch (RuntimeException e){

}

执行结果: empoyee回滚,users写入数据库成功。要处理employeeService.save()异常,不然异常会导致multSave出现异常事务回滚了。加入一个对照,在把employeeService.save()改成默认属性,发现users,employee 都会回滚了,还会抛出一个异常

org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only

Spring官方文档说到如果内部事务(外部调用者不知道)将事务无提示地标记为仅回滚,则外部调用者仍会调用commit。外部调用者需要接收一个,UnexpectedRollbackException以清楚地指示已执行回滚。

强制不使用事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.NEVER)
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();

}

执行结果: 两个表都写入失败,employeeService.save() 抛出异常

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

不支持事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
throw new RuntimeException("33333");
}

@Transactional
public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();
}

执行结果:employee成功写入数据库,employee 将事务挂了,用无事务方法执行,users受到异常抛出,触发事务回滚。如果你把employeeService.save()处理一下,两种表操作都写入成功了。

强制事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional(propagation = Propagation.MANDATORY)
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
}

public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();
}

执行结果: users 成功写入数据库,employee写入失败并且抛出异常

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

嵌套事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Transactional
public void multSave(){
Users users = getUsers();
save(users);
employeeService.save();
}

@Transactional(propagation = Propagation.NESTED)
public void save(){
Employee employee = new Employee();
employee.setDepartmentId(2);
employee.setName("Jorry");
employee.setSalary(60000);
repository.save(employee);
}

执行结果: employeeService.save()抛出一个异常,好像JPA的实现不支持嵌套执行,这个看不到效果了。

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider’s capabilities