面試突擊:為什麼事務@Transactional會失效?
導致 @Transactional 失效的常見場景有以下 5 個:
- 非 public 修飾的方法。
- timeout 超時時間設定過小。
- 程式碼中使用 try/catch 處理異常。
- 呼叫類內部的 @Transactional 方法。
- 資料庫不支援事務。
很多人只知道答案但不知道原因,這就像只談戀愛不結婚一樣,是不能讓人接受的,所以本篇我們就來討論一下,導致事務失效的背後原因到底是啥?
在以上 5 種場景中,第 2 種(timeout 超時時間設定過小)和第 5 種(資料庫不支援事務)很好理解,我們這裡就不贅述了,本文我們重點來討論其他 3 種情況。
1、非 public 修飾的方法
非 public 修飾的方法上,即使加了 @Transactional 事務依然不會生效,原因是因為 @Transactional 使用的是 Spring AOP 實現的,而 Spring AOP 是通過動態代理實現的,而 @Transactional 在生成代理時會判斷,如果方法為非 public 修飾的方法,則不生成代理物件,這樣也就沒辦法自動執行事務了,它的部分實現原始碼如下:
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 執行流程是:@Transactional 會在方法執行前,會自動開啟事務;在方法成功執行完,會自動提交事務;如果方法在執行期間,出現了異常,那麼它會自動回滾事務。
然而如果在方法中自行添加了 try/catch 之後,事務就不會自動回滾了,這是怎麼回事呢?
造成這個問題的主要原因和 @Transactional 註解的實現有關,它的部分實現原始碼如下:
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. // 反射呼叫業務方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception // 異常時,在 catch 邏輯中,自動回滾事務 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } // 自動提交事務 commitTransactionAfterReturning(txInfo); return retVal; } else { // ..... } }
從上述實現原始碼我們可以看出:當執行的方法中出現了異常,@Transactional 才能感知到,然後再執行事務回滾,而當開發者自行添加了 try/catch 之後,@Transactional 就感知不到異常了,從而就不會觸發事務的自動回滾了,這就是為什麼當 @Transactional 遇到 try/catch 之後就不會自動回滾(事務)的原因。
3、呼叫類內用的 @Transactional 方法
當呼叫類內部的 @Transactional 修飾的方法時,事務也不會生效,如下程式碼所示:
@RequestMapping("/save") public int saveMappping(UserInfo userInfo) { return save(userInfo); } @Transactional 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; }
上述程式碼在新增使用者之後即使遇到了異常,程式也沒有執行回滾,這是因為 @Transactional 是基於 Spring AOP 實現的,而 Spring AOP 又是基於動態代理實現的,而當呼叫類內部的方法時,不是通過代理物件完成的,而是通過 this 物件實現的,這樣就繞過了代理物件,從而事務就失效了。
總結
非 public 修飾的方法在 @Transactional 實現時做了判斷,如果是非 public 則不會生成代理物件,所以事務就失效了;而呼叫類內部的 @Transactional 修飾的方法時,也是因為沒有成功呼叫代理物件,是通過 this 來呼叫方法的,所以事務也失效了;@Transactional 在遇到開發者自定義的 try/catch 也會失效,這是因為 @Transactional 只有感知到了異常才會自動回滾(事務),但如果使用者自定義了 try/catch,那麼 @Transactional 就感知不到異常,所以也就不會自動回滾事務了。
- Spring中實現非同步呼叫的方式有哪些?
- 帶引數的全型別 Python 裝飾器
- 整理了幾個Python正則表示式,拿走就能用!
- SOLID:開閉原則Go程式碼實戰
- React中如何引入CSS呢
- 一個新視角:前端框架們都卷錯方向了?
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 手寫程式語言-遞迴函式是如何實現的?
- 一文搞懂模糊匹配:定義、過程與技術
- 新來個阿里 P7,僅花 2 小時,做出一個多執行緒永動任務,看完直接跪了
- Puzzlescript,一種開發H5益智遊戲的引擎
- @Autowired和@Resource到底什麼區別,你明白了嗎?
- CSS transition 小技巧!如何保留 hover 的狀態?
- React如此受歡迎離不開這4個主要原則
- LeCun再炮轟Marcus: 他是心理學家,不是搞AI的
- Java保證執行緒安全的方式有哪些?
- 19個殺手級 JavaScript 單行程式碼,讓你看起來像專業人士
- Python 的"self"引數是什麼?
- 別整一坨 CSS 程式碼了,試試這幾個實用函式
- 再有人問你什麼是MVCC,就把這篇文章發給他!