Transação marcada como reversão apenas: como encontro a causa


92

Estou tendo problemas para confirmar uma transação no meu método @Transactional:

methodA() {
    methodB()
}

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

Quando chamo methodB () de methodA (), o método foi aprovado com sucesso e posso ver "OK" em meus logs. Mas então eu consigo

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. O contexto do método B está completamente ausente na exceção - o que está certo, suponho?
  2. Algo dentro do methodB () marcou a transação apenas como rollback? Como posso descobrir? Existe, por exemplo, uma maneira de verificar algo como getCurrentTransaction().isRollbackOnly()?- eu poderia percorrer o método e encontrar a causa.



O que é interessante notar é que, se sua tabela de banco de dados não existir, às vezes esse erro também será mostrado.
Ng Sek Long de

Respostas:


101

Quando você marca seu método como @Transactional, a ocorrência de qualquer exceção dentro de seu método marcará o TX circundante apenas como reversão (mesmo se você capturá-los). Você pode usar outros atributos de @Transactionalanotação para evitar que ela volte, como:

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

6
Bem, tentei usar noRollbackFor=Exception.class, mas parece não ter surtido efeito - funciona para exceções herdadas?
Vojtěch

6
Sim. Olhando para a sua própria resposta, isso mesmo (você não forneceu methodCna sua primeira postagem). Ambos methodBe methodCusam o mesmo TX e sempre a @Transactionalanotação mais específica é usada, portanto, quando methodClançar a exceção, o TX adjacente será marcado como somente rollback. Você também pode usar marcadores de propagação diferentes para evitar isso.
Ean V

@Ean qualquer exceção dentro de seu método marcará o TX ao redor como apenas reversão . Isso também se aplica a transações somente leitura?
Marko Vranjkovic

1
@lolotron @Ean Posso confirmar que realmente se aplicará a uma transação somente leitura. Meu método estava lançando uma EmptyResultDataAccessExceptionexceção em uma transação somente leitura e recebi o mesmo erro. Alterar minha anotação para @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)corrigir o problema.
cbmeeks

5
Esta resposta está errada. O Spring só conhece as exceções que passam pelo @Transactionalproxy wrapper, ou seja, não capturadas . Veja outra resposta da Vojtěch para a história completa. Pode haver @Transactionalmétodos aninhados que podem marcar sua transação apenas como rollback.
Yaroslav Stavnichiy

68

Eu finalmente entendi o problema:

methodA() {
    methodB()
}

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

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

O que acontece é que, embora methodBtenha a anotação correta, methodCnão tem. Quando a exceção é lançada, o segundo @Transactionalmarca a primeira transação apenas como Rollback.


5
O status da transação é armazenado em uma variável local do thread. Quando a mola intercepta o método C e define o sinalizador como rollback, sua transação já está marcada para rollback. Qualquer supressão adicional de exceção não ajudará porque quando o commit final acontecer, você receberá o erro
vive

@ Vojtěch De qualquer maneira, hipoteticamente, se o método C tiver propagation=requires_new, o método B não reverterá?
deFreitas de

4
methodCdeve estar em um bean / serviço diferente do Spring ou de alguma forma acessado por meio do proxy Spring. Caso contrário, a Spring não terá possibilidade de saber sobre sua exceção. Apenas a exceção que passa pela @Transactionalanotação pode marcar a transação como somente rollback.
Yaroslav Stavnichiy

43

Para buscar rapidamente a exceção causadora sem a necessidade de recodificar ou reconstruir , defina um ponto de interrupção em

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

e sobe na pilha, geralmente para algum Interceptor. Lá você pode ler a exceção causadora de algum bloco catch.


6
No Hibernate 4.3.11, éorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe

Muito bem meu amigo!
Rafael Andrade

Obrigado! Em versões mais recentes do Hibernate (5.4.17) a classe é org.hibernate.engine.transaction.internal.TransactionImple o método é setRollbackOnly.
Peter Catalin

11

Lutei com essa exceção ao executar meu aplicativo.

Finalmente o problema estava na consulta sql . quero dizer que a consulta está errada.

por favor, verifique sua consulta. Esta é minha sugestão


1
Para esclarecer: se você 1. tiver um erro em sua sintaxe sql 2. estiver configurado para reverter na exceção 3. tiver transações readOnly, você receberá este erro porque a sintaxe sql causa uma exceção que dispara uma reversão que falha porque você está em " modo somente leitura ".
Dave

6

Procure exceções sendo lançadas e capturadas no ... seções de seu código. As exceções de tempo de execução e de aplicativo de reversão causam reversão quando descartadas de um método de negócios, mesmo se capturadas em algum outro lugar.

Você pode usar o contexto para descobrir se a transação está marcada para reversão.

@Resource
private SessionContext context;

context.getRollbackOnly();

1
Parece-me que encontrei a causa, mas não entendo por que isso está acontecendo. Um método interno lança uma exceção, que eu capturo, registro e ignoro. Mas a transação é marcada como Rollback apenas de qualquer maneira. Como posso evitar isso? Não quero que as transações sejam influenciadas por exceções que eu pego corretamente.
Vojtěch

É SessionContextuma aula padrão na primavera? Parece-me que é bastante EJB3 e não está contido em meu aplicativo Spring.
Vojtěch

3
Meu mal, eu perdi o fato de que é sobre a primavera. De qualquer forma, deve haver algo como TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()disponível.
Mareen,

2

Encontrou uma boa explicação com as soluções: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) remova o @Transacional do método aninhado se ele realmente não exigir o controle da transação. Portanto, mesmo com exceção, ele simplesmente surge e não afeta as transações.

OU:

2) se o método aninhado precisar de controle de transação, torne-o REQUIRE_NEW para a política de propagação dessa forma, mesmo que lance uma exceção e seja marcado apenas como rollback, o chamador não será afetado.


1

desative o gerenciador de transações em seu 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>

comente essas linhas e você verá a exceção que causa a reversão;)


0

aplique o código abaixo em productRepository

@Query("update Product set prodName=:name where prodId=:id ") @Transactional @Modifying int updateMyData(@Param("name")String name, @Param("id") Integer id);

enquanto no teste junit, aplique o código abaixo

@Test
public void updateData()
{
  int i=productRepository.updateMyData("Iphone",102);

  System.out.println("successfully updated ... ");
  assertTrue(i!=0);

}

está funcionando bem para o meu código

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.