今天在看spring的事務底層原始碼時,想到一個問題,@Transactional註解真的有必要宣告rollbackFor屬性嗎?因為之前有許多資料,包括公司的java編碼規範上也有提及到這一點。
不知道讀者們有沒想過這個問題,但我看完原始碼後,個人覺得是沒必要的。見解不到位的話,希望讀者能指明。
異常:如下圖所示,我們都知道Exception分為執行時異常RuntimeException和非執行時異常(檢查時異常)。
那麼spring預設會對如上的哪些異常進行回滾呢?
答案:RuntimeException、Error.
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的另一種實現就是非執行時異常,這種我們程式碼中已經處理了,所以也不需要再去指定。