Spring 雙層事務,我丟擲的異常去哪了?
技術標籤:程式語言bugjavazookeeperspring
作者:AMOS0626
來源:my.oschina.net/AmosWang/blog/4773386
系統 A 呼叫系統 B 執行資料同步,系統 B 返回了錯誤提示,系統 A 需要將前邊儲存的回滾掉,同時把錯誤資訊向上拋。
大致程式碼如下
@Service("noteService") publicclassNoteServiceImplimplementsNoteService{ @Resource privateSearchServicesearchService; @Transactional(rollbackFor=Throwable.class) @Override publicCommonResponse<NoteEntity>save(NoteEntitynote){ //一系列DB操作 try{ searchService.sync(note); }catch(Exceptione){ e.printStackTrace(); } returnCommonResponse.success(entity); } } @Service("searchService") publicclassSearchServiceImplimplementsSearchService{ @Transactional(rollbackFor=Throwable.class) @Override publicvoidsync(NoteEntitynote){ //一系列DB操作 thrownewRuntimeException("同步異常![XXX]"); } } @SpringBootTest publicclassNoteTests{ @Resource privateNoteServicenoteService; @Test publicvoidsaveNote(){ NoteEntityentity=newNoteEntity(); entity.setTitle("念奴嬌赤壁懷古"); entity.setContent("大江東去,浪淘盡,千古風流人物。故壘西邊,人道是:三國周郎赤壁。。。"); entity.setTags("蘇軾,宋代"); entity.setCategory("蘇軾詩詞"); try{ noteService.save(entity); }catch(Exceptione){ e.printStackTrace(); //FIXME我想在這裡拿到的是同步異常![XXX] //FIXME但是這裡拿到的是Transactionsilentlyrolledbackbecauseithasbeenmarkedasrollback-only System.out.println(">>>>>>>>>>"+e.getMessage()); } } }
事出有因
程式碼歷史久遠,為何這樣寫已無從追溯。
納悶了一會兒,看到雙層事務,就想起了 Spring事務傳播機制,前邊理解得比較膚淺。Spring 系列面試題和答案我全部整理好了,請關注公眾號網際網路架構師,回覆:2T。
沒有特殊的配置,自然是走預設的事務傳播機制了,也就是 Propagation.REQUIRED。
國際慣例,列出事務傳播機制:
1、PROPAGATION_REQUIRED 當前沒事務,則建立事務;存在事務,就加入該事務,這是最常用的設定。 2、PROPAGATION_SUPPORTS 當前存在事務,就加入事務,當前不存在事務,就以非事務方式執行。 3、PROPAGATION_MANDATORY 當前存在事務,就加入事務;當前不存在事務,就丟擲異常。 4、PROPAGATION_REQUIRES_NEW 無條件建立新事務。 5、PROPAGATION_NOT_SUPPORTED 以非事務方式執行,如果當前存在事務,就將當前事務掛起。 6、PROPAGATION_NEVER 以非事務方式執行,如果存在事務,就丟擲異常。 7、PROPAGATION_NESTED 開始執行事務前,先儲存一個savepoint,當發生異常時,就回滾到savepoint;沒有異常時,跟著外部事務一起提交或回滾。
具體原因
1、看了上邊的事務傳播機制,繼續細化問題,內外層共用一個事務,內層丟擲異常,會導致整個事務失敗。
2、繼續分析,外層邏輯進行了try catch,就導致內層的異常無法繼續向上丟擲,外層事務會繼續提交。
3、事務提交時,進行事務狀態的判斷,就發現這個事務是失敗的,需要回滾,所以丟擲了Transaction silently rolled back because it has been marked as rollback-only
的異常。
另外,大家想學 Spring Boot 的看下這個倉庫,太全了。
https://github.com/javastacks/spring-boot-best-practice
怎麼解決?
銀彈自然是沒有的,根據業務場景選擇合適的方案。
1、當前這種場景,直接把外層邏輯中的 try catch 去掉即可。異常直接向上拋,事務就不會繼續提交,呼叫方拿到的就是一手的異常;
2、如果內層不是核心邏輯,記錄個日誌啥的,可以把內層事務配置為@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW)
, 無論如何,都建立新的事務,外層事務不受內層事務影響。但是有個問題,外層事務失敗了,內層事務還是把記錄入庫了,有可能產生髒資料;
3、如果外層事務失敗了,內層事務也不能提交,那就可以使用@Transactional(rollbackFor = Throwable.class, propagation = Propagation.NESTED)
。注意:hibernate/jpa 不支援巢狀事務 NESTED,可用 JdbcTemplate 代替。
PS:如果覺得我的分享不錯,歡迎大家隨手點贊、在看。
大家一起在評論區聊聊唄~
猜你喜歡
1、GitHub 標星 3.2w!史上最全技術人員面試手冊!FackBoo發起和總結
5、37歲程式設計師被裁,120天沒找到工作,無奈去小公司,結果懵了...