Spring service层的事务探究(Transaction rolled back because it has been marked as rollback-only)

不念不忘少年蓝@ 2023-06-17 14:59 32阅读 0赞

1 背景

代码在一个service层的事务方法中捕获并解决另一个事务方法抛出的异常,想要以一个正常信息返回到事务外,即controller层。

示例代码如下:

controller层代码

  1. @Controller
  2. public class TestController {
  3. @Resource
  4. private TestService testService;
  5. @RequestMapping(value="/test", produces="application/json;charset=UTF-8")
  6. @ResponseBody
  7. public String test() {
  8. try {
  9. String mes = testService.testService();
  10. return "{\"code\":1, \"message\":mes}";
  11. } catch (ParameterException e) {
  12. return "{\"code\":0, \"message\":e.getMessage()}";
  13. } catch (Exception e) {
  14. return "{\"code\":0, \"message\":\"失败\"}";
  15. }
  16. }
  17. }

service层代码

  1. @Service
  2. @Transactional
  3. public class TestService {
  4. @Resource
  5. private TestService testService;
  6. public String testService() {
  7. try {
  8. testService.testException();
  9. } catch (ParameterException e) {
  10. return e.getMessage();
  11. } catch (Exception e) {
  12. return "报错返回";
  13. }
  14. return "成功返回";
  15. }
  16. public String testException() throws Exception {
  17. if (true) {
  18. throw new ParameterException("报参数错误返回");
  19. }
  20. return "gg";
  21. }
  22. }

ParameterException是我自己定义的异常类,从上面代码可以看到,我们预期的结果应该是这样的:

1.在 testException() 方法中抛出 ParameterException 异常开始,异常信息是:报参数错误返回

2.异常抛出到 testService() 方法中就给消化解决了,并且返回抛上来的异常信息:报参数错误返回

3.返回的异常信息到controller层,并被成功返回到页面上: {“code”:1, “message”:”报参数错误返回”} 。

然而事实并非如此,而是直接报错: Transaction rolled back because it has been marked as rollback-only ,最后返回:{“code”:0, “message”:”失败”}

2 原因说明

在service层抛出异常,到返回数据到controller层,Spring到底做了什么呢?我们可以自己从 throw new ParameterException(“报参数错误返回”); 处开始打断点一个一个地看,这里就不仔细分析了。

将代码分析转变为文字,在全局配置了事务的传播机制是REQUIRED(或明确将事务设置为了RollbackOnly),在 testException() 方法以事务方式运行时抛出的 非受检异常 被上一层的事务方法 testService() 消化解决了,没有继续抛出去,而运行完 testService() 方法并准备正常返回信息时,Spring会将这个事务进行一次commit,但是此时并不能正常commit,而是进行了rollback操作,并且在事务边界到达时抛出了UnexpectedRollbackException异常。

总的来说就是:抛出的非受检异常被处理了,导致设置了RollbackOnly的事务无法正常commit和rollback操作。

3 解决方法

这里的解决方法有如下几种:

1.在事务中的非受检异常(即自定义的事务异常)不要在事务中自己解决掉,而是继续抛出去到事务边界外(这里即controller层)再来解决。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1MDkyMDc5_size_16_color_FFFFFF_t_70

2.若确实需要在事务中消化它,可以解决一个正常受检异常(如:Exception),让事务commit操作可以正常进行。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1MDkyMDc5_size_16_color_FFFFFF_t_70 1

3.将处理非受检异常的事务方法用注解 @Transactional(propagation = Propagation.NOT_SUPPORTED) 配置为此方法以非事务方式运行,这样就不会另外执行其他操作了。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1MDkyMDc5_size_16_color_FFFFFF_t_70 2

4 事务扩展

4.1 事务传播方式

@Transactional注解中可以配置propagation指定事务传播方式,spring的Propagation提供了如下事务传播属性:

  • REQUIRED
    进入当前事务,如果没有事务则创建新的事务
  • SUPPORTS
    支持当前事务状态,如果有事务则进入,没有则无事务方式运行
  • MANDATORY
    当前必须有事务,如果没有则抛异常
  • REQUIRES_NEW
    无论如何都创建新的事务
  • NOT_SUPPORTED
    非事务方式运行
  • NEVER
    非事务方式运行,如果当前有事务则抛出异常
  • NESTED
    新建一个事务,如果当前事务存在,则以嵌套事务运行

发表评论

表情:
评论列表 (有 0 条评论,32人围观)

还没有评论,来说两句吧...

相关阅读