什么是事務傳播機制
事務的傳播機制,顧名思義就是多個事務方法之間調用,事務如何在這些方法之間傳播。
舉個例子,方法 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 | 嵌套事務,新建一個子事務,事務執行相互獨立,如果調用方出現異常,直接回滾 |
測試源碼
參考
如果覺得文章對你有幫助的話,請點個推薦吧!