Spring事务回滚报错:Transaction rolled back because it has been marked as rollback-only

1.起因

Transaction rolled back because it has been marked as rollback-only

避免大家没有耐心阅读,先说总结,整个问题是由:

事务嵌套,下层事务把异常吞了,导致的报错。

接下来,展开讲讲详情,先来看代码:

代码逻辑其实很简单实现一个双重校验逻辑:

图片验证码+手机验证码+账号密码一起来进行登录校验。应为要操作多张表,所以使用了事务来包裹整个登录过程。

verifyMobCode、verifyLogin两个方法调用了之前就写好的专门进来鉴权的服务:

为了将登录失败的情况封装成统一信息返回,这里捕获了异常,没有将异常抛出,而是返回的实体。

这一步尤其关键,因为这里就是出问题的核心原因。

这个鉴权服务是具有事务性的:

OK,这里我们可以总结,代码结构是:

事务嵌套事务。

好,进入提测环节:

进行验证码错误的用例的时候,直接报错:

报错从字面意思来讲很好理解,下层事务回滚了,所以上层事务也必须回滚了。那么这里就出现一个问题,这个异常你是捕获不了的,它会直接返给前端,因为它是Spring框架的一个错误。既然是框架的错误说明我们在使用上就有错了。

错在哪里?下层事务把异常吞了!事务的回滚是因为发现到异常而被触发的,下层把异常吞了,上层事务根本感受不到异常,在上层事务提交的时候才发现,下层事务已经回滚了,所以自己也跟着回滚了,所以这根本就不是一个正常的事务回滚的过程,所以抛出了框架层面的报错。正常的事务回滚流程应该是什么样的:

层层抛异常,引起层层的回滚。

2.解决方法

用try-catch将异常捕获下来,将错误信息放在Exception的Message中,前端直接抛出这个Message就是了。

3.复习一下事务的传播行为

Spring中存在着一个概念——事务的传播行为。事务的传播行为指的是一个事务方法被另一个事务方法调用,即嵌套事务,出现异常该怎么回滚。

Spring规定有7种事务的传播行为:

  • required:如果出现异常的方法有事务就用这个事务,没有的话再去用其他的事务。例如:方法A调用方法B,如果B出现异常,B有事务就用B的事务,B没有事务,才会去用A的事务(只要有一个回滚,整体就会回滚)

  • requires_new 如果当前有事务,就用当前事务,否则就重新开一个事务。例如:方法A调用方法B,如果B出现异常,B有事务就用B的事务,B没有事务,就会去新建一个事务,最后效果是B会回滚,但是A不会回滚。

  • supports 当前没有事务,就以非事务运行。当前有事务呢?就以当前事务运行。

  • mandatory 其他没有事务就会抛异常。当前没有事务抛出异常,当前有事务则支持当前事务

  • not_supported 以非事务执行。

  • never 以非事务执行,如果存在事务,抛出异常。

  • nested 如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。