事务方法A
调用事务方法B
,当方法B
抛出的异常被方法A
catch
后会发生什么?
1
场景描述
在一个事务方法中调用另一个事务方法。如在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
2
异常原因追溯
由于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);
}
}
}
3
没有声明事务为什么会存在事务?
虽然方法没有声明事务,可是该方法却在事务中执行,那么我们可以在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
方法。
最终找到是项目中配置事务拦截器时注入的。
因为我对这个项目不熟悉,所以才有这么一波源码分析的操作。
4
这个事务拦截器是怎么生效的呢?
这个事务拦截器是怎么生效的?答案是通过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;
}
5
解决方案
在与同事沟通后,本来想将这些配置去掉,但去掉后会导致一些事务方法不生效,如:
public class Servie{
public void method1(){
this.method2();
}
@Transactional
public void method2(){
}
}
如上面代码所示,这种情况下method2
方法的事务是不生效的。method1
方法虽然没有加事务注解,但由于加了BeanNameAutoProxyCreator
配置,等同于给该方法加了事务注解,所以methid1
方法的事务生效,所以method2
也能在事务中执行。
去掉配置后对系统的影响很大,事务不生效会引发很多问题,将整个系统让测试部门重新测试一遍也不现实。那么怎么解决这个问题呢?
既然所有业务类的public
方法都会被放在事务中执行,那么我就添加一个注解@NotNeedTransactional
,被该注解声明的方法不在事务中执行,与@Transactional
的作用正好相反。这样问题就能解决。(为什么不把catch去掉呢?业务需要,不抛出异常)
那么,怎么让@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;
}
6
END
本篇给出的解决方案我个人也不建议使用,也是因为目前想不到完美的解决方案。
如果这个配置不去掉,未来可能遇到的问题会更多。比如可能会将原本一个小的事务变成一个大事务。