什麼是事務傳播機制
事務的傳播機制,顧名思義就是多個事務方法之間呼叫,事務如何在這些方法之間傳播。
舉個例子,方法 A 是一個事務的方法,方法 A 執行的時候呼叫了方法 B,此時方法 B 有無事務以及是否需要事務都會對方法 A 和方法 B 產生不同的影響,而這個影響是由兩個方法的事務傳播機制決定的。
傳播屬性 Propagation 列舉
Spring 對事務的傳播機制在 Propagation 列舉中定義了7個分類:
- REQUIRED 預設
- SUPPORTS 支援
- MANDATORY 強制
- REQUIRES_NEW 新建
- NOT_SUPPORTED 不支援
- NEVER 從不
- NESTED 巢狀
事務的傳播機制,是 spring 規定的。因為在開發中,最簡單的事務是,業務程式碼都處於同一個事務下,這也是預設的傳播機制,如果出現的報錯,所有的資料回滾。
但是在處理複雜的業務邏輯時,方法之間的呼叫,有以下的需求:
- 呼叫的方法需要新增一個事務,新事務和原來的事務各自獨立。
- 呼叫的方法不支援事務
- 呼叫的方法是一個巢狀的事務
7種傳播機制詳解
首先建立兩個方法 A 和 B 實現資料的插入,插入資料A:
public class AService {
public void A(String name) {
userService.insertName("A-" + name);
}
}
插入資料B:
public class BService {
public void B(String name) {
userService.insertName("B-" + name);
}
}
使用虛擬碼建立 mainTest 方法和 childTest 方法
public void mainTest(String name) {
// 存入a1
A(a1);
childTest(name);
}
main 呼叫 test 方法,其中
public void childTest(String name) {
// 存入b1
B(b1);
throw new RuntimeException();
// 存入 b2
B2(b2);
}
以上虛擬碼,呼叫 mainTest 方法,如果mainTest 和childTest 都不使用事務的話,資料儲存的結果是如何呢?
因為都沒使用事務,所以 a1 和 b1 都存到成功了,而之後丟擲異常之後,b2是不會執行的。所以 a1 和 b1 都插入的資料,而 b2 沒有插入資料。
REQUIRED(預設事務)
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
如果當前不存在事務,就新建一個事務。如果存在事務,就加入到當前事務。這是一個預設的事務。
示例1:根據場景舉個例子,在 childTest 新增事務,設定傳播屬性為 REQUIRED,虛擬碼如下:
public void mainTest(String name) {
// 存入a1
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.REQUIRED)
public void childTest(String name) {
// 存入b1
B(b1);
throw new RuntimeException();
}
因為 mainTest 沒有事務,而 childTest 又是新建一個事務,所以 a1 新增成功。在 childTest 因為丟擲了異常,不會執行 b2 新增,而 b1 新增回滾。最終 a1 新增成功,b1沒新增成功。
示例2:在 mainTest 和 childTest 都新增事務,傳播屬性都為 REQUIRED,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
// 存入a1
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.REQUIRED)
public void childTest(String name) {
// 存入b1
B(b1);
throw new RuntimeException();
}
根據 REQUIRED 傳播屬性,如果存在事務,就加入到當前事務。兩個方法都屬於同一個事務,同一個事務的話,如果有發生異常,則全部都回滾。所以 a1 和 b1 都沒新增成功。
SUPPORTS
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
*/
如果當前沒有事務,則以非事務的方式執行。如果存在事務,就加入到當前事務。
示例3:childTest 新增事務,傳播屬性設定為 SUPPORTS,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
傳播屬性為 SUPPORTS,如果沒有事務,就以非事務的方式執行。表明兩個方法都沒有使用事務,沒有事務的話,a1、b1 都新增成功。
示例4:mainTest 新增事務,設定傳播屬性為 REQUIRED。childTest 新增事務,設定傳播屬性為 SUPPORTS,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
B2(b2);
}
SUPPORTS 傳播屬性,如果存在事務,就加入到當前事務。mainTest 和 childTest 都屬於同一個事務,而 childTest 丟擲異常,a1 和b1 新增都回滾,最終 a1、b1 新增失敗。
MANDATORY
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
如果存在事務,就加入到當前事務。如果不存在事務,就報錯。
這就說明如果想呼叫 MANDATORY 傳播屬性的方法,一定要有事務,不然就會報錯。
MANDATORY 類似功能限制,必須要被有事務的方法的呼叫,不然就會報錯。
示例5: 首先在 childTest 新增事務,設定傳播屬性為 MANDATORY,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.MANDATORY)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
B2(b2);
}
在控制檯直接報錯:
No existing transaction found for transaction marked with propagation 'mandatory'
說明被標記為 mandatory 傳播屬性沒找到事務,直接報錯。因為 mainTest 沒有事務,a1 新增成功。而 childTest 由於報錯,b1 新增失敗。
示例6: mainTest 新增事務,設定傳播屬性為 REQUIRED。childTest 新增事務,設定傳播屬性為 MANDATOR,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.MANDATORY)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
如果存在事務,就把事務加入到當前事務。同一個事務中 childTest 丟擲異常,a1 和 b1 新增被回滾,所以a1 和 b1新增失敗。
REQUIRES_NEW
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
*/
建立一個新的事務。如果存在事務,就將事務掛起。
無論是否有事務,都會建立一個新的事務。
示例7:childTest 新增事務,設定傳播屬性為 REQUIRES_NEW,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
mainTest 不存在事務,a1 新增成功,childTest 新建了一個事務,報錯,回滾 b1。所以 a1 新增成功,b1 新增失敗。
示例8:mainTest 新增事務,設定傳播屬性為 REQUIRED。childTest 新增事務,設定傳播屬性為 REQUIRES_NEW,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
mainTest 建立了一個事務,childTest 新建一個事務,在 childTest 事務中,丟擲異常,b1 回滾,異常拋到 mainTest 方法,a1 也回滾,最終 a1 和 b1 都回滾。
示例9:在示例8中,如果不想讓 REQUIRES_NEW 傳播屬性影響到被呼叫事務,將異常捕獲就不會影響到被呼叫事務。
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
try {
childTest(name);
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
childTest 丟擲了異常,在 mainTest 捕獲了,對 mainTest 沒有影響,所以 b1 被回滾,b1 新增失敗,a1 新增成功。
示例10:mainTest 設定傳播屬性為 REQUIRED,並在 mainTest 丟擲異常。childTest 同樣設定 REQUIRES_NEW 傳播屬性,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childTest(String name) {
B(b1);
B2(b2);
}
childTest 是一個新建的事務,只要不丟擲異常是不會回滾,所以 b1 新增成功,而 mainTest 丟擲了異常,a1 新增失敗。
REQUIRES_NEW 傳播屬性如果有異常,只會從被呼叫方影響呼叫方,而呼叫方不會影響呼叫方,即 childTest 丟擲異常會影響 mainTest,而 mainTest 丟擲異常不會到 childTest。
NOT_SUPPORTED
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
*/
無論是否存在當前事務,都是以非事務的方式執行。
示例11:childTest 新增事務,設定傳播屬性為 NOT_SUPPORTED,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
NOT_SUPPORTED 都是以非事務的方式執行,childTest 不存在事務,b1 新增成功。而 mainTest 也是沒有事務,a1 也新增成功。
示例12:childTest 新增事務,設定傳播屬性為 NOT_SUPPORTED,mainTest 新增預設傳播屬性 REQUIRED,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
其中 childTest 都是以非事務的方式執行,b1 新增成功。而 mainTest 存在事務,報錯後回滾,a1 新增失敗。
NEVER
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
不使用事務,如果存在事務,就丟擲異常。
NEVER 的方法不使用事務,呼叫 NEVER 方法如果有事務,就丟擲異常。
示例13:childTest 新增 NEVER 傳播屬性,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.NEVER)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
B2(b2);
}
NEVER 不使用事務,mainTest 也不使用事務,所以 a1 和 b1 都新增成功,b2新增失敗。
示例14: mainTest 新增 REQUIRED 傳播屬性,childTest 傳播屬性設定為 NEVER,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.NEVER)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
mainTest 存在事務,導致 childTest 報錯,b1新增失敗,childTest 拋錯到 mainTest,a1 新增失敗。
NESTED
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
* <p>Note: Actual creation of a nested transaction will only work on specific
*/
如果當前事務存在,就執行一個巢狀事務。如果不存在事務,就和 REQUIRED 一樣新建一個事務。
示例15: childTest 設定 NESTED 傳播屬性,虛擬碼如下:
public void mainTest(String name) {
A(a1);
childTest(name);
}
@Transactional(propagation = Propagation.NESTED)
public void childTest(String name) {
B(b1);
throw new RuntimeException();
}
在 childTest 設定 NESTED 傳播屬性,相當於新建一個事務,所以 b1 新增失敗, mainTest 沒有事務,a1 新增成功。
示例16:設定 mainTest 傳播屬性為 REQUIRED,新建一個事務,並在方法最後丟擲異常。 childTest 設定屬性為 NESTED,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
childTest(name);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NESTED)
public void childTest(String name) {
B(b1);
B2(b2);
}
childTest 是一個巢狀的事務,當主事務的丟擲異常時,巢狀事務也受影響,即 a1、b1 和 b2 都新增失敗。和示例10不同的是,示例10不會影響 childTest 事務。
- NESTED 和 REQUIRED_NEW 的區別:
- REQUIRED_NEW 是開啟一個新的事務,和呼叫的事務無關。呼叫方回滾,不會影響到 REQUIRED_NEW 事務。
- NESTED 是一個巢狀事務,是呼叫方的一個子事務,如果呼叫方事務回滾,NESTED 也會回滾。
示例17:和示例16一樣,在 mainTest 設定傳播屬性為 REQUIRED,childTest 設定傳播屬性為 NESTED,不同的是,在 mainTest 捕獲 childTest 丟擲的異常,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
try {
childTest(name);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.NESTED)
public void childTest(String name) {
B(b1);
B2(b2);
throw new RuntimeException();
}
childTest 是一個子事務,報錯回滾,b1 和 b2 都新增失敗。而 mainTest 捕獲了異常,不受異常影響,a1 新增成功。
示例18:將示例2改造一下,mainTest 捕獲 childTest 丟擲的異常,虛擬碼如下:
@Transactional(propagation = Propagation.REQUIRED)
public void mainTest(String name) {
A(a1);
try {
childTest(name);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void childTest(String name) {
B(b1);
B2(b2);
throw new RuntimeException();
}
mainTest 和 childTest 兩個方法都處於同一個事務,如果有一個方法報錯回滾,並且被捕獲。整個事務如果還有資料新增就會丟擲 Transaction rolled back because it has been marked as rollback-only
異常,同一個事務,不能出現有的回滾了,有的不回滾,要麼一起回滾,要不一起執行成功。所以全部資料都新增失敗。
- 對比示例17和示例18,NESTED 和 REQUIRED 的區別:
- REQUIRED 傳播屬性表明呼叫方和被呼叫方都是使用同一個事務,被呼叫方出現異常,無論異常是否被捕獲,因為屬於同一個事務,只要發生異常,事務都會回滾。
- NESTED 被呼叫方出現異常,只要異常被捕獲,只有被呼叫方事務回滾,呼叫方事務不受影響。
總結
傳播屬性 | 總結 |
---|---|
REQUIRED | 預設屬性,所有的事務都處於同一個事務下,出現異常,不管是否捕獲所有事務回滾 |
SUPPORTS | 如果不存事務,就以非事務的方式執行,存在事務就加入該事務 |
MANDATORY | 強制呼叫方新增事務,如果不存在事務就報錯,存在事務就加入該事務 |
REQUIRES_NEW | 無論呼叫方是否存在事務,都會建立新的事務,並且呼叫方異常不會影響 REQUIRES_NEW事務 |
NOT_SUPPORTED | 無論是否呼叫方是否存在事務,都是以非事務的方式執行,出現異常也會回滾 |
NEVER | 不用事務,存在事務就報錯,和 MANDATORY 相反 |
NESTED | 巢狀事務,新建一個子事務,事務執行相互獨立,如果呼叫方出現異常,直接回滾 |
測試原始碼
參考
如果覺得文章對你有幫助的話,請點個推薦吧!