仅标记为回滚的事务:如何找到原因
Posted
技术标签:
【中文标题】仅标记为回滚的事务:如何找到原因【英文标题】:Transaction marked as rollback only: How do I find the cause 【发布时间】:2013-10-18 14:13:27 【问题描述】:我在@Transactional 方法中提交事务时遇到问题:
methodA()
methodB()
@Transactional
methodB()
...
em.persist();
...
em.flush();
log("OK");
当我从 methodA() 调用 methodB() 时,该方法成功通过,我可以在日志中看到“OK”。但后来我得到了
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at methodA()...
-
异常中完全缺少 methodB 的上下文 - 我想这可以吗?
methodB() 中的某些东西将事务标记为仅回滚?我怎样才能找到它?例如,有没有办法检查
getCurrentTransaction().isRollbackOnly()?
之类的东西 - 像这样我可以逐步检查该方法并找到原因。
【问题讨论】:
相关:***.com/questions/33277563/… 相关:***.com/q/25322658/697313 有趣的是,如果你的数据库表不存在,有时也会出现这个错误。 【参考方案1】:当您将您的方法标记为@Transactional
时,您的方法中出现的任何异常都会将周围的 TX 标记为仅回滚(即使您捕获它们)。您可以使用 @Transactional
注释的其他属性来防止它回滚,例如:
@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
【讨论】:
好吧,我尝试使用noRollbackFor=Exception.class
,但它似乎没有任何效果——它是否适用于继承的异常?
是的。查看您自己的答案,这是正确的(您没有在第一篇文章中提供methodC
)。 methodB
和 methodC
都使用相同的 TX 并且始终使用最具体的 @Transactional
注释,因此当 methodC
抛出异常时,周围的 TX 将被标记为仅回滚。您还可以使用不同的传播标记来防止这种情况发生。
@lolotron @Ean 我可以确认它确实适用于只读事务。我的方法是在只读事务上抛出EmptyResultDataAccessException
异常,我得到了同样的错误。将我的注释更改为 @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)
解决了问题。
这个答案是错误的。 Spring 只知道通过 @Transactional
代理包装器的异常,即 uncaught。有关完整故事,请参阅 Vojtěch 的其他答案。可能有嵌套的 @Transactional
方法可以标记您的事务仅回滚。
noRollbackFor
仅在 globalRollbackOnParticipationFailure=false
时有效【参考方案2】:
我终于明白了问题所在:
methodA()
methodB()
@Transactional(noRollbackFor = Exception.class)
methodB()
...
try
methodC()
catch (...) ...
log("OK");
@Transactional
methodC()
throw new ...();
即使methodB
有正确的注解,methodC
却没有。抛出异常时,第二个@Transactional
将第一个事务标记为仅回滚。
【讨论】:
事务的状态存储在线程局部变量中。当spring拦截methodC并将标志设置为回滚时,您的事务已经标记为回滚。任何进一步抑制异常都无济于事,因为当最终提交发生时,您将收到错误 @Vojtěch 假设如果 methodC 有propagation=requires_new
那么 methodB 不会回滚?
methodC
必须在不同的 Spring bean/服务中,或者以某种方式通过 Spring 代理访问。否则 Spring 将不可能知道您的异常。只有通过@Transactional
注解的异常才能将事务标记为rollback-only。
这不是解决方案。这只是对Spring Transaction AOP机制的误解和错误使用。仅当您确定在该位置应用的操作需要单独的事务上下文或传播时,才应使用事务注释。在任何其他情况下,您都可以正确设置事务传播。 -1
标记第一笔交易。没有第一笔或第二笔交易——那里只有一个。因为默认@Transactional
的传播是REQUIRED
(被视为“在现有事务中执行,如果有的话”)【参考方案3】:
要快速获取导致的异常无需重新编码或重建,请在
上设置断点org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3
然后在堆栈中向上,通常到某个拦截器。在那里您可以从某个 catch 块中读取导致异常的原因。
【讨论】:
在 Hibernate 4.3.11 中,它是org.hibernate.jpa.internal.TransactionImpl
非常好我的朋友!
谢谢!在较新版本的 Hibernate (5.4.17) 中,类是 org.hibernate.engine.transaction.internal.TransactionImpl
,方法是 setRollbackOnly
。
org.hibernate.jpa.internal.TransactionImpl.setRollbackOnly
Hibernate 5.0.12 的方法【参考方案4】:
我在运行我的应用程序时遇到了这个异常。
最后问题出在 sql 查询上。我的意思是查询是错误的。
请验证您的查询。这是我的建议
【讨论】:
澄清一下:如果您 1. sql 语法有错误 2. 设置回滚异常 3. 有 readOnly 事务您将收到此错误,因为 sql 语法会导致触发一个异常回滚失败,因为您处于“只读”模式。【参考方案5】:查找在代码的...
部分中引发和捕获的异常。运行时和回滚应用程序异常在从业务方法中抛出时会导致回滚,即使在其他地方捕获也是如此。
您可以使用上下文来确定事务是否标记为回滚。
@Resource
private SessionContext context;
context.getRollbackOnly();
【讨论】:
在我看来我找到了原因,但我不明白为什么会这样。内部方法会引发异常,我会捕获、记录并忽略该异常。但无论如何,事务仅被标记为回滚。我该如何预防?我不希望事务受到我正确捕获的异常的影响。SessionContext
是 Spring 中的标准类吗?在我看来,它更像是 EJB3,并且它不包含在我的 Spring 应用程序中。
糟糕的是,我错过了关于春天的事实。无论如何,应该有类似TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()
的东西可用。【参考方案6】:
找到一个很好的解释和解决方案:https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/
1) 如果嵌套方法不需要事务控制,则从嵌套方法中删除 @Transacional。所以即使它有异常,它只是冒泡,不会影响事务性的东西。
或者:
2) 如果嵌套方法确实需要事务控制,则将其设置为 REQUIRE_NEW 用于传播策略,这样即使抛出异常并仅标记为回滚,调用者也不会受到影响。
【讨论】:
谢谢!在嵌套方法中要求新事务是我的问题的解决方案【参考方案7】:在 Bean.xml 中禁用事务管理器
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
注释掉这些行,你会看到导致回滚的异常;)
【讨论】:
【参考方案8】:在productRepository中应用下面的代码
@Query("update Product set prodName=:name where prodId=:id ")
@Transactional
@Modifying
int updateMyData(@Param("name")String name, @Param("id") Integer id);
在junit测试中应用下面的代码
@Test
public void updateData()
int i=productRepository.updateMyData("Iphone",102);
System.out.println("successfully updated ... ");
assertTrue(i!=0);
我的代码运行良好
【讨论】:
【参考方案9】:嵌套方法回滚总是有原因的。如果您没有看到原因,您需要将您的记录器级别更改为调试,您将在其中看到事务失败的更多详细信息。我通过添加更改了我的 logback.xml
<logger name="org.springframework.transaction" level="debug"/>
<logger name="org.springframework.orm.jpa" level="debug"/>
然后我在日志中得到了这一行:
Participating transaction failed - marking existing transaction as rollback-only
所以我只是单步执行了我的代码,看看这行是在哪里生成的,发现有一个 catch 块没有抛出任何东西。
private Student add(Student s)
try
Student retval = studentRepository.save(s);
return retval;
catch (Exception e)
return null;
【讨论】:
以上是关于仅标记为回滚的事务:如何找到原因的主要内容,如果未能解决你的问题,请参考以下文章