Spring?@Transactional事務(wù)失效的原因分析
前言
一個(gè)程序中不可能沒有事務(wù),而 Spring 中,事務(wù)的實(shí)現(xiàn)方式分為兩種:編程式事務(wù)和聲明式事務(wù),又因?yàn)榫幊淌绞聞?wù)實(shí)現(xiàn)相對(duì)麻煩,而聲明式事務(wù)實(shí)現(xiàn)極其簡(jiǎn)單,所以在日常項(xiàng)目中,我們都會(huì)使用聲明式事務(wù) @Transactional 來(lái)實(shí)現(xiàn)事務(wù)。
@Transactional 使用極其簡(jiǎn)單,只需要在類上或方法上添加 @Transactional 關(guān)鍵字,就可以實(shí)現(xiàn)事務(wù)的自動(dòng)開啟、提交或回滾了,它的基礎(chǔ)用法如下:
@Transactional @RequestMapping("/add") public int add(UserInfo userInfo) { int result = userService.add(userInfo); return result; }
@Transactional 執(zhí)行流程
@Transactional 會(huì)在方法執(zhí)行前,會(huì)自動(dòng)開啟事務(wù);在方法成功執(zhí)行完,會(huì)自動(dòng)提交事務(wù);如果方法在執(zhí)行期間,出現(xiàn)了異常,那么它會(huì)自動(dòng)回滾事務(wù)。
然而,就是看起來(lái)極其簡(jiǎn)單的 @Transactional,卻隱藏著一些“坑”,這些坑就是我們今天要講的主題:導(dǎo)致 @Transactional 事務(wù)失效的常見場(chǎng)景有哪些?
在開始之前,我們先要明確一個(gè)定義,什么叫做“失效”?
本文中的“失效”指的是“失去(它的)功效”,也就是當(dāng) @Transactional 不符合我們預(yù)期的結(jié)果時(shí),我們就可以說(shuō) @Transactional 失效了。
那 @Transactional 失效的場(chǎng)景有哪些呢?接下來(lái)我們一一來(lái)看。
1.非 public 修飾的方法
當(dāng) @Transactional 修飾的方法為非 public 時(shí),事務(wù)就失效了,比如以下代碼當(dāng)遇到異常之后,不能自動(dòng)實(shí)現(xiàn)回滾:
@RequestMapping("/save") int save(UserInfo userInfo) { // 非空效驗(yàn) if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) return 0; // 執(zhí)行添加操作 int result = userService.save(userInfo); System.out.println("add 受影響的行數(shù):" + result); int num = 10 / 0; // 此處設(shè)置一個(gè)異常 return result; }
以上程序的運(yùn)行結(jié)果如下:
當(dāng)程序出現(xiàn)運(yùn)行時(shí)異常時(shí),我們預(yù)期的結(jié)果是事務(wù)應(yīng)該實(shí)現(xiàn)自動(dòng)回滾,也就是添加用戶失敗,然而當(dāng)我們查詢數(shù)據(jù)庫(kù)時(shí),卻發(fā)現(xiàn)事務(wù)并未執(zhí)行回滾操作,數(shù)據(jù)庫(kù)的數(shù)據(jù)如下圖所示:
2.timeout 超時(shí)
當(dāng)在 @Transactional 上,設(shè)置了一個(gè)較小的超時(shí)時(shí)間時(shí),如果方法本身的執(zhí)行時(shí)間超過了設(shè)置的 timeout 超時(shí)時(shí)間,那么就會(huì)導(dǎo)致本來(lái)應(yīng)該正常插入數(shù)據(jù)的方法執(zhí)行失敗,示例代碼如下:
@Transactional(timeout = 3) // 超時(shí)時(shí)間為 3s @RequestMapping("/save") int save(UserInfo userInfo) throws InterruptedException { // 非空效驗(yàn) if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.save(userInfo); return result; }
UserService 的 save 方法實(shí)現(xiàn)如下:
public int save(UserInfo userInfo) throws InterruptedException { // 休眠 5s TimeUnit.SECONDS.sleep(5); int result = userMapper.add(userInfo); return result; }
以上程序的運(yùn)行結(jié)果如下:
數(shù)據(jù)庫(kù)沒有正確的插入數(shù)據(jù),如下圖所示:
3.代碼中有 try/catch
在前面 @Transactional 的執(zhí)行流程中,我們提到:當(dāng)方法中出現(xiàn)了異常之后,事務(wù)會(huì)自動(dòng)回滾。然而,如果在程序中加了 try/catch 之后,@Transactional 就不會(huì)自動(dòng)回滾事務(wù)了,示例代碼如下:
@Transactional @RequestMapping("/save") public int save(UserInfo userInfo) throws InterruptedException { // 非空效驗(yàn) if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.save(userInfo); try { int num = 10 / 0; // 此處設(shè)置一個(gè)異常 } catch (Exception e) { } return result; }
以上程序的運(yùn)行結(jié)果如下:
此時(shí),查詢數(shù)據(jù)庫(kù)我們發(fā)現(xiàn),程序并沒有執(zhí)行回滾操作,數(shù)據(jù)庫(kù)中被成功的添加了一條數(shù)據(jù),如下圖所示:
4.調(diào)用類內(nèi)部 @Transactional 方法
當(dāng)調(diào)用類內(nèi)部的 @Transactional 修飾的方法時(shí),事務(wù)是不會(huì)生效的,示例代碼如下:
@RequestMapping("/save") public int saveMappping(UserInfo userInfo) { return save(userInfo); } @Transactional public int save(UserInfo userInfo) { // 非空效驗(yàn) if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) return 0; int result = userService.save(userInfo); int num = 10 / 0; // 此處設(shè)置一個(gè)異常 return result; }
以上代碼我們?cè)谔砑臃椒?save 中添加了 @Transactional 聲明式事務(wù),并且添加了異常代碼,我們預(yù)期的結(jié)果是程序出現(xiàn)異常,事務(wù)進(jìn)行自動(dòng)回滾,以上程序的執(zhí)行結(jié)果如下:
然而,當(dāng)我們查詢數(shù)據(jù)庫(kù)時(shí)發(fā)現(xiàn),程序執(zhí)行并不符合我們的預(yù)期,添加的數(shù)據(jù)并沒有進(jìn)行自動(dòng)回滾操作,如下圖所示:
5.數(shù)據(jù)庫(kù)不支持事務(wù)
我們程序中的 @Transactional 只是給調(diào)用的數(shù)據(jù)庫(kù)發(fā)送了:開始事務(wù)、提交事務(wù)、回滾事務(wù)的指令,但是如果數(shù)據(jù)庫(kù)本身不支持事務(wù),比如 MySQL 中設(shè)置了使用 MyISAM 引擎,那么它本身是不支持事務(wù)的,這種情況下,即使在程序中添加了 @Transactional 注解,那么依然不會(huì)有事務(wù)的行為,這就是巧婦也難為無(wú)米之炊吧。
總結(jié)
當(dāng)聲明式事務(wù) @Transactional 遇到以下場(chǎng)景時(shí),事務(wù)會(huì)失效:
- 非 public 修飾的方法;
- timeout 設(shè)置過小;
- 代碼中使用 try/catch 處理異常;
- 調(diào)用類內(nèi)部 @Transactional 方法;
- 數(shù)據(jù)庫(kù)不支持事務(wù)。
到此這篇關(guān)于Spring @Transactional事務(wù)失效的原因分析的文章就介紹到這了,更多相關(guān)Spring @Transactional事務(wù)失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring中的注解@@Transactional失效的場(chǎng)景代碼演示
- Spring中的@Transactional事務(wù)失效場(chǎng)景解讀
- spring事務(wù)@Transactional失效原因及解決辦法小結(jié)
- Spring注解@Transactional失效的場(chǎng)景分析
- Spring事務(wù)控制策略及@Transactional失效問題解決避坑
- 解讀Spring接口方法加@Transactional失效的原因
- spring中12種@Transactional的失效場(chǎng)景(小結(jié))
- Spring事務(wù)注解@Transactional失效的八種場(chǎng)景分析
- Spring @Transactional注解失效解決方案
- spring中@Transactional?注解失效的原因及解決辦法
相關(guān)文章
java使用UDP實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)通信
這篇文章主要為大家詳細(xì)介紹了java使用UDP實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)通信,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過程解析
這篇文章主要介紹了spring boot基于DRUID實(shí)現(xiàn)數(shù)據(jù)源監(jiān)控過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12jpa?EntityManager?復(fù)雜查詢實(shí)例
這篇文章主要介紹了jpa?EntityManager?復(fù)雜查詢實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Mybatis MapperScannerConfigurer自動(dòng)掃描Mapper接口生成代理注入到Spring的方法
這篇文章主要給大家介紹了關(guān)于Mybatis MapperScannerConfigurer自動(dòng)掃描將Mapper接口生成代理注入到Spring的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2019-03-03Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor
本文介紹了Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor,手動(dòng)方式使用ThreadPoolExecutor創(chuàng)建線程池和使用Executors執(zhí)行器自動(dòng)創(chuàng)建線程池,下文更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-05-05java中timer的schedule和scheduleAtFixedRate方法區(qū)別詳解
這篇文章主要為大家詳細(xì)介紹了java中timer的schedule和scheduleAtFixedRate方法區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12