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


93

我在@Transactional方法中提交事务时遇到问题:

methodA() {
    methodB()
}

@Transactional
methodB() {
    ...
    em.persist();
    ...
    em.flush();
    log("OK");
}

当我从methodA()调用methodB()时,该方法成功通过,并且我可以在日志中看到“确定”。但后来我明白了

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()...
  1. 在异常中完全缺少methodB的上下文-我想这可以吗?
  2. methodB()中的某些内容将事务标记为仅回滚?我如何找到它?例如,是否有一种检查类似getCurrentTransaction().isRollbackOnly()?方法的方法-这样,我可以逐步检查方法并找到原因。



有趣的是,如果您的数据库表不存在,有时还会显示此错误。
Ng Sek Long

Answers:


101

当您将方法标记@Transactional为时,如果方法内部发生任何异常,则只会将周围的TX标记为回滚(即使您捕获了它们)。您可以使用@Transactional批注的其他属性来防止其回滚,例如:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)

6
好吧,我尝试使用noRollbackFor=Exception.class,但是它似乎没有效果–它对继承的异常有效吗?
Vojtěch

6
是的,它确实。查看您自己的答案,这是正确的(您methodC在第一篇文章中没有提供)。两者methodBmethodC用相同的TX始终把最具体的@Transactional使用注释,所以当methodC抛出异常,周围TX将被标记为只回滚。您也可以使用其他传播标记来防止这种情况。
Ean V

@Ean 方法中的任何异常都将仅将周围的TX标记为回滚。这也适用于readOnly事务吗?
Marko Vranjkovic 2014年

1
@lolotron @Ean我可以确认它确实适用于只读事务。我的方法是EmptyResultDataAccessException在只读事务上引发异常,而我遇到了同样的错误。更改我的注释以@Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)解决问题。
cbmeeks

5
这个答案是错误的。Spring只知道通过@Transactional代理包装器传递的异常,即uncaught。有关完整的故事,请参见Vojtěch的其他答案。可能有嵌套@Transactional方法可以标记您的事务仅回滚。
Yaroslav Stavnichiy

68

我终于明白了这个问题:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

发生的事情是,即使methodB具有正确的注释,methodC也没有。当引发异常时,第二个异常@Transactional将第一个事务始终标记为仅回滚。


5
事务的状态存储在线程局部变量中。当spring截获methodC并将标志设置为rollback时,您的事务已被标记为可以回滚。任何进一步的异常抑制都将无济于事,因为当最后一次提交发生时,您会得到此错误
生活

@Vojtěch任何方式假设如果methodCpropagation=requires_new那么的methodB不会回滚?
deFreitas '16

4
methodC必须位于不同的Spring Bean /服务中,或通过Spring代理以某种方式访问​​。否则,Spring将无法得知您的异常。只有通过@Transactional注释传递的异常才能将事务标记为仅回滚。
Yaroslav Stavnichiy

43

要快速获取导致异常的信息而无需重新编码或重建,请在上设置一个断点

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3

并通常放在某些拦截器中。在那里,您可以从一些catch块中读取导致异常的信息。


6
在Hibernate 4.3.11中,它是org.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe

我的朋友很好!
拉斐尔·安德拉德

谢谢!在较新版本的Hibernate(5.4.17)中,类为org.hibernate.engine.transaction.internal.TransactionImpl,方法为setRollbackOnly
Peter Catalin

11

运行我的应用程序时,我为此异常而苦苦挣扎。

最后问题出在sql查询上。我的意思是查询是错误的。

请验证您的查询。这是我的建议


1
需要说明的是:如果1. SQL语法中有错误2.设置为在异常3上回滚。具有readOnly事务,则您将收到此错误,因为sql语法会导致触发回滚的异常,因为您在“只读”模式。
戴夫

7

查找在...代码部分中引发和捕获的异常。当运行时和回滚应用程序异常抛出某个业务方法时,即使在其他地方被捕获,也会导致回滚。

您可以使用上下文来查找是否将事务标记为回滚。

@Resource
private SessionContext context;

context.getRollbackOnly();

1
在我看来,我找到了原因,但我不明白为什么会这样。内部方法引发异常,我捕获,记录和忽略该异常。但是无论如何,该事务仅被标记为回滚。我该如何预防?我不希望交易受到我正确捕获的异常的影响。
Vojtěch

SessionContext春季班是标准班吗?在我看来,它似乎是EJB3,并且未包含在我的Spring Application中。
Vojtěch

3
糟糕的是,我错过了有关Spring的事实。无论如何,应该有一些TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()可用的东西。
2013年

2

找到了很好的解决方案说明:https : //vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1)如果@Transacional确实不需要事务控制,则从嵌套方法中删除它。因此,即使它有例外,它也会冒泡,并且不会影响事务性内容。

要么:

2)如果嵌套方法确实需要事务控制,则即使传播引发异常并将其标记为仅回滚,也可以将其设置为传播策略的REQUIRE_NEW,这样调用方就不会受到影响。


1

在Bean.xml中禁用transactionmanager

<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>

注释掉这些行,您将看到导致回滚的异常;)


0

在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);

}

我的代码工作正常

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.