關注“Java藝術”一起來充電吧!
事務方法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
本篇給出的解決方案我個人也不建議使用,也是因爲目前想不到完美的解決方案。
如果這個配置不去掉,未來可能遇到的問題會更多。比如可能會將原本一個小的事務變成一個大事務。
公衆號:Java藝術
掃碼關注最新動態