spring 第一期:@Transactional 下的事務管理以及該註解失效的常見原因

目錄

1.回顧一下事務

2.幾種實現事務的方式

3.@Transactional的使用

3.1.DataSourceTransactionManager 配置

3.2.開啓事務管理

3.3.@Transactional

3.3.1.value、transactionManager屬性

3.3.2.propagation屬性

3.3.3.isolation屬性

3.3.4.timeout屬性

3.3.5.read-only屬性

3.3.6. rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

4.@Transactional註解使用須知

4.1.@Transactional註解可以作用於哪些地方?

4.2.@Transactional失效的場景

4.2.1@Transactional 應用在非 public 修飾的方法上

4.2.2.@Transactional 註解屬性 propagation 設置錯誤

4.2.3.@Transactional 註解屬性 rollbackFor 設置錯誤

4.2.4.異常被你的 catch“吃了”導致@Transactional失效

4.2.5.同一個類中方法調用,導致@Transactional失效

4.2.5.數據庫引擎不支持事務管理


1.回顧一下事務

事務Transaction,它是一些嚴密操作的集合,要麼都操作完成,要麼都回滾撤銷,事務具備ACID四種特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和Durability(持久性)的英文縮寫。 

  (1)原子性(Atomicity)
    事務最基本的操作單元,要麼全部成功,要麼全部失敗,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
  (2)一致性(Consistency)
    事務的一致性指的是在一個事務執行之前和執行之後數據庫都必須處於一致性狀態。如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態。
  (3)隔離性(Isolation)
    指的是在併發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看到中間狀態的數據。
  (4)持久性(Durability)
    指的是隻要事務成功結束,它對數據庫所做的更新就必須永久保存下來。即使發生系統崩潰,重新啓動數據庫系統後,數據庫還能恢復到事務成功結束時的狀態。

使用 SQL 我們可以簡單的演示這個過程

#事務
START TRANSACTION;#開始事務
UPDATE course
SET course_class_hour=100,
    course_credit=8
WHERE course_name = '程序語言設計';
UPDATE course
SET course_class_hour=98,
    course_credit=7
WHERE course_name = '離散數學';
COMMIT;#提交(只有提交後的數據,在數據庫中才真正更行,否則回滾就前功盡棄)
ROLLBACK;#回滾

不過,這種方式顯然不適合當下的高效率的開發模式,於是便有了Spring 對事務管理的支持

2.幾種實現事務的方式

  • 編程式事務管理對基於 POJO 的應用來說是唯一選擇。我們需要在代碼中調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理
  • 基於 TransactionProxyFactoryBean的聲明式事務管理
  • 基於 @Transactional 的聲明式事務管理
  • 基於Aspectj AOP配置事務
  • 編程式事務:允許用戶在代碼中精確定義事務的邊界。編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。

  • 聲明式事務: 基於AOP,有助於用戶將操作與事務規則進行解耦。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。聲明式事務管理也有兩種常用的方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional註解的方式。顯然基於註解的方式更簡單易用,更清爽。@Transactional註解的使用也是我們本文着重要理解的部分。

       顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立爲方法等等。

3.@Transactional的使用

3.1.DataSourceTransactionManager 配置

顧名思義,這是數據源事務管理器,它需要一個DataSource作爲參數,如下配置:

/**
     * 多數據源事務管理器配置
     *
     * @param dynamicDataSource 數據源
     * @return 事務管理器
     */
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dynamicDataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dynamicDataSource);
        return dataSourceTransactionManager;
    }

注意:在SpringBoot中,DataSourceTransactionManager 是默認配置好的,但當有多個數據源時(例如我這裏使用的是動態數據源),則需要手動配置,指定數據源)

3.2.開啓事務管理

我這裏使用註解開啓事務管理:

@Configuration
@EnableTransactionManagement
public class SkyConfig

3.3.@Transactional

配置好事務環境後,我們只需要在需要事務管理的方法上加上@Transactional註解即可,由於Dao 層的方法通常是一條語句執行的,本生就具有原子性,所以一般我們習慣在Service層上使用該註解

如下是一個簡單的實例,由於這兩個字段我設置了組合唯一索引,所以第二次插入操作會報錯,於是整個方法執行事務回滾,結果就是:第一條(看似執行過了)也無效

    @Transactional
    public int batchInsert() {
        List<SysUserRoleLinkDO> list = new ArrayList<>();
        list.add(new SysUserRoleLinkDO(1L, 2L));
        list.add(new SysUserRoleLinkDO(2L, 7L));
        int i = sysUserRoleLinkDAO.batchInsert(list);
        List<SysUserRoleLinkDO> list2 = new ArrayList<>();
        list2.add(new SysUserRoleLinkDO(2L, 7L));
        list2.add(new SysUserRoleLinkDO(5L, 1L));
        int j = sysUserRoleLinkDAO.batchInsert(list2);
        return i + j;
    }

接下來我們來分析一下該註解的幾個屬性

3.3.1.value、transactionManager屬性

它們兩個是一樣的意思。當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。大多數項目只需要一個事務管理器。然而,有些項目爲了提高效率、或者有多個完全不同又不相干的數據源,從而使用了多個事務管理器。

3.3.2.propagation屬性

propagation用於指定事務的傳播行爲,這裏先來說一下事務傳播行爲:當事務方法被另一個事務方法調用時(例如一個Service去調用另一個Service),必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。Spring定義了七種傳播行爲:

propagation屬性 事務屬性-傳播行爲 含義
REQUIRED TransactionDefinition.PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務,則加入到這個事務中。這是最常見的選擇。
SUPPORTS TransactionDefinition.PROPAGATION_SUPPORTS 支持當前事務,如果當前沒有事務,就以非事務方式執行。
MANDATORY TransactionDefinition.PROPAGATION_MANDATORY 表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常。
REQUIRES_NEW TransactionDefinition.PROPAGATION_REQUIRES_NEW 表示當前方法必須運行在它自己的事務中。一個新的事務將被啓動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。
NOT_SUPPORTED TransactionDefinition.PROPAGATION_NOT_SUPPORTED 表示該方法不應該運行在事務中。如果當前存在事務,就把當前事務掛起。
NEVER TransactionDefinition.PROPAGATION_NEVER 表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常。
NESTED TransactionDefinition.PROPAGATION_NESTED 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

3.3.3.isolation屬性

定義事務的隔離級別,前面說到事務具有隔離性,在實際開發過程中,我們絕大部分的事務都是有併發情況。下多個事務併發運行,經常會操作相同的數據來完成各自的任務。在這種情況下可能會導致以下的問題:

  • 髒讀(Dirty reads)—— 事務A讀取了事務B更新的數據,然後B回滾操作,那麼A讀取到的數據是髒數據。
  • 不可重複讀(Nonrepeatable read)—— 事務 A 多次讀取同一數據,事務 B 在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
  • 幻讀(Phantom read)—— 系統管理員A將數據庫中所有學生的成績從具體分數改爲ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。

Spring定義了5種隔離規則,如下所示:

@isolation屬性 事務屬性-隔離規則 含義 髒讀 不可重複讀 幻讀
DEFAULT TransactionDefinition.ISOLATION_DEFAULT 使用後端數據庫默認的隔離級別    
READ_UNCOMMITTED TransactionDefinition.ISOLATION_READ_UNCOMMITTED 允許讀取尚未提交的數據變更(最低的隔離級別)
READ_COMMITTED TransactionDefinition.ISOLATION_READ_COMMITTED 允許讀取併發事務已經提交的數據
REPEATABLE_READ TransactionDefinition.ISOLATION_REPEATABLE_READ 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改
SERIALIZABLE TransactionDefinition.ISOLATION_SERIALIZABLE 最高的隔離級別,完全服從ACID的隔離級別,也是最慢的事務隔離級別,因爲它通常是通過完全鎖定事務相關的數據庫表來實現的

3.3.4.timeout屬性

事務的超時時間,默認值爲-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。

3.3.5.read-only屬性

指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。

3.3.6. rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

ollbackFor、rollbackForClassName用於設置那些異常需要回滾;noRollbackFor、noRollbackForClassName用於設置那些異常不需要回滾。他們就是在設置事務的回滾規則

(注:在阿里巴巴規範中,需要在Transactional註解指定rollbackFor或者在方法中顯式的rollback)

 

4.@Transactional註解使用須知

4.1.@Transactional註解可以作用於哪些地方?

  • 作用於類:當把@Transactional 註解放在類上時,表示所有該類的public方法都配置相同的事務屬性信息
  • 作用於方法:當類配置了@Transactional,方法也配置了@Transactional,方法的事務會覆蓋類的事務配置信息。
  • 作用於接口:不推薦這種使用方法,因爲一旦標註在Interface上並且配置了Spring AOP 使用CGLib動態代理,將會導致@Transactional註解失效

4.2.@Transactional失效的場景

此註解有很多失效的場景,但前提是,上面的基本環境你已經配置好了,再接着如下分析,希望對你有幫助

4.2.1@Transactional 應用在非 public 修飾的方法上

如果Transactional註解應用在非public修飾的方法上,Transactional將會失效。

 

之所以會失效是因爲在Spring AOP 代理時,如上圖所示 TransactionInterceptor (事務攔截器)在目標方法執行前後進行攔截,DynamicAdvisedInterceptor(CglibAopProxy 的內部類)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute

方法,獲取Transactional 註解的事務配置信息。

 

此方法會檢查目標方法的修飾符是否爲 public,不是 public則不會獲取@Transactional 的屬性配置信息。

注意:protected、private修飾的方法上使用 @Transactional 註解,雖然事務無效,但不會有任何報錯,這是我們很容犯錯的一點。

4.2.2.@Transactional 註解屬性 propagation 設置錯誤

這種失效是由於配置錯誤,若是錯誤的配置以下三種 propagation,事務將不會發生回滾。

TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。

TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。

4.2.3.@Transactional 註解屬性 rollbackFor 設置錯誤

rollbackFor 可以指定能夠觸發事務回滾的異常類型。Spring默認拋出了未檢查unchecked異常(繼承自 RuntimeException 的異常)或者 Error纔回滾事務;其他異常不會觸發回滾事務。如果在事務中拋出其他類型的異常,但卻期望 Spring 能夠回滾事務,就需要指定 rollbackFor屬性。

4.2.4.異常被你的 catch“吃了”導致@Transactional失效

這種情況是最常見的一種@Transactional註解失效場景,在A方法中調用了B方法,如果B方法內部拋了異常,而A方法此時try catch了B方法的異常,這時候@Transacational就無效了

4.2.5.同一個類中方法調用,導致@Transactional失效

開發中避免不了會對同一個類裏面的方法調用,比如有一個類Test,它的一個方法A,A再調用本類的方法B(不論方法B是用public還是private修飾),但方法A沒有聲明註解事務,而B方法有。則外部調用方法A之後,方法B的事務是不會起作用的。這也是經常犯錯誤的一個地方。

那爲啥會出現這種情況?其實這還是由於使用Spring AOP代理造成的,因爲只有當事務方法被當前類以外的代碼調用時,纔會由Spring生成的代理對象來管理。

4.2.5.數據庫引擎不支持事務管理

這種情況不太常見,MySQL是支持的

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章