Java中Spring對事務的支持詳解
事務屬性
- 事務傳播行為;--Propagation(本文重點解讀事務傳播行為)
- 事務隔離級別;--Isolation
- 事務超時;--timeout
- 只讀事務;--readOnly
- 設置出現(xiàn)哪些異?;貪L事務;--rollbackFor
- 設置出現(xiàn)哪些異常不回滾事務;--noRollbackFor
事務的傳播行為
一、什么是事務的傳播行為
在service類中有a()方法和b()方法,a()方法上有事務,b()方法上也有事務,當a()方法執(zhí)行過程中調(diào)用了b()方法,事務是如何傳遞的?合并到一個事務里?還是開啟一個新的事務?這就是事務傳播行為。
注意:以下事務傳播屬性都是打在內(nèi)部方法b()方法上的事務注解
二、7種事務傳播行為

Spring 可以通過 @Transactional 注解的 propagation 屬性來設置不同的傳播行為策略。
Spring 為此提供了一個枚舉類 Propagation,源碼如下:
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
/**
* 需要事務,它是默認傳播行為,如果當前存在事務,就沿用當前事務,
* 否則新建一個事務運行內(nèi)部方法
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 支持事務,如果當前存在事務,就沿用當前事務,
* 如果不存在,則繼續(xù)采用無事務的方式運行內(nèi)部方法
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 必須使用事務,如果當前沒有事務,則會拋出異常,
* 如果存在當前事務,則沿用當前事務
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 無論當前事務是否存在,都會創(chuàng)建新事務運行方法,
* 這樣新事務就可以擁有新的鎖和隔離級別等特性,與當前事務相互獨立
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 不支持事務,當前存在事務時,將掛起事務,運行方法
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* 不支持事務,如果當前方法存在事務,則拋出異常,否則繼續(xù)使用無事務機制運行
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 在當前方法調(diào)用內(nèi)部方法時,如果內(nèi)部方法發(fā)生異常,
* 只回滾內(nèi)部方法執(zhí)行過的 SQL ,而不回滾當前方法的事務
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
......
}接下來我們通過對其中三種最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略進行對比來更深入的理解。以下測試均在外部方法開啟事務的情況下進行,因為在外部沒有事務的情況下,三者都會新建事務,效果一樣。
1. REQUIRED
當內(nèi)部方法的事務傳播行為設置為 REQUIRED 時,內(nèi)部方法會加入外部方法的事務。我們在 UserServiceImpl 中添加如下方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addWithRequired(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addWithRequiredAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}創(chuàng)建 TransactionServiceImpl 類,并添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
@Autowired
private UserService userService;
@Override
public void noTransaction_required_required_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequired(xiaoJing);
throw new RuntimeException();
}
@Override
public void noTransaction_required_requiredException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_required_required_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequired(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_required_requiredException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_required_requiredException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
try {
userService.addWithRequiredAndException(xiaoJing);
} catch (Exception e) {
log.error("發(fā)生異常,事務回滾!");
}
}
}結果分析如下表所示:

前面四種情況都比較好理解,很多人不能理解最后一種情況:我都 try-catch 了你還想怎樣?這里的關鍵點在于所有方法都處于同一個事務中,此時「小鏡」的插入方法發(fā)生異常,那么這個方法所在的事務就會被 Spring 設置為 rollback 狀態(tài)。因為異常被 catch 了,所以外部方法執(zhí)行完要進行 commit 操作,這時卻發(fā)現(xiàn)當前事務已經(jīng)處于 rollback 狀態(tài)了,雖然它不知道哪里出了問題,但也只能聽從指揮,回滾所有操作了。
PS:由于外部方法不開啟事務的情況,在每種傳播行為下結果都是類似的,所以后面不再給出示例。
2. REQUIRES_NEW
當內(nèi)部方法的傳播行為設置為 REQUIRES_NEW 時,內(nèi)部方法會先將外部方法的事務掛起,然后開啟一個新的事務 。在 UserServiceImpl 中添加如下方法(UserServiceImpl 類中上面的方法還在哦):
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
...
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addWithRequiredNew(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addWithRequiredNewAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}在 TransactionServiceImpl 中添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
...
@Override
@Transactional
public void transaction_required_requiredNew_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_required_requiredNew_requiredNewException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
User shuiJing = new User().setName("水鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
userService.addWithRequiredNewAndException(shuiJing);
}
@Override
@Transactional
public void transaction_required_requiredNewException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
User shuiJing = new User().setName("水鏡");
userService.addWithRequired(xiaoShui);
userService.addWithRequiredNew(xiaoJing);
try {
userService.addWithRequiredNewAndException(shuiJing);
} catch (Exception e) {
log.error("發(fā)生異常,事務回滾!");
}
}
}結果分析如下表所示:

3. NESTED
當內(nèi)部方法的傳播行為設置為 NESTED 時,內(nèi)部方法會開啟一個新的嵌套事務(子事務)。在 UserServiceImpl 中添加如下方法:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
...
@Override
@Transactional(propagation = Propagation.NESTED)
public void addWithNested(User user) {
mapper.insert(user);
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void addWithNestedAndException(User user) {
mapper.insert(user);
throw new RuntimeException();
}
}在 TransactionServiceImpl 中添加如下方法:
@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
...
@Override
@Transactional
public void transaction_nested_nested_externalException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithNested(xiaoShui);
userService.addWithNested(xiaoJing);
throw new RuntimeException();
}
@Override
@Transactional
public void transaction_nested_nestedException() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
userService.addWithNested(xiaoShui);
userService.addWithNestedAndException(xiaoJing);
}
@Override
@Transactional
public void transaction_nested_nestedException_try() {
User xiaoShui = new User().setName("小水");
User xiaoJing = new User().setName("小鏡");
User shuiJing = new User().setName("水鏡");
userService.addWithRequired(xiaoShui);
userService.addWithNested(xiaoJing);
try {
userService.addWithNestedAndException(shuiJing);
} catch (Exception e) {
log.error("發(fā)生異常,事務回滾!",e);
}
}
}結果分析如下表所示:

每個 NESTED 事務執(zhí)行前會將當前操作保存下來,叫做 savepoint (保存點),如果當前 NESTED 事務執(zhí)行失敗,則回滾到之前的保存點,保存點使得子事務的回滾不對主事務造成影響。NESTED 事務在外部事務提交以后自己才會提交。
4. 總結
REQUIRES_NEW 最為簡單,不管當前有無事務,它都會開啟一個全新事務,既不影響外部事務,也不會影響其他內(nèi)部事務,真正的井水不犯河水,堅定而獨立。
REQUIRED 在沒有外部事務的情況下,會開啟一個獨立的新事務,且不會對其他同級事務造成影響;而當存在外部事務的情況下,則會與外部事務同生共死。
NESTED 在沒有外部事務的情況下與 REQUIRED 效果相同;而當存在外部事務的情況下,當外部事務回滾時,它會創(chuàng)建一個嵌套事務(子事務)。外部事務回滾時,子事務會跟著回滾;但子事務的回滾不會對外部事務和其他同級事務造成影響。
三、事務的傳播行為(理解記憶)


到此這篇關于Java中Spring對事務的支持詳解的文章就介紹到這了,更多相關Spring對事務的支持內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot實現(xiàn)動態(tài)數(shù)據(jù)源切換的方法總結
項目開發(fā)中經(jīng)常會遇到多數(shù)據(jù)源同時使用的場景,比如冷熱數(shù)據(jù)的查詢等情況,所以接下來本文就來介紹一下如何使用實現(xiàn)自定義注解的形式來實現(xiàn)動態(tài)數(shù)據(jù)源切換吧2023-12-12
Spring Security組件一鍵接入驗證碼登錄和小程序登錄的詳細過程

