1. 程式人生 > >Spring官方都推薦使用的@Transactional事務,為啥我不建議使用!

Spring官方都推薦使用的@Transactional事務,為啥我不建議使用!

[GitHub 17k Star 的Java工程師成神之路,不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) [GitHub 17k Star 的Java工程師成神之路,真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) [GitHub 17k Star 的Java工程師成神之路,真的真的不來了解一下嗎!](https://github.com/hollischuang/toBeTopJavaer) 事務管理在系統開發中是不可缺少的一部分,Spring提供了很好事務管理機制,主要分為程式設計式事務和宣告式事務兩種。 關於事務的基礎知識,如什麼是事務,資料庫事務以及Spring事務的ACID、隔離級別、傳播機制、行為等,就不在這篇文章中詳細介紹了。預設大家都有一定的瞭解。 本文,作者會先簡單介紹下什麼是宣告式事務和程式設計式事務,再說一下為什麼我不建議使用宣告式事務。 #### 程式設計式事務 基於底層的API,如PlatformTransactionManager、TransactionDefinition 和 TransactionTemplate 等核心介面,開發者完全可以通過程式設計的方式來進行事務管理。 **程式設計式事務方式需要是開發者在程式碼中手動的管理事務的開啟、提交、回滾等操作。**
public void test() {
      TransactionDefinition def = new DefaultTransactionDefinition();
      TransactionStatus status = transactionManager.getTransaction(def);

       try {
         // 事務操作
         // 事務提交
         transactionManager.commit(status);
      } catch (DataAccessException e) {
         // 事務提交
         transactionManager.rollback(status);
         throw e;
      }
}
如以上程式碼,開發者可以通過API自己控制事務。 #### 宣告式事務 **宣告式事務管理方法允許開發者配置的幫助下來管理事務,而不需要依賴底層API進行硬編碼。開發者可以只使用註解或基於配置的 XML 來管理事務。**
@Transactional
public void test() {
     // 事務操作  
}
如上,使用@Transactional即可給test方法增加事務控制。 當然,上面的程式碼只是簡化後的,想要使用事務還需要一些配置內容。這裡就不詳細闡述了。 這兩種事務,格子有各自的優缺點,那麼,各自有哪些適合的場景呢?為什麼有人會拒絕使用宣告式事務呢? ### 宣告式事務的優點 通過上面的例子,其實我們可以很容易的看出來,宣告式事務幫助我們節省了很多程式碼,他會自動幫我們進行事務的開啟、提交以及回滾等操作,把程式設計師從事務管理中解放出來。 **宣告式事務管理使用了 AOP 實現的,本質就是在目標方法執行前後進行攔截。**在目標方法執行前加入或建立一個事務,在執行方法執行後,根據實際情況選擇提交或是回滾事務。 **使用這種方式,對程式碼沒有侵入性,方法內只需要寫業務邏輯就可以了。** 但是,宣告式事務真的有這麼好麼?倒也不見得。 ### 宣告式事務的粒度問題 首先,**宣告式事務有一個侷限,那就是他的最小粒度要作用在方法上。** 也就是說,如果想要給一部分程式碼塊增加事務的話,那就需要把這個部分程式碼塊單獨獨立出來作為一個方法。 但是,正是因為這個粒度問題,本人並不建議過度的使用宣告式事務。 首先,因為宣告式事務是通過註解的,有些時候還可以通過配置實現,這就會導致一個問題,那就是這個事務有可能被開發者忽略。 事務被忽略了有什麼問題呢? **首先,如果開發者沒有注意到一個方法是被事務巢狀的,那麼就可能會再方法中加入一些如RPC遠端呼叫、訊息傳送、快取更新、檔案寫入等操作。** 我們知道,這些操作如果被包在事務中,有兩個問題: 1、這些操作自身是無法回滾的,這就會導致資料的不一致。可能RPC呼叫成功了,但是本地事務回滾了,可是PRC呼叫無法回滾了。 2、在事務中有遠端呼叫,就會拉長整個事務。那麼久會導致本事務的資料庫連線一直被佔用,那麼如果類似操作過多,就會導致資料庫連線池耗盡。 有些時候,即使沒有在事務中進行遠端操作,但是有些人還是可能會不經意的進行一些記憶體操作,如運算。或者如果遇到分庫分表的情況,有可能不經意間進行跨庫操作。 但是如果是程式設計式事務的話,業務程式碼中就會清清楚楚看到什麼地方開啟事務,什麼地方提交,什麼時候回滾。這樣有人改這段程式碼的時候,就會強制他考慮要加的程式碼是否應該方法事務內。 有些人可能會說,已經有了宣告式事務,但是寫程式碼的人沒注意,這能怪誰。 話雖然是這麼說,但是我們還是希望可以通過一些機制或者規範,降低這些問題發生的概率。 比如建議大家使用程式設計式事務,而不是宣告式事務。因為,作者工作這麼多年來,發生過不止一次開發者沒注意到宣告式事務而導致的故障。 因為有些時候,宣告式事務確實不夠明顯。 ### 宣告式事務用不對容易失效 除了事務的粒度問題,還有一個問題那就是宣告式事務雖然看上去幫我們簡化了很多程式碼,但是一旦沒用對,也很容易導致事務失效。 如以下幾種場景就可能導致宣告式事務失效: 1、@Transactional 應用在非 public 修飾的方法上 2、@Transactional 註解屬性 propagation 設定錯誤 3、@Transactional 註解屬性 rollbackFor 設定錯誤 4、同一個類中方法呼叫,導致@Transactional失效 5、異常被catch捕獲導致@Transactional失效 6、資料庫引擎不支援事務 以上幾個問題,如果使用程式設計式事務的話,很多都是可以避免的。 使用宣告事務失效的問題我們發生過很多次。不知道大家有沒有遇到過,我是實際遇到過的 因為Spring的事務是基於AOP實現的,但是在程式碼中,有時候我們會有很多切面,不同的切面可能會來處理不同的事情,多個切面之間可能會有相互影響。 在之前的一個專案中,我就發現我們的Service層的事務全都失效了,一個SQL執行失敗後並沒有回滾,排查下來才發現,是因為一位同事新增了一個切面,這個切面裡面做個異常的統一捕獲,導致事務的切面沒有捕獲到異常,導致事務無法回滾。 這樣的問題,發生過不止一次,而且不容易被發現。 很多人還是會說,說到底還是自己能力不行,對事務理解不透徹,用錯了能怪誰。 但是我還是那句話,**我們確實無法保證所有人的能力都很高**,也無法要求所有開發者都能不出錯。我們能做的就是,儘量可以通過機制或者規範,來避免或者降低這些問題發生的概率。 其實,如果大家有認真看過阿里巴巴出的那份Java開發手冊的話,其實就能發現,其中的很多規約並不是完完全全容易被人理解,有些也比較生硬,但是其實,這些規範都是從無數個坑裡爬出來的開發者們總結出來的。 關於@Transactional的用法,規約中也有提到過,只不過規約中的觀點沒有我這麼鮮明:
### 總結 最後,相信本文的觀點很多人都並不一定認同,很多人會說:Spring官方都推薦無侵入性的宣告式事務,你有啥資格出來BB 。 說實話,剛工作的前幾年,我也熱衷於使用宣告式事務,覺得很乾淨,也很"優雅"。覺得師兄們使用程式設計式事務多此一舉,沒有工匠精神。 但是慢慢的,線上發生過幾次問題之後,我們覆盤後發現,很多時候你自己寫的程式碼很優雅,這完全沒問題。 但是,優雅的同時也帶來了一些副作用,師兄們又不能批評我,因為我的用法確實沒錯... 所以,有些事,還是要痛過之後才知道。 當然,本文並不要求大家一定要徹底不使用宣告式事務,只是建議大家日後在使用事務的時候,能夠考慮到本文中提到的觀點,然後自行選擇。