Java中Spring對(duì)事務(wù)的支持詳解
事務(wù)屬性
- 事務(wù)傳播行為;--Propagation(本文重點(diǎn)解讀事務(wù)傳播行為)
- 事務(wù)隔離級(jí)別;--Isolation
- 事務(wù)超時(shí);--timeout
- 只讀事務(wù);--readOnly
- 設(shè)置出現(xiàn)哪些異?;貪L事務(wù);--rollbackFor
- 設(shè)置出現(xiàn)哪些異常不回滾事務(wù);--noRollbackFor
事務(wù)的傳播行為
一、什么是事務(wù)的傳播行為
在service類中有a()方法和b()方法,a()方法上有事務(wù),b()方法上也有事務(wù),當(dāng)a()方法執(zhí)行過程中調(diào)用了b()方法,事務(wù)是如何傳遞的?合并到一個(gè)事務(wù)里?還是開啟一個(gè)新的事務(wù)?這就是事務(wù)傳播行為。
注意:以下事務(wù)傳播屬性都是打在內(nèi)部方法b()方法上的事務(wù)注解
二、7種事務(wù)傳播行為
Spring 可以通過 @Transactional 注解的 propagation 屬性來設(shè)置不同的傳播行為策略。
Spring 為此提供了一個(gè)枚舉類 Propagation,源碼如下:
package org.springframework.transaction.annotation; import org.springframework.transaction.TransactionDefinition; public enum Propagation { /** * 需要事務(wù),它是默認(rèn)傳播行為,如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù), * 否則新建一個(gè)事務(wù)運(yùn)行內(nèi)部方法 */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * 支持事務(wù),如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù), * 如果不存在,則繼續(xù)采用無事務(wù)的方式運(yùn)行內(nèi)部方法 */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * 必須使用事務(wù),如果當(dāng)前沒有事務(wù),則會(huì)拋出異常, * 如果存在當(dāng)前事務(wù),則沿用當(dāng)前事務(wù) */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * 無論當(dāng)前事務(wù)是否存在,都會(huì)創(chuàng)建新事務(wù)運(yùn)行方法, * 這樣新事務(wù)就可以擁有新的鎖和隔離級(jí)別等特性,與當(dāng)前事務(wù)相互獨(dú)立 */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * 不支持事務(wù),當(dāng)前存在事務(wù)時(shí),將掛起事務(wù),運(yùn)行方法 */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * 不支持事務(wù),如果當(dāng)前方法存在事務(wù),則拋出異常,否則繼續(xù)使用無事務(wù)機(jī)制運(yùn)行 */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * 在當(dāng)前方法調(diào)用內(nèi)部方法時(shí),如果內(nèi)部方法發(fā)生異常, * 只回滾內(nèi)部方法執(zhí)行過的 SQL ,而不回滾當(dāng)前方法的事務(wù) */ NESTED(TransactionDefinition.PROPAGATION_NESTED); ...... }
接下來我們通過對(duì)其中三種最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略進(jìn)行對(duì)比來更深入的理解。以下測(cè)試均在外部方法開啟事務(wù)的情況下進(jìn)行,因?yàn)樵谕獠繘]有事務(wù)的情況下,三者都會(huì)新建事務(wù),效果一樣。
1. REQUIRED
當(dāng)內(nèi)部方法的事務(wù)傳播行為設(shè)置為 REQUIRED 時(shí),內(nèi)部方法會(huì)加入外部方法的事務(wù)。我們?cè)?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ā)生異常,事務(wù)回滾!"); } } }
結(jié)果分析如下表所示:
前面四種情況都比較好理解,很多人不能理解最后一種情況:我都 try-catch 了你還想怎樣?這里的關(guān)鍵點(diǎn)在于所有方法都處于同一個(gè)事務(wù)中,此時(shí)「小鏡」的插入方法發(fā)生異常,那么這個(gè)方法所在的事務(wù)就會(huì)被 Spring 設(shè)置為 rollback 狀態(tài)。因?yàn)楫惓1?catch 了,所以外部方法執(zhí)行完要進(jìn)行 commit 操作,這時(shí)卻發(fā)現(xiàn)當(dāng)前事務(wù)已經(jīng)處于 rollback 狀態(tài)了,雖然它不知道哪里出了問題,但也只能聽從指揮,回滾所有操作了。
PS:由于外部方法不開啟事務(wù)的情況,在每種傳播行為下結(jié)果都是類似的,所以后面不再給出示例。
2. REQUIRES_NEW
當(dāng)內(nèi)部方法的傳播行為設(shè)置為 REQUIRES_NEW 時(shí),內(nèi)部方法會(huì)先將外部方法的事務(wù)掛起,然后開啟一個(gè)新的事務(wù) 。在 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ā)生異常,事務(wù)回滾!"); } } }
結(jié)果分析如下表所示:
3. NESTED
當(dāng)內(nèi)部方法的傳播行為設(shè)置為 NESTED 時(shí),內(nèi)部方法會(huì)開啟一個(gè)新的嵌套事務(wù)(子事務(wù))。在 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ā)生異常,事務(wù)回滾!",e); } } }
結(jié)果分析如下表所示:
每個(gè) NESTED 事務(wù)執(zhí)行前會(huì)將當(dāng)前操作保存下來,叫做 savepoint (保存點(diǎn)),如果當(dāng)前 NESTED 事務(wù)執(zhí)行失敗,則回滾到之前的保存點(diǎn),保存點(diǎn)使得子事務(wù)的回滾不對(duì)主事務(wù)造成影響。NESTED 事務(wù)在外部事務(wù)提交以后自己才會(huì)提交。
4. 總結(jié)
REQUIRES_NEW 最為簡(jiǎn)單,不管當(dāng)前有無事務(wù),它都會(huì)開啟一個(gè)全新事務(wù),既不影響外部事務(wù),也不會(huì)影響其他內(nèi)部事務(wù),真正的井水不犯河水,堅(jiān)定而獨(dú)立。
REQUIRED 在沒有外部事務(wù)的情況下,會(huì)開啟一個(gè)獨(dú)立的新事務(wù),且不會(huì)對(duì)其他同級(jí)事務(wù)造成影響;而當(dāng)存在外部事務(wù)的情況下,則會(huì)與外部事務(wù)同生共死。
NESTED 在沒有外部事務(wù)的情況下與 REQUIRED 效果相同;而當(dāng)存在外部事務(wù)的情況下,當(dāng)外部事務(wù)回滾時(shí),它會(huì)創(chuàng)建一個(gè)嵌套事務(wù)(子事務(wù))。外部事務(wù)回滾時(shí),子事務(wù)會(huì)跟著回滾;但子事務(wù)的回滾不會(huì)對(duì)外部事務(wù)和其他同級(jí)事務(wù)造成影響。
三、事務(wù)的傳播行為(理解記憶)
到此這篇關(guān)于Java中Spring對(duì)事務(wù)的支持詳解的文章就介紹到這了,更多相關(guān)Spring對(duì)事務(wù)的支持內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
項(xiàng)目開發(fā)中經(jīng)常會(huì)遇到多數(shù)據(jù)源同時(shí)使用的場(chǎng)景,比如冷熱數(shù)據(jù)的查詢等情況,所以接下來本文就來介紹一下如何使用實(shí)現(xiàn)自定義注解的形式來實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換吧2023-12-12

Spring Security組件一鍵接入驗(yàn)證碼登錄和小程序登錄的詳細(xì)過程

Springboot整合hutool驗(yàn)證碼的實(shí)例代碼

Springboot如何設(shè)置靜態(tài)資源緩存一年