Spring事務總結
1. 什麼是事務?
事務是邏輯上的一組操作,要麼都執行,要麼都不執行。
我們系統的每個業務方法可能包括了多個原子性的資料庫操作,比如下面的savePerson()
方法中就有兩個原子性的資料庫操作。這些原子性的資料庫操作是有依賴的,它們要麼都執行,要不就都不執行。
public void savePerson() {
personDao.save(person);
personDetailDao.save(personDetail);
}
另外,需要格外注意的是:事務能否生效資料庫引擎是否支援事務是關鍵。比如常用的 MySQL 資料庫預設使用支援事務的innodb
引擎。但是,如果把資料庫引擎變為myisam
事務最經典也經常被拿出來說例子就是轉賬了。假如小明要給小紅轉賬 1000 元,這個轉賬會涉及到兩個關鍵操作就是:
-
將小明的餘額減少 1000 元
-
將小紅的餘額增加 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)瞭解麼?
- 原子性(Atomicity):一個事務(transaction)中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。即,事務不可分割、不可約簡。
- 一致性(Consistency):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設約束、觸發器、級聯回滾等。
- 隔離性(Isolation):資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。事務隔離分為不同級別,包括未提交讀(Read uncommitted)、提交讀(read committed)、可重複讀(repeatable read)和序列化(Serializable)。
- 永續性(Durability):事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。
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
註解預設使用就是這個事務傳播行為。如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。也就是說:
- 如果外部方法沒有開啟事務的話,
Propagation.REQUIRED
修飾的內部方法會新開啟自己的事務,且開啟的事務相互獨立,互不干擾。 - 如果外部方法開啟事務並且被
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
。也就是說:
- 在外部方法未開啟事務的情況下
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修飾的內部方法都會新開啟自己的事務,且開啟的事務相互獨立,互不干擾。 - 如果外部方法開啟事務的話,
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 8.0 該命令改為SELECT @@transaction_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
(可重讀)並不會什麼任何效能上的損失。
更多關於事務隔離級別的內容請看:
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
會開啟一個單獨的事務,中間被其它事務改了資料,都會實時讀取到最新值。
分享一下關於事務只讀屬性,其他人的解答:
- 如果你一次執行單條查詢語句,則沒有必要啟用事務支援,資料庫預設支援 SQL 執行期間的讀一致性;
- 如果你一次執行多條查詢語句,例如統計查詢,報表查詢,在這種場景下,多條查詢 SQL 必須保證整體的讀一致性,否則,在前條 SQL 查詢之後,後條 SQL 查詢之前,資料被其他使用者改變,則該次整體的統計查詢將會出現讀資料不一致的狀態,此時,應該啟用事務支援
3.3.4. 事務回滾規則
這些規則定義了哪些異常會導致事務回滾而哪些不會。預設情況下,事務只有遇到執行期異常(RuntimeException 的子類)時才會回滾,Error 也會導致事務回滾,但是,在遇到檢查型(Checked)異常時不會回滾。
如果你想要回滾你定義的特定的異常型別的話,可以這樣:
@Transactional(rollbackFor= MyException.class)
3.4. @Transactional 註解使用詳解
1)@Transactional
的作用範圍
- 方法:推薦將註解使用於方法上,不過需要注意的是:該註解只能應用到 public 方法上,否則不生效。
- 類:如果這個註解使用在類上的話,表明該註解對該類中所有的 public 方法都生效。
- 介面:不推薦在介面上使用。
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
的使用注意事項總結
@Transactional
註解只有作用到 public 方法上事務才生效,不推薦在介面上使用;- 避免同一個類中呼叫
@Transactional
註解的方法,這樣會導致事務失效; - 正確的設定
@Transactional
的 rollbackFor 和 propagation 屬性,否則事務可能會回滾失敗 - ......
4. Reference
- [總結]Spring 事務管理中@Transactional 的引數:http://www.mobabel.net/spring 事務管理中 transactional 的引數/
- Spring 官方文件:https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html
- 《Spring5 高階程式設計》
- 透徹的掌握 Spring 中@transactional 的使用:https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
- Spring 事務的傳播特性:https://github.com/love-somnus/Spring/wiki/Spring 事務的傳播特性
- Spring 事務傳播行為詳解:https://segmentfault.com/a/1190000013341344
- 全面分析 Spring 的程式設計式事務管理及宣告式事務管理:https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html
作者:Snailclimb
連結:Spring事務總結
來源:github