仅标记为回滚的事务:如何找到原因

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)。 methodBmethodC 都使用相同的 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 假设如果 methodCpropagation=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;
    

【讨论】:

以上是关于仅标记为回滚的事务:如何找到原因的主要内容,如果未能解决你的问题,请参考以下文章

Java事务不回滚的原因总结

MySql事务无法回滚的原因

MySql事务无法回滚的原因

ROLLBACK语句只能针对未提交的事务进行回滚操作,已提交的事务是不能回滚的?

Django数据库--事务及事务回滚

Spring事务管理中的配置文件