转载自:https://dongguabai.blog.csdn.net/article/details/114686998
PROPAGATION_REQUIRES_NEW:原有事务A新起事务B,事务B中的commit和rollback不会影响外部事务A的commit和rollback,相互独立,如果事务B抛出异常,肯定会影响外事务A的。
PROPAGATION_NESTED:表示嵌套事务,看如下示例:
ServiceA {
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
@Transactional(propagation=Propagation.REQUIRED) // 1
void methodA() {
insertData(); //2
try {
ServiceB.methodB(); //3
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC(); //5
}
updateData(); //6
}
}
ServiceB {
@Transactional(propagation=Propagation.NESTED)
void methodB(){
//
updateData(); //4
}
}
在上面的1,将开起新事务A,2的时候会插入数据,此时事务A挂起,没有commit,3的时候,使用PROPAGATION_NESTED传播,将在3点的时候新建一个savepoint保存2插入的数据,不提交。
如果methodB出现异常,将回滚4的操作,不影响2的操作,同时可以处理后面的5,6逻辑,最后一起commit: 2,5,6;
如果methodB没有出现异常,那么将一起commit: 2,4,6。
假如methodB使用的PROPAGATION_REQUIRES_NEW
那么B异常,会commit: 2,5,6,和NESTED一致,如果methodB没有出现异常,那么会先commit4,再commit:6,那么事务将分离开,不能保持一致,假如执行6报错,2和6将回滚,而4却没有被回滚,不能达到预期效果。
最近在项目中发现了一则报错:“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。根据报错信息来看是spring框架中的事务管理报错:事务回滚了,因为它被标记为回滚状态。
报错原因:
多层嵌套事务中,如果使用了默认的事务传播方式,当内层事务抛出异常,外层事务捕捉并正常执行完毕时,就会报出rollback-only异常。
spring框架是使用AOP的方式来管理事务,如果一个被事务管理的方法正常执行完毕,方法结束时spring会将方法中的sql进行提交。如果方法执行过程中出现异常,则回滚。
spring框架的默认事务传播方式是 PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
在项目中,一般我们都会使用默认的传播方式,这样无论外层事务和内层事务任何一个出现异常,那么所有的sql都不会执行。在嵌套事务场景中,内层事务的sql和外层事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。
代码示例如下:
Class ServiceA {
@Resource(name = "serviceB")
private ServiceB b;
@Transactional
public void a() {
try {
b.b()
} catch (Exception ignore) {
}
}
}
Class ServiceB {
@Transactional
public void b() {
throw new RuntimeException();
}
}
当调用a()时,就会报出“rollback-only”异常。
解决方案:
- 如果希望内层事务抛出异常时中断程序执行,直接在外层事务的catch代码块中抛出e;
- 如果希望程序正常执行完毕,并且希望外层事务结束时全部提交,需要在内层事务中做异常捕获处理:
@Transactional
public void b() {
try {
throw new RuntimeException();
}catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
- 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为Propagation.REQUIRES_NEW(A和B不需要同一个事务,把事务分开)或者NESTED(A和B同一个事务,但是B回滚的时候A不会回滚)。注:PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。