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

Voj*_*ěch 83 java spring hibernate jpa transactions

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

methodA() {
    methodB()
}

@Transactional
methodB() {
    ...
    em.persist();
    ...
    em.flush();
    log("OK");
}
Run Code Online (Sandbox Code Playgroud)

当我从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()...
Run Code Online (Sandbox Code Playgroud)
  1. 方法B的上下文在异常中完全丢失 - 我想这没关系?
  2. methodB()中的某些内容将事务标记为仅回滚?我怎么能找到它?例如,有一种方法来检查类似的东西getCurrentTransaction().isRollbackOnly()?- 像这样我可以逐步完成方法并找到原因.

Ean*_*n V 92

当您将方法标记为时,方法中@Transactional出现的任何异常都会将周围的TX标记为仅回滚(即使您捕获它们).您可以使用@Transactional注释的其他属性来阻止它回滚,如:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Run Code Online (Sandbox Code Playgroud)

  • 是的,它确实.看看你自己的答案,这是对的(你在第一篇文章中没有提供`methodC`).`methodB`和`methodC`都使用相同的TX,并且始终使用最具体的`@Transactional`注释,因此当`methodC`抛出异常时,周围的TX将被标记为仅回滚.您还可以使用不同的传播标记来防止这种情况. (6认同)
  • 好吧,我尝试使用`noRollbackFor = Exception.class`,但似乎没有效果 - 它是否适用于继承的异常? (5认同)
  • 这个答案是对的.Spring只知道通过`@Transactional`代理包装器的异常,即*uncaught*.有关完整故事,请参阅Vojtěch的其他答案.可能存在嵌套的`@Transactional`方法,可以标记您的事务仅回滚. (5认同)
  • @lolotron @Ean 我可以确认它确实适用于只读事务。我的方法在只读事务上抛出“EmptyResultDataAccessException”异常,并且我得到了相同的错误。将我的注释更改为“@Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)”解决了问题。 (2认同)
  • 仅当“globalRollbackOnParticipationFailure = false”时“noRollbackFor”才起作用 (2认同)

Voj*_*ěch 62

我终于明白了这个问题:

methodA() {
    methodB()
}

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

@Transactional
methodC() {
    throw new ...();
}
Run Code Online (Sandbox Code Playgroud)

会发生什么是即使methodB有正确的注释,methodC也没有.抛出异常时,第二个@Transactional事务将第一个事务标记为仅回滚.

  • 事务的状态存储在线程局部变量中.当spring拦截方法C并将标志设置为回滚时,您的事务已标记为回滚.任何进一步的异常抑制都无济于事,因为当最终提交发生时,您将收到错误 (5认同)
  • `methodC`必须在不同的Spring bean/service中,或以某种方式通过Spring代理访问.否则Spring不可能知道你的异常.只有通过`@Transactional`注释的异常才能将事务标记为仅回滚. (3认同)
  • _标记第一笔交易_。没有第一笔或第二笔交易——只有一笔交易。因为默认情况下`@Transactional`的传播是`REQUIRED`(被视为“在现有事务中执行,如果有的话”) (2认同)

Fel*_*r42 40

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

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3
Run Code Online (Sandbox Code Playgroud)

然后进入堆栈,通常是一些拦截器.在那里你可以从一些catch块中读取导致异常.

  • 在Hibernate 4.3.11中,它是`org.hibernate.jpa.internal.TransactionImpl` (6认同)
  • 谢谢!在较新版本的 Hibernate (5.4.17) 中,类是“org.hibernate.engine.transaction.internal.TransactionImpl”,方法是“setRollbackOnly”。 (4认同)

Kum*_*mal 11

我在运行我的应用程序时遇到了这个异常.

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

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


aqu*_*ach 6

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

1) 如果它并不真正需要事务控制,则从嵌套方法中删除 @Transacional。所以即使它有异常,它也只是冒泡,不会影响事务性的东西。

或者:

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


Jan*_*net 6

嵌套方法回滚总是有原因的。如果您没有看到原因,则需要将记录器级别更改为调试,您将在其中看到事务失败的更多详细信息。我通过添加更改了 logback.xml

<logger name="org.springframework.transaction" level="debug"/>
<logger name="org.springframework.orm.jpa" level="debug"/>
Run Code Online (Sandbox Code Playgroud)

然后我在日志中得到这一行:

Participating transaction failed - marking existing transaction as rollback-only
Run Code Online (Sandbox Code Playgroud)

所以我只是单步执行我的代码来查看这一行是在哪里生成的,发现有一个 catch 块没有抛出任何东西。

private Student add(Student s) {
        try {
            Student retval = studentRepository.save(s);
            return retval;
        } catch (Exception e) {
            
        }
        return null;
    }
Run Code Online (Sandbox Code Playgroud)


Mar*_*een 5

查找抛出异常并在...代码的各个部分中捕获的异常.运行时和回滚应用程序异常会在抛出业务方法时导致回滚,即使在某些其他位置被捕获也是如此.

您可以使用上下文来确定事务是否标记为回滚.

@Resource
private SessionContext context;

context.getRollbackOnly();
Run Code Online (Sandbox Code Playgroud)

  • 我的错,我错过了关于Spring的事实.无论如何应该有类似`TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()`的东西. (2认同)