事務管理在系統開發中是不可缺少的一部分,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執行失敗后並沒有回滾,排查下來才發現,是因為一位同事新增了一個切面,這個切面里面做個異常的統一捕獲,導致事務的切面沒有捕獲到異常,導致事務無法回滾。
這樣的問題,發生過不止一次,而且不容易被發現。
很多人還是會說,說到底還是自己能力不行,對事務理解不透徹,用錯了能怪誰。
但是我還是那句話,我們確實無法保證所有人的能力都很高,也無法要求所有開發者都能不出錯。我們能做的就是,盡量可以通過機制或者規范,來避免或者降低這些問題發生的概率。