spring事務傳播屬性定義在org.springframework.transaction.TransactionDefinition接口,類似于EJB CMT的事務傳播屬性定義,主要有以下幾種類型:
propagation | 說明 |
---|---|
PROPAGATION_REQUIRED | 支持當前事務,如果當前沒有事務,則新建一個事務執(zhí)行 |
PROPAGATION_SUPPORTS | 支持當前事務,如果沒有當前事務,則以非事務的方式執(zhí)行 |
PROPAGATION_MANDATORY | 支持當前事務,如果當前沒有事務,則拋出異常 |
PROPAGATION_REQUIRES_NEW | 創(chuàng)建一個新的事務,如果當前已經有事務了,則將當前事務掛起 |
PROPAGATION_NOT_SUPPORTED | 不支持當前事務,而且總是以非事務方式執(zhí)行 |
PROPAGATION_NEVER | 不支持當前事務,如果存在事務,則拋出異常 |
PROPAGATION_NESTED | 如果當前事務存在,則在嵌套事務中執(zhí)行,否則行為類似于PROPAGATION_REQUIRED。 EJB中沒有類似的功能。 |
下面,用一個例子來解釋下各個傳播屬性的不同:
在數據庫中新建了一張產品表,兩條數據,一條產品id為1,產品名稱為IPhone6S,另一條產品id為2,產品名稱為MAC PRO。
PROPAGATION_REQUIRED
兩個服務serviceA和serviceB,serviceA的methodA方法先減IPhone6S的庫存,然后調用serviceB中methodB方法扣減MAC Pro庫存,為了觀察,methodA最后手動拋出異常,模擬回滾場景。
-
支持當前事務
在傳播屬性為PROPAGATION_REQUIRED時,事務加入方會使用當前事務,methodA代碼為:
public void methodA() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
try {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(status -> {
OrdProduct product = ordProductMapper.selectForUpdate(1);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() - 1);
int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
serviceB.methodB();
if (result == 1) {
throw new RuntimeException("test");
}
return result;
});
} finally {
OrdProduct product1 = ordProductMapper.selectForUpdate(1);
OrdProduct product2 = ordProductMapper.selectForUpdate(2);
System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
}
}
methodB的代碼為:
public void methodB(){
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(transactionStatus -> {
OrdProduct product = ordProductMapper.selectForUpdate(2);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() -1 );
int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
/*if (result == 1) {
throw new RuntimeException("test");
}*/
return result;
});
}
methodA在完成6s的減庫存操作后,調用methodB,methodB的傳播屬性是PROPAGATION_REQUIRED,此時methodA已經起了事務,methodB會使用現(xiàn)存的事務,在methodA拋出異常后,兩個方法的減庫存操作都會回滾。
-
當前沒有事務,則新建一個事務
methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,methodB方法去除注釋拋出異常,methodB會新建一個事務,執(zhí)行后methodB會回滾,methodA不會,即mac庫存不變,6s庫存減1;
PROPAGATION_SUPPORTS
-
支持當前事務
和PROPAGATION_REQUIRED行為相同; -
沒有當前事務,則以非事務的方式執(zhí)行
methodB的傳播屬性設置為PROPAGATION_SUPPORTS
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,methodB方法去除注釋拋出異常,methodB不會新建事務,執(zhí)行后兩個方法都不會回滾,兩個產品的庫存均減1.
PROPAGATION_MANDATORY
-
支持當前事務
和PROPAGATION_REQUIRED行為相同; -
當前沒有事務,則拋出異常
methodB的傳播屬性設置為PROPAGATION_MANDATORY
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY);
methodA代碼邏輯不用transactionTemplate.execute()方式執(zhí)行,執(zhí)行時則會拋出如下異常
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
PROPAGATION_REQUIRES_NEW
在當前沒有事務的情況下,行為和PROPAGATION_REQUIRED一致,如果當前已有事務,則將當前事務掛起,開啟一個新的事務。
從上圖可以看出傳播屬性是PROPAGATION_REQUIRES_NEW時候的事務執(zhí)行過程。PROPAGATION_REQUIRES_NEW始終對每個受影響的事務范圍采用獨立的物理事務,而不受外圍事務的影響。
在這樣的安排中,底層資源事務是不同的,因此可以獨立地提交或回滾,外部事務不受內部事務的回滾狀態(tài)的影響,并且內部事務的鎖在完成后立即釋放。這樣一個獨立的內部事務也可以聲明它自己的隔離級別,超時和只讀設置,而不是繼承外部事務的特性。
-
獨立的物理事務
要理解這一點可以用下面的例子來說明:
public void methodA() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
try {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(status -> {
OrdProduct product = ordProductMapper.selectForUpdate(1);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() - 1);
int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
serviceB.methodB();
if (result == 1) {
throw new RuntimeException("test");
}
return result;
});
} finally {
OrdProduct product1 = ordProductMapper.selectForUpdate(1);
OrdProduct product2 = ordProductMapper.selectForUpdate(2);
System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
}
}
public void methodB(){
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(transactionStatus -> {
OrdProduct product = ordProductMapper.selectForUpdate(2);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() -1 );
int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
/*if (result == 1) {
throw new RuntimeException("test");
}*/
return result;
});
}
serviceA拋出異常,serviceB是內部事務,在serviceB的傳播行為是PROPAGATION_REQUIRES_NEW時,那么serviceB將開啟一個獨立的物理事務,并不受外部事務的影響,因此不會回滾,結果可以看到,serviceA中IPhone6S庫存不變,serviceB中MAC Pro庫存減1.
before update IPhone6S, inventory is: 9985
before update MAC Pro, inventory is: 9989
after update IPhone6S, inventory is: 9985
after update MAC Pro, inventory is: 9988
同樣,內部事務的回滾狀態(tài)也不會影響外部事務,可以用以下的例子來說明:
public void methodA() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
try {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.execute(status -> {
OrdProduct product = ordProductMapper.selectForUpdate(1);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() - 1);
int result = ordProductMapper.updateInventoryByProductId(1, product.getInventory());
serviceB.methodB();
/* if (result == 1) {
throw new RuntimeException("test");
}*/
return result;
});
} finally {
OrdProduct product1 = ordProductMapper.selectForUpdate(1);
OrdProduct product2 = ordProductMapper.selectForUpdate(2);
System.out.println("after update " + product1.getProductName() + ", inventory is: " + product1.getInventory());
System.out.println("after update " + product2.getProductName() + ", inventory is: " + product2.getInventory());
}
}
public void methodB(){
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(transactionStatus -> {
OrdProduct product = ordProductMapper.selectForUpdate(2);
System.out.println("before update " + product.getProductName() + ", inventory is: " + product.getInventory());
product.setInventory(product.getInventory() -1 );
int result = ordProductMapper.updateInventoryByProductId(2, product.getInventory());
/*if (result == 1) {
throw new RuntimeException("test");
}*/
transactionStatus.setRollbackOnly();
return result;
});
}
methodA不拋出異常,methodB設置rollbackOnly,結果是methodB回滾,methodA不受影響,不回滾。
before update IPhone6S, inventory is: 9985
before update MAC Pro, inventory is: 9988
after update IPhone6S, inventory is: 9984
after update MAC Pro, inventory is: 9988
而在methodB的傳播屬性是PROPAGATION_REQUIRED是,methodA會拋出UnexpectedRollbackException,并進行回滾。
PROPAGATION_NOT_SUPPORTED & PROPAGATION_NEVER
這兩者都比較好理解,此處不做過多贅述。
PROPAGATION_NESTED
PROPAGATION_NESTED 開始一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務. 嵌套事務開始執(zhí)行時, 它將取得一個 savepoint. 如果這個嵌套事務失敗, 我們將回滾到此 savepoint. 嵌套事務是外部事務的一部分, 只有外部事務結束后它才會被提交. 如果外部事務 commit, 嵌套事務也會被 commit, 這個規(guī)則同樣適用于 roll back.
關于PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW的區(qū)別,spring的創(chuàng)始人之一Juergen Hoeller有以下解釋:
PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed.
Such independent inner transactions are for example used for id generation through manual sequences, where the access to the sequence table should happen in its own transactions, to keep the lock there as short as possible. The goal there is to avoid tying the sequence locks to the (potentially much longer running) outer transaction, with the sequence lock not getting released before completion of the outer transaction.
PROPAGATION_NESTED on the other hand starts a "nested" transaction, which is a true subtransaction of the existing one. What will happen is that a savepoint will be taken at the start of the nested transaction. if the nested transaction fails, we will roll back to that savepoint. The nested transaction is part of of the outer transaction, so it will only be committed at the end of of the outer transaction.
Nested transactions essentially allow to try some execution subpaths as subtransactions: rolling back to the state at the beginning of the failed subpath, continuing with another subpath or with the main execution path there - all within one isolated transaction, and not losing any previous work done within the outer transaction.
For example, consider parsing a very large input file consisting of account transfer blocks: The entire file should essentially be parsed within one transaction, with one single commit at the end. But if a block fails, its transfers need to be rolled back, writing a failure marker somewhere. You could either start over the entire transaction every time a block fails, remembering which blocks to skip - or you mark each block as a nested transaction, only rolling back that specific set of operations, keeping the previous work of the outer transaction. The latter is of course much more efficient, in particular when a block at the end of the file fails.
需要注意的是,PROPAGATION_NESTED使用JDBC保存點,因此,它只適用于JDBC資源事務。
筆者在實際中也未使用過PROPAGATION_NESTED屬性,因此,如果有讀者使用碰到問題,歡迎來信一起探討wgy@live.cn。