面試突擊:為什么事務@Transactional會失效?
導致 @Transactional 失效的常見場景有以下 5 個:
- 非 public 修飾的方法。
- timeout 超時時間設置過小。
- 代碼中使用 try/catch 處理異常。
- 調用類內部的 @Transactional 方法。
- 數(shù)據(jù)庫不支持事務。
很多人只知道答案但不知道原因,這就像只談戀愛不結婚一樣,是不能讓人接受的,所以本篇我們就來討論一下,導致事務失效的背后原因到底是啥?
在以上 5 種場景中,第 2 種(timeout 超時時間設置過?。┖偷?5 種(數(shù)據(jù)庫不支持事務)很好理解,我們這里就不贅述了,本文我們重點來討論其他 3 種情況。
1、非 public 修飾的方法
非 public 修飾的方法上,即使加了 @Transactional 事務依然不會生效,原因是因為 @Transactional 使用的是 Spring AOP 實現(xiàn)的,而 Spring AOP 是通過動態(tài)代理實現(xiàn)的,而 @Transactional 在生成代理時會判斷,如果方法為非 public 修飾的方法,則不生成代理對象,這樣也就沒辦法自動執(zhí)行事務了,它的部分實現(xiàn)源碼如下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
// 非 public 方法,設置為 null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 后面代碼省略....
}
2、try/catch 導致事務失效
@Transactional 執(zhí)行流程是:@Transactional 會在方法執(zhí)行前,會自動開啟事務;在方法成功執(zhí)行完,會自動提交事務;如果方法在執(zhí)行期間,出現(xiàn)了異常,那么它會自動回滾事務。
然而如果在方法中自行添加了 try/catch 之后,事務就不會自動回滾了,這是怎么回事呢?
造成這個問題的主要原因和 @Transactional 注解的實現(xiàn)有關,它的部分實現(xiàn)源碼如下:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 自動開啟事務
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 反射調用業(yè)務方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 異常時,在 catch 邏輯中,自動回滾事務
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 自動提交事務
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// .....
}
}
從上述實現(xiàn)源碼我們可以看出:當執(zhí)行的方法中出現(xiàn)了異常,@Transactional 才能感知到,然后再執(zhí)行事務回滾,而當開發(fā)者自行添加了 try/catch 之后,@Transactional 就感知不到異常了,從而就不會觸發(fā)事務的自動回滾了,這就是為什么當 @Transactional 遇到 try/catch 之后就不會自動回滾(事務)的原因。
3、調用類內用的 @Transactional 方法
當調用類內部的 @Transactional 修飾的方法時,事務也不會生效,如下代碼所示:
"/save")(
public int saveMappping(UserInfo userInfo) {
return save(userInfo);
}
public int save(UserInfo userInfo) {
// 非空效驗
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
int result = userService.save(userInfo);
int num = 10 / 0; // 此處設置一個異常
return result;
}
上述代碼在添加用戶之后即使遇到了異常,程序也沒有執(zhí)行回滾,這是因為 @Transactional 是基于 Spring AOP 實現(xiàn)的,而 Spring AOP 又是基于動態(tài)代理實現(xiàn)的,而當調用類內部的方法時,不是通過代理對象完成的,而是通過 this 對象實現(xiàn)的,這樣就繞過了代理對象,從而事務就失效了。
總結
非 public 修飾的方法在 @Transactional 實現(xiàn)時做了判斷,如果是非 public 則不會生成代理對象,所以事務就失效了;而調用類內部的 @Transactional 修飾的方法時,也是因為沒有成功調用代理對象,是通過 this 來調用方法的,所以事務也失效了;@Transactional 在遇到開發(fā)者自定義的 try/catch 也會失效,這是因為 @Transactional 只有感知到了異常才會自動回滾(事務),但如果用戶自定義了 try/catch,那么 @Transactional 就感知不到異常,所以也就不會自動回滾事務了。