事务方法A
调用事务方法B
,当方法B
抛出的异常被方法A
catch
后会发生什么?
场景描述
在一个事务方法中调用另一个事务方法。如在ServiceA
的methodA
方法中调用ServiceB
的methodB
方法,两个方法都设置了事务,传播机制都是PROPAGATION_REQUIRED
。
ServiceB
的methodB
方法声明事务如下。
public class ServiceB{
@Transactional(rollbackFor = Exception.class)
public void methodB(){
}
}
在methodA
方法中捕获methodB
异常,代码如下。
public class ServiceA{
public void methodA(){
try{
serviceB.methodB();
}catch(Exception e){
// do
}
}
}
methodA
没有加事务注解,但methodA
是在事务中执行的,也是因为如此,我才调试了半天Spring
事务源码。其效果等同于:
public class ServiceA{
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void methodA(){
try{
serviceB.methodB();
}catch(Exception e){
// do
}
}
}
当methodB
方法抛出异常后,当前事务回滚,异常往外抛出,被methodA
方法catch
。由于methodA
方法catch
了异常,异常不再往外抛出,当methodA
方法执行完成时,事务切面走的不是回滚逻辑,而是提交逻辑。这就出现了如下异常。
异常信息:
Transaction rolled back because it has been marked as rollback-only
异常原因追溯
由于methodB
方法抛出异常导致事务已经回滚,且当前事务被标志为仅回滚,因此当前事务只能回滚,不能再执行提交,如果执行提交,就能看到上述异常。该异常在AbstractPlatformTransactionManager
的processRollback
方法抛出。该方法源码如下。
public abstract class AbstractPlatformTransactionManage{
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
} else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
} else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
} else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
} else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}finally {
cleanupAfterCompletion(status);
}
}
}
没有声明事务为什么会存在事务?
虽然方法没有声明事务,可是该方法却在事务中执行,那么我们可以在TransactionAspectSupport
的invokeWithinTransaction
方法中下断点调试。invokeWithinTransaction
方法中会调用TransactionAttributeSource
的getTransactionAttribute
方法获取事务的配置信息。
如使用注解声明事务时,会调用AnnotationTransactionAttributeSource
的getTransactionAttribute
方法。经调试得知,这里调用的是NameMatchTransactionAttributeSource
的getTransactionAttribute
方法,如下图所示。
ServiceA
的methodA
方法匹配了'*'
这一项。可是这又是在哪里配置的呢?只要找出在哪里配置的,将配置去掉问题也就能解决了。
首先找到nameMap
字段是在什么时候初始化的,什么时候赋值的。
看源码可知:在NameMatchTransactionAttributeSource
的setProperties
方法中调用setNameMap
方法为nameMap
字段赋值,而setProperties
方法由TransactionAspectSupport
的setTransactionAttributes
调用,该方法的源码如下。
public void setTransactionAttributes(Properties transactionAttributes) {
NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
tas.setProperties(transactionAttributes);
this.transactionAttributeSource = tas;
}
再继续查看哪里会调用TransactionAspectSupport
的setTransactionAttributes
方法。
最终找到是项目中配置事务拦截器时注入的。
因为我对这个项目不熟悉,所以才有这么一波源码分析的操作。
这个事务拦截器是怎么生效的呢?
这个事务拦截器是怎么生效的?答案是通过InstantiationAwareBeanPostProcessor
代理bean
,拦截bean
的public
方法的执行,交给事务拦截器TransactionInterceptor
处理。项目中的配置如下。
@Bean
public BeanNameAutoProxyCreator getBeanNameAutoProxyCreator() {
BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
// 设置方法拦截器的bean名称
creator.setInterceptorNames("getTransactionInterceptor");
// 拦截哪些bean
creator.setBeanNames("*Service", "*ServiceImpl");
// 使用cglib
creator.setProxyTargetClass(true);
creator.setOrder(100);
return creator;
}
解决方案
在与同事沟通后,本来想将这些配置去掉,但去掉后会导致一些事务方法不生效,如:
public class Servie{
public void method1(){
this.method2();
}
@Transactional
public void method2(){
}
}
如上面代码所示,method1
方法虽然没有加事务注解,但由于加了BeanNameAutoProxyCreator
配置,等同于给该方法加了事务注解,所以methid1
方法的事务生效,虽然method1调用method2,method2的事务注解不生效,但由于method1在事务中,所以method2
也能在事务中执行。
如果我们把BeanNameAutoProxyCreator
配置去掉,那么method1就不会在事务中执行,这种情况下method2
方法的事务注解也是不生效的,因为method1是通过this调用,不是调用代理对象的method2方法,所以method2的事务注解的AOP逻辑没有执行,那么method1和method2的事务就都不生效了。但我们希望method2的事务生效。
此案例我们可以通过获取代理类实例再调用method2方法,而不是使用this调用。
public class Servie{
public void method1(){
getBean(Service.class).method2();
}
@Transactional
public void method2(){
}
}
但是,整个项目可能存在很多像method1方法一样需要依赖BeanNameAutoProxyCreator
配置而使事务生效的。如果直接去掉配置,对系统的影响很大。
为什么不把method1中catch异常的逻辑去掉呢?使method1和method2合并成一个事务,提交一起提交回滚一起回滚。因为业务需要,method1方法不抛出异常,所以实际上method1也是不需要在事务中执行的。只是method2需要在事务中执行。
那么怎么解决这个问题呢?
既然所有业务类的public
方法都会被放在事务中执行,那么我就添加一个注解@NotNeedTransactional
,被该注解声明的方法不在事务中执行,与@Transactional
的作用正好相反。这样问题就能解决。
那么,怎么让@NotNeedTransactional
注解生效呢?
继承事务拦截器,重写invoke
方法,判断如果方法加了@NotNeedTransactional
注解,则直接调用方法,不走切面。代码如下。
@Bean(name = "getTransactionInterceptor")
public TransactionInterceptor getTransactionInterceptor(AbstractPlatformTransactionManager transactionManager) {
TransactionInterceptor ti = new TransactionInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 有@NotNeedTransaction注解
if (invocation.getMethod().getAnnotation(NotNeedTransaction.class) != null) {
return invocation.proceed();
} else {
return super.invoke(invocation);
}
}
};
ti.setTransactionManager(transactionManager);
ti.setTransactionAttributes(getTransactionAttributes());
return ti;
}
案例修改如下:
public class Servie{
@NotNeedTransactional
public void method1(){
getBean(Service.class).method2();
}
@Transactional
public void method2(){
}
}
由于method1声明了@NotNeedTransactional注解,TransactionInterceptor中,直接调用method1方法,不走事务逻辑了。