7000+字的Spring事務總結來啦!我飽了!

本文已經收錄進 Github 75k+ Star 的Java項目JavaGuide 。JavaGuide項目地址 : github.com/Snailclimb/… 。強烈建議學習 Java的朋友看看!html

你們好,我是 Guide 哥,前段答應讀者的 Spring 事務分析總結終於來了。這部份內容比較重要,不管是對於工做仍是面試,可是網上比較好的參考資料比較少。 相關閱讀:V2.0 版本的 《JavaGuide面試突擊版》來啦!帶着它的在線閱讀版原本啦!java

若是本文有任何不對或者須要完善的地方,請幫忙指出!Guide 哥感激涕零!mysql

1. 什麼是事務?

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

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

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

public void savePerson() {
 personDao.save(person);  personDetailDao.save(personDetail);  } 複製代碼

另外,須要格外注意的是:事務可否生效數據庫引擎是否支持事務是關鍵。好比經常使用的 MySQL 數據庫默認使用支持事務的innodb引擎。可是,若是把數據庫引擎變爲 myisam,那麼程序也就再也不支持事務了!面試

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

  1. 將小明的餘額減小 1000 元spring

  2. 將小紅的餘額增長 1000 元。sql

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

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 接口能夠被看做是事務上層的管理者,而 TransactionDefinitionTransactionStatus 這兩個接口能夠看做是事物的描述。

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.NESTEDPropagation.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 舉例子,根據官網 dev.mysql.com/doc/refman/… 描述:

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 屬性,不然事務可能會回滾失敗
  4. ......

4. Reference

  1. [總結]Spring 事務管理中@Transactional 的參數: www.mobabel.net/spring 事務管理…
  2. Spring 官方文檔: docs.spring.io/spring/docs…
  3. 《Spring5 高級編程》
  4. 透徹的掌握 Spring 中@transactional 的使用: www.ibm.com/developerwo…
  5. Spring 事務的傳播特性: github.com/love-somnus…
  6. Spring 事務傳播行爲詳解segmentfault.com/a/119000001…
  7. 全面分析 Spring 的編程式事務管理及聲明式事務管理: www.ibm.com/developerwo…

本文使用 mdnice 排版

相關文章
相關標籤/搜索