Spring雙層事務(wù),我拋出的異常去哪了?
系統(tǒng) A 調(diào)用系統(tǒng) B 執(zhí)行數(shù)據(jù)同步,系統(tǒng) B 返回了錯誤提示,系統(tǒng) A 需要將前邊保存的回滾掉,同時把錯誤信息向上拋。
大致代碼如下
- @Service("noteService")
- public class NoteServiceImpl implements NoteService {
- @Resource
- private SearchService searchService;
- @Transactional(rollbackFor = Throwable.class)
- @Override
- public CommonResponse<NoteEntity> save(NoteEntity note) {
- // 一系列 DB 操作
- try {
- searchService.sync(note);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return CommonResponse.success(entity);
- }
- }
- @Service("searchService")
- public class SearchServiceImpl implements SearchService {
- @Transactional(rollbackFor = Throwable.class)
- @Override
- public void sync(NoteEntity note) {
- // 一系列 DB 操作
- throw new RuntimeException("同步異常! [XXX]");
- }
- }
- @SpringBootTest
- public class NoteTests {
- @Resource
- private NoteService noteService;
- @Test
- public void saveNote() {
- NoteEntity entity = new NoteEntity();
- entity.setTitle("念奴嬌赤壁懷古");
- entity.setContent("大江東去,浪淘盡,千古風(fēng)流人物。故壘西邊,人道是:三國周郎赤壁。。。");
- entity.setTags("蘇軾,宋代");
- entity.setCategory("蘇軾詩詞");
- try {
- noteService.save(entity);
- } catch (Exception e) {
- e.printStackTrace();
- // FIXME 我想在這里拿到的是 同步異常! [XXX]
- // FIXME 但是這里拿到的是 Transaction silently rolled back because it has been marked as rollback-only
- System.out.println(">>>>>>>>>> " + e.getMessage());
- }
- }
- }
事出有因
代碼歷史久遠,為何這樣寫已無從追溯。
納悶了一會兒,看到雙層事務(wù),就想起了 Spring事務(wù)傳播機制,前邊理解得比較膚淺。Spring 系列面試題和答案我全部整理好了,請關(guān)注公眾號Java技術(shù)棧,回復(fù):面試。
沒有特殊的配置,自然是走默認的事務(wù)傳播機制了,也就是 Propagation.REQUIRED。
國際慣例,列出事務(wù)傳播機制:
- 1、PROPAGATION_REQUIRED
- 當(dāng)前沒事務(wù),則創(chuàng)建事務(wù);存在事務(wù),就加入該事務(wù),這是最常用的設(shè)置。
- 2、PROPAGATION_SUPPORTS
- 當(dāng)前存在事務(wù),就加入事務(wù),當(dāng)前不存在事務(wù),就以非事務(wù)方式執(zhí)行。
- 3、PROPAGATION_MANDATORY
- 當(dāng)前存在事務(wù),就加入事務(wù);當(dāng)前不存在事務(wù),就拋出異常。
- 4、PROPAGATION_REQUIRES_NEW
- 無條件創(chuàng)建新事務(wù)。
- 5、PROPAGATION_NOT_SUPPORTED
- 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起。
- 6、PROPAGATION_NEVER
- 以非事務(wù)方式運行,如果存在事務(wù),就拋出異常。
- 7、PROPAGATION_NESTED
- 開始執(zhí)行事務(wù)前,先保存一個savepoint,當(dāng)發(fā)生異常時,就回滾到savepoint;沒有異常時,跟著外部事務(wù)一起提交或回滾。
具體原因
1、看了上邊的事務(wù)傳播機制,繼續(xù)細化問題,內(nèi)外層共用一個事務(wù),內(nèi)層拋出異常,會導(dǎo)致整個事務(wù)失敗。
2、繼續(xù)分析,外層邏輯進行了 try catch,就導(dǎo)致內(nèi)層的異常無法繼續(xù)向上拋出,外層事務(wù)會繼續(xù)提交。
3、事務(wù)提交時,進行事務(wù)狀態(tài)的判斷,就發(fā)現(xiàn)這個事務(wù)是失敗的,需要回滾,所以拋出了 Transaction silently rolled back because it has been marked as rollback-only 的異常。
另外,大家想學(xué) Spring Boot 的看下這個倉庫,太全了。
https://github.com/javastacks/spring-boot-best-practice
怎么解決?
銀彈自然是沒有的,根據(jù)業(yè)務(wù)場景選擇合適的方案。
1、當(dāng)前這種場景,直接把外層邏輯中的 try catch 去掉即可。異常直接向上拋,事務(wù)就不會繼續(xù)提交,調(diào)用方拿到的就是一手的異常;
2、如果內(nèi)層不是核心邏輯,記錄個日志啥的,可以把內(nèi)層事務(wù)配置為 @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW), 無論如何,都創(chuàng)建新的事務(wù),外層事務(wù)不受內(nèi)層事務(wù)影響。但是有個問題,外層事務(wù)失敗了,內(nèi)層事務(wù)還是把記錄入庫了,有可能產(chǎn)生臟數(shù)據(jù);
3、如果外層事務(wù)失敗了,內(nèi)層事務(wù)也不能提交,那就可以使用 @Transactional(rollbackFor = Throwable.class, propagation = Propagation.NESTED)。注意:hibernate/jpa 不支持嵌套事務(wù) NESTED,可用 JdbcTemplate 代替。