您现在的位置是:网站首页 > 代码编程 > JAVA开发JAVA开发

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

不忘初心 不忘初心 2021-12-05 围观() 评论() 点赞() JAVA开发

简介:最近在整理以前的工作笔记,又发现一个有意思的bug,在一个service调用另外一个类的方法时,出现了一个spring的事务问题,Transaction rolled back because it has been marked as rollback-only,时间过去有点儿久了,不记得是压测时出的问题还是线上运行时出的问题了。

最近在整理以前的工作笔记,又发现一个有意思的bug,在一个service调用另外一个类的方法时,出现了一个spring的事务问题,Transaction rolled back because it has been marked as rollback-only,时间过去有点儿久了,不记得是压测时出的问题还是线上运行时出的问题了。

2021-01-25 19:42:00.025 [pool-3-thread-1] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.sowin.xszz.service.impl.BsBjServiceImpl$$EnhancerBySpringCGLIB$$35a078c9.addBj(<generated>)
at com.sowin.xszz.task.XsBjTask.addBj(XsBjTask.java:23)
at sun.reflect.GeneratedMethodAccessor248.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

上面是完整的详细错误信息,Transaction rolled back because it has been marked as rollback-only这句话翻译过来大体上的意思就是:事务已经回滚,因为它被标记为仅回滚,咋一听还是挺拗口,但是如果大家对spring事务有一定了解的话,就很容易看出来,这个问题就是一个典型的嵌套事务问题

我为什么强调是这是一个典型案例呢?

因为在很多时候,有些人害怕方法出错导致整个调用链崩溃,所以在自己写的方法中都会下意识的加上try catch语句,而spring的事务是利用aop机制来实现的,也就是说提交事务和回滚事务这些操作都是它帮我们做的,我们并没有显示处理,那它之所以能够正确知道是提交事务还是回滚事务,这个就得靠我们给它信号了,没错,你们已经猜到了,抛异常就是我们给它的信号

这么解释大家应该清楚了,应该也能大概猜到这问题多少都跟try catch有关,不着急,我们接着往下看,后面会用到这块儿知识点,我们现在回到spring事务本身来。

在spring的事务体系中,事务是可以继承的,它利用一个传播属性propagation来标记各种继承关系(详细的继承关系这里就不细说,如果大家想了解清楚,请移步Srping事务的七种传播特性),默认的是REQUIRED,也就是说里层的方法共用外层方法的事务。

为了给大家看的更清楚,我给大家简单模拟两句出问题的代码,这样有助于理解,模拟场景为方法A调用方法B,双方都加事务,然后B方法出错抛异常

// 外层
@Transactional(readOnly = true, rollbackFor = Exception.class)
public String A() {
try {
// 调用B方法
String s = B();
// ...
return s;
} catch(Exception e) {
e.printStacktrace();
}
return null;
}
// 里层
@Transactional(readOnly = true, rollbackFor = Exception.class)
public String B() {
// throw Exception
return "test";
}

如果上面的分析看明白了,那这里就好理解了,A()调用B()时,B()方法出错抛异常了,但是A()方法呢,自己把异常给捕获了,而且在吃掉异常之后,它并没有做进一步处理,也没有继续向上抛,那此时就会出现本文中所提到的错误Transaction rolled back because it has been marked as rollback-only。

那可能有人又要问了,那A()吃掉异常跟B()有什么关系呢?

诶,问到点子上来了,我们上面提到了事务的继承关系,那么代入到这个案例中,也就是说B()其实最终使用的还是A()的事务,那么在B()本身出异常时,它自己是已经抛过了的,此时它的transaction已经被设置为了rollback-only了,而当A()将异常吃掉之后,统一交由spring来提交事务,这时候B()就会报这个错误了,大家可以简单理解一下,在这种嵌套事务中外层一旦吃掉异常之后,就会导致里层方法的事务状态不对,从而就会导致该错误的产生。

解决方法:

1、在try catch中手动处理transaction事务状态;

// 外层
@Transactional(readOnly = true, rollbackFor = Exception.class)
public String A() {
try {
// 调用B方法
String s = B();
// ...
return s;
} catch(Exception e) {
// 划重点,看这里
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStacktrace();
}
return null;
}

2、给每一层开启新事务,各自为政(如果涉及到db操作则不建议)

// 里层
// 重点看注解中的propagation参数
@Transactional(readOnly = true, rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public String B() {
// throw Exception
return "test";
}
问题总结:

这个问题其实真的很典型,尤其是新手,一不小心就可能遇到,如果项目是MVC架构,那我们尽量不要在service层中搞太多的try catch,除非是必须的一些场景(如:多线程、IO),不要害怕抛异常,现在很多框架都会有全局异常处理器这个东西,很多时候,我们完全可以利用异常和它的搭配减轻很多重复代码。

事务springbootspring

看完文章,有任何疑问,请加入群聊一起交流!!!

很赞哦! ()

文章评论

  • 请先说点什么
    人参与,条评论

请使用电脑浏览器访问本页面,使用手机浏览器访问本页面会导致下载文件异常!!!

雨落无影

关注上方公众号,回复关键字【下载】获取下载码

用完即删,每次下载需重新获取下载码

若出现下载不了的情况,请及时联系站长进行解决

站点信息

  • 网站程序:spring + freemarker
  • 主题模板:《今夕何夕》
  • 文章统计:篇文章
  • 标签管理标签云
  • 微信公众号:扫描二维码,关注我们