首页   >   代码编程   >   JAVA开发

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),不要害怕抛异常,现在很多框架都会有全局异常处理器这个东西,很多时候,我们完全可以利用异常和它的搭配减轻很多重复代码。

雨落无影

QQ群Ⅰ: 686430774

QQ群Ⅱ: 718410762

QQ群Ⅲ: 638620451

QQ群Ⅳ: 474195684

QQ群Ⅴ: 463034360

QQ群Ⅵ: 879266502

QQ群Ⅶ: 627786015

工作5分钟,吹逼2小时: 855525339 (娱乐消遣,广告狗勿进)

如果文章有帮到你,可以考虑请博主喝杯咖啡!

分享到:

欢迎分享本文,转载请注明出处!

作者:不忘初心

发布时间:2021-12-05

永久地址:https://www.jiweichengzhu.com/article/9e7c096df3484dc1bd78c2b95a702620

评论

雨落无影

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

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

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