Spring双层事务,我抛出的异常去哪了?

开发 后端
系统 A 调用系统 B 执行数据同步,系统 B 返回了错误提示,系统 A 需要将前边保存的回滚掉,同时把错误信息向上抛。

[[375634]]

系统 A 调用系统 B 执行数据同步,系统 B 返回了错误提示,系统 A 需要将前边保存的回滚掉,同时把错误信息向上抛。

大致代码如下 

  1. @Service("noteService")  
  2. public class NoteServiceImpl implements NoteService {  
  3.     @Resource  
  4.     private SearchService searchService;  
  5.     @Transactional(rollbackFor = Throwable.class)  
  6.     @Override  
  7.     public CommonResponse<NoteEntity> save(NoteEntity note) {  
  8.         // 一系列 DB 操作  
  9.         try {  
  10.             searchService.sync(note);  
  11.         } catch (Exception e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         return CommonResponse.success(entity);  
  15.     }  
  16.  
  17. @Service("searchService")  
  18. public class SearchServiceImpl implements SearchService {  
  19.     @Transactional(rollbackFor = Throwable.class)  
  20.     @Override  
  21.     public void sync(NoteEntity note) {  
  22.         // 一系列 DB 操作  
  23.         throw new RuntimeException("同步异常! [XXX]"); 
  24.     }  
  25.  
  26. @SpringBootTest  
  27. public class NoteTests {  
  28.     @Resource  
  29.     private NoteService noteService;  
  30.     @Test  
  31.     public void saveNote() {  
  32.         NoteEntity entity = new NoteEntity();  
  33.         entity.setTitle("念奴娇赤壁怀古");  
  34.         entity.setContent("大江东去,浪淘尽,千古风流人物。故垒西边,人道是:三国周郎赤壁。。。");  
  35.         entity.setTags("苏轼,宋代");  
  36.         entity.setCategory("苏轼诗词");  
  37.         try {  
  38.             noteService.save(entity);  
  39.         } catch (Exception e) {  
  40.             e.printStackTrace(); 
  41.              // FIXME 我想在这里拿到的是 同步异常! [XXX]  
  42.             // FIXME 但是这里拿到的是 Transaction silently rolled back because it has been marked as rollback-only  
  43.             System.out.println(">>>>>>>>>> " + e.getMessage());  
  44.         }  
  45.     }  

事出有因

代码历史久远,为何这样写已无从追溯。

纳闷了一会儿,看到双层事务,就想起了 Spring事务传播机制,前边理解得比较肤浅。Spring 系列面试题和答案我全部整理好了,请关注公众号Java技术栈,回复:面试。

没有特殊的配置,自然是走默认的事务传播机制了,也就是 Propagation.REQUIRED。

国际惯例,列出事务传播机制: 

  1. 1、PROPAGATION_REQUIRED 
  2. 当前没事务,则创建事务;存在事务,就加入该事务,这是最常用的设置。  
  3. 2、PROPAGATION_SUPPORTS  
  4. 当前存在事务,就加入事务,当前不存在事务,就以非事务方式执行。 
  5. 3、PROPAGATION_MANDATORY  
  6. 当前存在事务,就加入事务;当前不存在事务,就抛出异常。  
  7. 4、PROPAGATION_REQUIRES_NEW  
  8. 无条件创建新事务。  
  9. 5、PROPAGATION_NOT_SUPPORTED  
  10. 以非事务方式执行,如果当前存在事务,就将当前事务挂起。  
  11. 6、PROPAGATION_NEVER  
  12. 以非事务方式运行,如果存在事务,就抛出异常。 
  13. 7、PROPAGATION_NESTED  
  14. 开始执行事务前,先保存一个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 代替。 

 

责任编辑:庞桂玉 来源: Java技术栈
相关推荐

2010-02-01 15:01:34

C++抛出异常

2022-07-05 14:19:30

Spring接口CGLIB

2009-03-18 08:59:28

throw异常Java

2021-09-04 07:56:44

Spring事务失效

2012-05-29 16:25:45

Transaction

2021-02-05 11:54:09

Bug漏洞代码

2020-10-19 11:05:17

SpringTransaction事务

2024-11-07 11:04:55

2023-10-12 10:04:03

Java代码

2023-10-30 07:36:19

Spring事务传播机制

2024-01-15 08:28:31

Spring事件

2019-08-23 09:20:35

Spring 5编程Java

2010-07-02 14:34:35

SQL Server

2010-05-05 09:05:07

checked异常

2020-08-19 09:45:29

Spring数据库代码

2018-05-21 11:02:24

Docker硬盘空间

2020-09-27 14:13:50

Spring BootJava框架

2019-07-09 09:19:51

分布式事务App

2013-07-17 14:13:08

产品产品失败

2020-02-06 11:30:08

代码JavaScript&&
点赞
收藏

51CTO技术栈公众号