耗時21天!1.5W字的Spring事務總結,這應該是最漂亮的講解了

SSM框架,程序員首次接觸的Java框架,而到現在,隨着互聯網的發展,單純的會一個ssm早已經不足以支持你的發展,但是現在,ssm依舊問的比較多,不過,就是問的越來越底層,問的越來越深入,其中,這三兄弟中最讓人頭疼的就是面試問spring源碼,而spring源碼中最讓人頭疼的個人覺得應該就是spring事務

隨意,今天,萬字文章,通過代碼+圖片,講解spring事務,話不多說,進入正題吧

 


 

1. 什麼是事務?

事務是邏輯上的一組操作,要麼都執行,要麼都不執行。

Guide 哥:大家應該都能背上面這句話了,下面我結合我們日常的真實開發來談一談。

我們系統的每個業務方法可能包括了多個原子性的數據庫操作,比如下面的 savePerson() 方法中就有兩個原子性的數據庫操作。這些原子性的數據庫操作是有依賴的,它們要麼都執行,要不就都不執行。

 public void savePerson() {
  personDao.save(person);
  personDetailDao.save(personDetail);
 }

另外,需要格外注意的是:事務能否生效數據庫引擎是否支持事務是關鍵。比如常用的 MySQL 數據庫默認使用支持事務的innodb引擎。但是,如果把數據庫引擎變爲 myisam,那麼程序也就不再支持事務了!

事務最經典也經常被拿出來說例子就是轉賬了。假如小明要給小紅轉賬 1000 元,這個轉賬會涉及到兩個關鍵操作就是:

  1. 將小明的餘額減少 1000 元
  2. 將小紅的餘額增加 1000 元。

萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰或者網絡故障,導致小明餘額減少而小紅的餘額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要麼都成功,要麼都要失敗。

public class OrdersService {
 private AccountDao accountDao;

 public void setOrdersDao(AccountDao accountDao) {
  this.accountDao = accountDao;
 }

  @Transactional(propagation = Propagation.REQUIRED,
                isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
 public void accountMoney() {
    //小紅賬戶多1000
  accountDao.addMoney(1000,xiaohong);
  //模擬突然出現的異常,比如銀行中可能爲突然停電等等
    //如果沒有配置事務管理的話會造成,小紅賬戶多了1000而小明賬戶沒有少錢
  int i = 10 / 0;
  //小王賬戶少1000
  accountDao.reduceMoney(1000,xiaoming);
 }
}

另外,數據庫事務的 ACID 四大特性是事務的基礎,下面簡單來了解一下。

2. 事物的特性(ACID)瞭解麼?

  • 原子性: 事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要麼完全不起作用;
  • 一致性: 執行事務前後,數據保持一致;
  • 隔離性: 併發訪問數據庫時,一個用戶的事物不被其他事務所幹擾也就是說多個事務併發執行時,一個事務的執行不應影響其他事務的執行;
  • 持久性: 一個事務被提交之後。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。

3. 詳談 Spring 對事務的支持

再提醒一次:你的程序是否支持事務首先取決於數據庫 ,比如使用 MySQL 的話,如果你選擇的是 innodb 引擎,那麼恭喜你,是可以支持事務的。但是,如果你的 MySQL 數據庫使用的是 myisam 引擎的話,那不好意思,從根上就是不支持事務的。

這裏再多提一下一個非常重要的知識點: MySQL 怎麼保證原子性的?

我們知道如果想要保證事務的原子性,就需要在異常發生時,對已經執行的操作進行回滾,在 MySQL 中,恢復機制是通過 回滾日誌(undo log) 實現的,所有事務進行的修改都會先先記錄到這個回滾日誌中,然後再執行相關的操作。如果執行過程中遇到異常的話,我們直接利用回滾日誌 中的信息將數據回滾到修改之前的樣子即可!並且,回滾日誌會先於數據持久化到磁盤上。這樣就保證了即使遇到數據庫突然宕機等情況,當用戶再次啓動數據庫的時候,數據庫還能夠通過查詢回滾日誌來回滾將之前未完成的事務。

3.1. Spring 支持兩種方式的事務管理

1).編程式事務管理

通過 TransactionTemplate或者TransactionManager手動管理事務,實際應用中很少使用,但是對於你理解 Spring 事務管理原理有幫助。

使用TransactionTemplate 進行編程式事務管理的示例代碼如下:

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  業務代碼
                } catch (Exception e){
                    //回滾
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}

使用 TransactionManager 進行編程式事務管理的示例代碼如下:

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  業務代碼
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}

2)聲明式事務管理

推薦使用(代碼侵入性最小),實際是通過 AOP 實現(基於@Transactional 的全註解方式使用最多)。

使用 @Transactional註解進行事務管理的示例代碼如下:

@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
  //do something
  B b = new B();
  C c = new C();
  b.bMethod();
  c.cMethod();
}

3.2. Spring 事務管理接口介紹

Spring 框架中,事務管理相關最重要的 3 個接口如下:

  • PlatformTransactionManager: (平臺)事務管理器,Spring 事務策略的核心。
  • TransactionDefinition: 事務定義信息(事務隔離級別、傳播行爲、超時、只讀、回滾規則)。
  • TransactionStatus: 事務運行狀態。

我們可以把 PlatformTransactionManager 接口可以被看作是事務上層的管理者,而 TransactionDefinition 和 TransactionStatus 這兩個接口可以看作是事物的描述。

PlatformTransactionManager 會根據 TransactionDefinition 的定義比如事務超時時間、隔離界別、傳播行爲等來進行事務管理 ,而 TransactionStatus 接口則提供了一些方法來獲取事務相應的狀態比如是否新事務、是否可以回滾等等。

3.2.1. PlatformTransactionManager:事務管理接口

Spring 並不直接管理事務,而是提供了多種事務管理器 。Spring 事務管理器的接口是: PlatformTransactionManager 。

通過這個接口,Spring 爲各個平臺如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。

PlatformTransactionManager 接口的具體實現如下:

PlatformTransactionManager接口中定義了三個方法:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
    //獲得事務
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事務
    void commit(TransactionStatus var1) throws TransactionException;
    //回滾事務
    void rollback(TransactionStatus var1) throws TransactionException;
}

這裏多插一嘴。爲什麼要定義或者說抽象出來PlatformTransactionManager這個接口呢?

主要是因爲要將事務管理行爲抽象出來,然後不同的平臺去實現它,這樣我們可以保證提供給外部的行爲不變,方便我們擴展。我前段時間分享過:“爲什麼我們要用接口?”

3.2.2. TransactionDefinition:事務屬性

事務管理器接口 PlatformTransactionManager 通過 getTransaction(TransactionDefinition definition) 方法來得到一個事務,這個方法裏面的參數是 TransactionDefinition 類 ,這個類就定義了一些基本的事務屬性。

那麼什麼是 事務屬性 呢?

事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。

事務屬性包含了 5 個方面:

TransactionDefinition 接口中定義了 5 個方法以及一些表示事務屬性的常量比如隔離級別、傳播行爲等等。

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    // 返回事務的傳播行爲,默認值爲 REQUIRED。
    int getPropagationBehavior();
    //返回事務的隔離級別,默認值是 DEFAULT
    int getIsolationLevel();
    // 返回事務的超時時間,默認值爲-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。
    int getTimeout();
    // 返回是否爲只讀事務,默認值爲 false
    boolean isReadOnly();

    @Nullable
    String getName();
}

3.2.3. TransactionStatus:事務狀態

TransactionStatus接口用來記錄事務的狀態 該接口定義了一組方法,用來獲取或判斷事務的相應狀態信息。

PlatformTransactionManager.getTransaction(…)方法返回一個 TransactionStatus 對象。

TransactionStatus 接口接口內容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢復點
    void setRollbackOnly();  // 設置爲只回滾
    boolean isRollbackOnly(); // 是否爲只回滾
    boolean isCompleted; // 是否已完成
}

3.3. 事務屬性詳解

實際業務開發中,大家一般都是使用 @Transactional 註解來開啓事務,很多人並不清楚這個參數裏面的參數是什麼意思,有什麼用。爲了更好的在項目中使用事務管理,強烈推薦好好閱讀一下下面的內容。

3.3.1. 事務傳播行爲

事務傳播行爲是爲了解決業務層方法之間互相調用的事務問題

當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。

舉個例子!

我們在 A 類的aMethod()方法中調用了 B 類的 bMethod() 方法。這個時候就涉及到業務層方法之間互相調用的事務問題。如果我們的 bMethod()如果發生異常需要回滾,如何配置事務傳播行爲才能讓 aMethod()也跟着回滾呢?這個時候就需要事務傳播行爲的知識了,如果你不知道的話一定要好好看一下。

Class A {
    @Transactional(propagation=propagation.xxx)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation=propagation.xxx)
    public void bMethod {
       //do something
    }
}

在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    ......
}

不過如此,爲了方便使用,Spring 會相應地定義了一個枚舉類:Propagation

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

public enum 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);


 private final int value;

 Propagation(int value) {
  this.value = value;
 }

 public int value() {
  return this.value;
 }

}

正確的事務傳播行爲可能的值如下 :

1.TransactionDefinition.PROPAGATION_REQUIRED

使用的最多的一個事務傳播行爲,我們平時經常使用的@Transactional註解默認使用就是這個事務傳播行爲。如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。也就是說:

  1. 如果外部方法沒有開啓事務的話,Propagation.REQUIRED修飾的內部方法會新開啓自己的事務,且開啓的事務相互獨立,互不干擾。
  2. 如果外部方法開啓事務並且被Propagation.REQUIRED的話,所有Propagation.REQUIRED修飾的內部方法和外部方法均屬於同一事務 ,只要一個方法回滾,整個事務均回滾。

舉個例子:如果我們上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED傳播行爲的話,兩者使用的就是同一個事務,只要其中一個方法回滾,整個事務均回滾。

Class A {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void bMethod {
       //do something
    }
}

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

創建一個新的事務,如果當前存在事務,則把當前事務掛起。也就是說不管外部方法是否開啓事務,Propagation.REQUIRES_NEW修飾的內部方法會新開啓自己的事務,且開啓的事務相互獨立,互不干擾。

舉個例子:如果我們上面的bMethod()使用PROPAGATION_REQUIRES_NEW事務傳播行爲修飾,aMethod還是用PROPAGATION_REQUIRED修飾的話。如果aMethod()發生異常回滾,bMethod()不會跟着回滾,因爲 bMethod()開啓了獨立的事務。但是,如果 bMethod()拋出了未被捕獲的異常並且這個異常滿足事務回滾規則的話,aMethod()同樣也會回滾,因爲這個異常被aMethod()的事務管理機制檢測到了。

Class A {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
    }
}

Class B {
    @Transactional(propagation=propagation.REQUIRES_NEW)
    public void bMethod {
       //do something
    }
}

3.TransactionDefinition.PROPAGATION_NESTED:

如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。也就是說:

  1. 在外部方法未開啓事務的情況下Propagation.NESTED和Propagation.REQUIRED作用相同,修飾的內部方法都會新開啓自己的事務,且開啓的事務相互獨立,互不干擾。
  2. 如果外部方法開啓事務的話,Propagation.NESTED修飾的內部方法屬於外部事務的子事務,外部主事務回滾的話,子事務也會回滾,而內部子事務可以單獨回滾而不影響外部主事務和其他子事務。

這裏還是簡單舉個例子:

如果 aMethod() 回滾的話,bMethod()和bMethod2()都要回滾,而bMethod()回滾的話,並不會造成 aMethod() 和bMethod()回滾。

Class A {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
        //do something
        B b = new B();
        b.bMethod();
        b.bMethod2();
    }
}

Class B {
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void bMethod {
       //do something
    }
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void bMethod2 {
       //do something
    }
}

4.TransactionDefinition.PROPAGATION_MANDATORY

如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。(mandatory:強制性)

這個使用的很少,就不舉例子來說了。

若是錯誤的配置以下 3 種事務傳播行爲,事務將不會發生回滾,這裏不對照案例講解了,使用的很少。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事務方式運行,如果當前存在事務,則拋出異常。

更多關於事務傳播行爲的內容請看這篇文章:《太難了~面試官讓我結合案例講講自己對 Spring 事務傳播行爲的理解。》

3.3.2 事務隔離級別

TransactionDefinition 接口中定義了五個表示隔離級別的常量:

public interface TransactionDefinition {
    ......
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    ......
}

和事務傳播行爲這塊一樣,爲了方便使用,Spring 也相應地定義了一個枚舉類:Isolation

public enum 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);

 private final int value;

 Isolation(int value) {
  this.value = value;
 }

 public int value() {
  return this.value;
 }

}

下面我依次對每一種事務隔離級別進行介紹:

  • TransactionDefinition.ISOLATION_DEFAULT :使用後端數據庫默認的隔離級別,MySQL 默認採用的 REPEATABLE_READ 隔離級別 Oracle 默認採用的 READ_COMMITTED 隔離級別.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔離級別,使用這個隔離級別很少,因爲它允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔離級別,完全服從 ACID 的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

因爲平時使用 MySQL 數據庫比較多,這裏再多提一嘴!

MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看,如下:

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

這裏需要注意的是:與 SQL 標準不同的地方在於 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務隔離級別下使用的是 Next-Key Lock 鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server)是不同的。所以說 InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL 標準的 SERIALIZABLE(可串行化) 隔離級別。

因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是 READ-COMMITTED(讀取提交內容) :,但是你要知道的是 InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀) 並不會什麼任何性能上的損失。

更多關於事務隔離級別的內容請看:

  1. 《一文帶你輕鬆搞懂事務隔離級別(圖文詳解)》
  2. 面試官:你說對 MySQL 事務很熟?那我問你 10 個問題

3.3.3. 事務超時屬性

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒,默認值爲-1。

3.3.3. 事務只讀屬性

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
    ......
    // 返回是否爲只讀事務,默認值爲 false
    boolean isReadOnly();

}

對於只有讀取數據查詢的事務,可以指定事務類型爲 readonly,即只讀事務。只讀事務不涉及數據的修改,數據庫會提供一些優化手段,適合用在有多條數據庫查詢操作的方法中。

很多人就會疑問了,爲什麼我一個數據查詢操作還要啓用事務支持呢?

拿 MySQL 的 innodb 舉例子,根據官網https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html描述:

MySQL 默認對每一個新建立的連接都啓用了autocommit模式。在該模式下,每一個發送到 MySQL 服務器的sql語句都會在一個單獨的事務中進行處理,執行結束後會自動提交事務,並開啓一個新的事務。

但是,如果你給方法加上了Transactional註解的話,這個方法執行的所有sql會被放在一個事務中。如果聲明瞭只讀事務的話,數據庫就會去優化它的執行,並不會帶來其他的什麼收益。

如果不加Transactional,每條sql會開啓一個單獨的事務,中間被其它事務改了數據,都會實時讀取到最新值。

分享一下關於事務只讀屬性,其他人的解答:

  1. 如果你一次執行單條查詢語句,則沒有必要啓用事務支持,數據庫默認支持 SQL 執行期間的讀一致性;
  2. 如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場景下,多條查詢 SQL 必須保證整體的讀一致性,否則,在前條 SQL 查詢之後,後條 SQL 查詢之前,數據被其他用戶改變,則該次整體的統計查詢將會出現讀數據不一致的狀態,此時,應該啓用事務支持

3.3.4. 事務回滾規則

這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常(RuntimeException 的子類)時纔會回滾,Error 也會導致事務回滾,但是,在遇到檢查型(Checked)異常時不會回滾。

如果你想要回滾你定義的特定的異常類型的話,可以這樣:

@Transactional(rollbackFor= MyException.class)

3.4. @Transactional 註解使用詳解

1) @Transactional 的作用範圍

  1. 方法 :推薦將註解使用於方法上,不過需要注意的是:該註解只能應用到 public 方法上,否則不生效。
  2.  :如果這個註解使用在類上的話,表明該註解對該類中所有的 public 方法都生效。
  3. 接口 :不推薦在接口上使用。

2) @Transactional 的常用配置參數

@Transactional註解源碼如下,裏面包含了基本事務屬性的配置:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

 @AliasFor("transactionManager")
 String value() default "";

 @AliasFor("value")
 String transactionManager() default "";

 Propagation propagation() default Propagation.REQUIRED;

 Isolation isolation() default Isolation.DEFAULT;

 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

 boolean readOnly() default false;

 Class<? extends Throwable>[] rollbackFor() default {};

 String[] rollbackForClassName() default {};

 Class<? extends Throwable>[] noRollbackFor() default {};

 String[] noRollbackForClassName() default {};

}

@Transactional 的常用配置參數總結(只列鉅額 5 個我平時比較常用的):

屬性名說明propagation事務的傳播行爲,默認值爲 REQUIRED,可選的值在上面介紹過isolation事務的隔離級別,默認值採用 DEFAULT,可選的值在上面介紹過timeout事務的超時時間,默認值爲-1(不會超時)。如果超過該時間限制但事務還沒有完成,則自動回滾事務。readOnly指定事務是否爲只讀事務,默認值爲 false。rollbackFor用於指定能夠觸發事務回滾的異常類型,並且可以指定多個異常類型。

3)@Transactional 事務註解原理

面試中在問 AOP 的時候可能會被問到的一個問題。簡單說下吧!

我們知道,@Transactional 的工作機制是基於 AOP 實現的,AOP 又是使用動態代理實現的。如果目標對象實現了接口,默認情況下會採用 JDK 的動態代理,如果目標對象沒有實現了接口,會使用 CGLIB 動態代理。

多提一嘴:createAopProxy() 方法 決定了是使用 JDK 還是 Cglib 來做動態代理,源碼如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

 @Override
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
   Class<?> targetClass = config.getTargetClass();
   if (targetClass == null) {
    throw new AopConfigException("TargetSource cannot determine target class: " +
      "Either an interface or a target is required for proxy creation.");
   }
   if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    return new JdkDynamicAopProxy(config);
   }
   return new ObjenesisCglibAopProxy(config);
  }
  else {
   return new JdkDynamicAopProxy(config);
  }
 }
  .......
}

如果一個類或者一個類中的 public 方法上被標註@Transactional 註解的話,Spring 容器就會在啓動的時候爲其創建一個代理類,在調用被@Transactional 註解的 public 方法的時候,實際調用的是,TransactionInterceptor 類中的 invoke()方法。這個方法的作用就是在目標方法之前開啓事務,方法執行過程中如果遇到異常的時候回滾事務,方法調用完成之後提交事務。

TransactionInterceptor 類中的 invoke()方法內部實際調用的是 TransactionAspectSupport 類的 invokeWithinTransaction()方法。由於新版本的 Spring 對這部分重寫很大,而且用到了很多響應式編程的知識,這裏就不列源碼了。

4)Spring AOP 自調用問題

若同一類中的其他沒有 @Transactional 註解的方法內部調用有 @Transactional 註解的方法,有@Transactional 註解的方法的事務會失效。

這是由於Spring AOP代理的原因造成的,因爲只有當 @Transactional 註解的方法在類以外被調用的時候,Spring 事務管理才生效。

MyService 類中的method1()調用method2()就會導致method2()的事務失效。

@Service
public class MyService {

private void method1() {
     method2();
     //......
}
@Transactional
 public void method2() {
     //......
  }
}

解決辦法就是避免同一類中自調用或者使用 AspectJ 取代 Spring AOP 代理。

5) @Transactional 的使用注意事項總結

  1. @Transactional 註解只有作用到 public 方法上事務才生效,不推薦在接口上使用;
  2. 避免同一個類中調用 @Transactional 註解的方法,這樣會導致事務失效;
  3. 正確的設置 @Transactional 的 rollbackFor 和 propagation 屬性,否則事務可能會回滾失敗

覺得寫的不錯的,歡迎點贊+關注支持一下

關注公衆號:Java架構師聯盟,每日更新技術好文

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