场景如下:
- A_Bean 中的方法a()中调用B_Bean的b();
- 方法都开启了事务,使用的默认的事务传递机制(即:属于同一事务);
如下两种场景会存在较大的差异:
- 在b()方法中出现了异常,在b()中进行捕获并处理且没有抛出新的异常,事务最终会进行提交;
- 在b()方法中出现了异常,在a()中进行捕获并处理且没有抛出新的异常,那么事务最终会如何呢?—— 先给结论:事务回滚
这个小差异平时编程的过程比较难留意到,会简单认为:当某个方法上面开启了事务,并且当前方法没有抛出任何异常,最终方法上面的事务一定会提交。其实这里是存在认知错误的。
code如下:
@SpringBootTest
public class TransactionTest {
@Autowired
private A a;
@Test
void testTransaction() throws Exception {
a.a();
}
}
@Service
public class A {
@Autowired
private B b;
@Transactional
public void a() {
try {
b.b();
} catch (Exception e) {
log.error("b执行异常,进行捕获且不抛出异常");
}
// for some db operation
}
}
@Service
public class B {
@Transactional
public void b() {
// for some db operation
throw new RuntimeException("b-error");
}
}}
为何错误?
- 当
a()
方法调用b()
方法时,如果两个方法都开启了事务且采用默认的事务传播行为(即事务嵌套),b()
方法的事务会加入到a()
方法的事务中,成为同一个事务。 - 那么b()中出现异常,b中没有捕获而在a中捕获,实则已经触发b()的事务处理异常的逻辑。
- 而a、b方法执行又同属一个事务,在b异常被事务管理器感知到后就会将当前事务标记为rollback,那么即使a最终没有感知到异常,最终a正常执行完毕后,a上面的事务管理逻辑也不会将事务进行提交,而是采取回滚的决定!
源码分析
当一个事务方法执行出现异常时(比如b()执行抛出异常时):
org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing
org.springframework.transaction.support.AbstractPlatformTransactionManager#rollback
org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
org.springframework.jdbc.datasource.DataSourceTransactionManager#doSetRollbackOnly
org.springframework.jdbc.datasource.DataSourceTransactionManager.DataSourceTransactionObject#setRollbackOnly
org.springframework.transaction.support.ResourceHolderSupport#setRollbackOnly
当a()正常执行完毕,准备提交事务时:
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning
org.springframework.transaction.support.AbstractPlatformTransactionManager#commit
org.springframework.transaction.support.SmartTransactionObject#isRollbackOnly
org.springframework.jdbc.datasource.DataSourceTransactionManager.DataSourceTransactionObject#isRollbackOnly
a执行完毕会进行判断:
最终还是会进行事务的回滚!
在 Spring 中,当事务被标记为 rollback-only 时,它会通知事务管理器,表示事务应该回滚。即使没有抛出新的异常,一旦事务被标记为 rollback-only,最终事务仍然会回滚。
因此,在你的情况下,如果 b()
方法中出现异常,在 a()
方法中进行了捕获并处理,但是事务在 b()
方法中被标记为 rollback-only,最终会导致 a()
方法的事务回滚。
如果有这种需要该如何处理?
如题:a事务嵌套b事务,不管b事务是否执行成功,只有a中最终没有抛出异常那么就需要将a提交,做到a事务不受内部嵌套事务的影响,该如何?
修改b事务的传播配置: