@Transactional註解真的有必要宣告rollbackFor屬性嗎?

2022-10-11 06:01:23

@Transactional註解真的有必要宣告rollbackFor屬性嗎?

​ 今天在看spring的事務底層原始碼時,想到一個問題,@Transactional註解真的有必要宣告rollbackFor屬性嗎?因為之前有許多資料,包括公司的java編碼規範上也有提及到這一點。

​ 不知道讀者們有沒想過這個問題,但我看完原始碼後,個人覺得是沒必要的。見解不到位的話,希望讀者能指明。

異常:如下圖所示,我們都知道Exception分為執行時異常RuntimeException和非執行時異常(檢查時異常)。

那麼spring預設會對如上的哪些異常進行回滾呢?

答案:RuntimeException、Error.

spring原始碼如下說明:

​ spring在執行方法丟擲異常後,會呼叫completeTransactionAfterThrowing方法,也在該方法中會去判斷並執行是回滾還是提交操作。

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}

			// transactionAttribute的實現類為RuleBasedTransactionAttribute,父類別為DefaultTransactionAttribute
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}

我們看到這個if分支進行分析,如果該if滿足,則會進行回滾。

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex))

檢視txInfo.transactionAttribute.rollbackOn(ex)方法,其中的this.rollbackRules就是我們在@Transactional註解上設定的rollbackFor屬性,

public boolean rollbackOn(Throwable ex) {
   RollbackRuleAttribute winner = null;
   int deepest = Integer.MAX_VALUE;

   if (this.rollbackRules != null) {
      // 遍歷所有的RollbackRuleAttribute,判斷現在丟擲的異常ex是否匹配RollbackRuleAttribute中指定的異常型別的子類或本身
      for (RollbackRuleAttribute rule : this.rollbackRules) {
         int depth = rule.getDepth(ex);
         if (depth >= 0 && depth < deepest) {
            deepest = depth;
            winner = rule;
         }
      }
   }

   // User superclass behavior (rollback on unchecked) if no rule matches.
   if (winner == null) {
      return super.rollbackOn(ex);
   }

   // ex所匹配的RollbackRuleAttribute,可能是NoRollbackRuleAttribute,如果是匹配的NoRollbackRuleAttribute,那就表示現在這個異常ex不用回滾
   return !(winner instanceof NoRollbackRuleAttribute);
}

我這裡簡單配了個ServiceException。如下圖

根據一:在rollbackFor屬性元素遍歷時,會根據getDepth方法去找丟擲的異常,是不是就是我們宣告的rollbackFor屬性中的異常,如果是,判斷是不是NoRollbackRuleAttribute型別,是的話就不回滾,否則就回滾。

再看getDepth方法,會根據丟擲的異常,判斷異常名字是否跟我們宣告的異常是否相同,相同則返回,不同則遞迴丟擲異常的父類別,直到遍歷到Throwable.class頂類。

private int getDepth(Class<?> exceptionClass, int depth) {
   if (exceptionClass.getName().contains(this.exceptionName)) {
      // Found it!
      return depth;
   }
   // If we've gone as far as we can go and haven't found it...
   if (exceptionClass == Throwable.class) {
      return -1;
   }
   return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

根據二:如果getDepth找不到對應的異常類,就從預設實現類DefaultTransactionAttribute.rollbackOn(Throwalbe x)方法進行判斷。

@Override
public boolean rollbackOn(Throwable ex) {
   return (ex instanceof RuntimeException || ex instanceof Error);
}

DefaultTransactionAttribute判斷丟擲的異常是RuntimeException或者Error就會進行回滾。說明spring沒有對非執行時異常(檢查時異常)進行處理,這是因為非執行時異常在編碼時,是需要我們開發人員手動去進行try catch進行處理的,也不允許丟擲非執行時異常,比如IOException,不然編譯器編譯都不通過,更別談執行程式了。

總結:我們規範中要求指定rollbackFor=Exception.class,但是spring中已經包含了RuntimeException的處理,Exception的另一種實現就是非執行時異常,這種我們程式碼中已經處理了,所以也不需要再去指定。