Transacción marcada solo como reversión: ¿Cómo encuentro la causa?

93

Tengo problemas para realizar una transacción dentro de mi método @Transactional:

methodA() {
    methodB()
}

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

Cuando llamo a methodB () desde methodA (), el método pasa correctamente y puedo ver "OK" en mis registros. Pero luego 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. El contexto del método B falta por completo en la excepción, ¿cuál está bien, supongo?
  2. ¿Algo dentro del método B () marcó la transacción como solo reversión? ¿Cómo puedo averiguarlo? ¿Existe, por ejemplo, una forma de comprobar algo como getCurrentTransaction().isRollbackOnly()?? Así podría recorrer el método y encontrar la causa.
Vojtěch
fuente
relacionado: stackoverflow.com/q/25322658/697313
Yaroslav Stavnichiy
Cosas interesantes a tener en cuenta es que, si la tabla de su base de datos no existe, en algún momento también se mostrará este error.
Ng Sek Long

Respuestas:

101

Cuando marca su método como @Transactional, la aparición de cualquier excepción dentro de su método marcará el TX circundante como solo retroceso (incluso si los detecta). Puede usar otros atributos de la @Transactionalanotación para evitar que retroceda como:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Ean V
fuente
6
Bueno, intenté usarlo noRollbackFor=Exception.class, pero parece que no tiene ningún efecto, ¿funciona para excepciones heredadas?
Vojtěch
6
Si lo hace. Mirando su propia respuesta, es correcta (no la había proporcionado methodCen su primera publicación). Ambos methodBy methodCusan el mismo TX y siempre @Transactionalse usa la anotación más específica , por lo que cuando methodCarroja la excepción, el TX circundante se marcará como solo retroceso. También puede utilizar diferentes marcadores de propagación para evitar esto.
Ean V
@Ean cualquier excepción dentro de su método marcará el TX circundante como solo retroceso. ¿Esto también se aplica a transacciones de solo lectura?
Marko Vranjkovic
1
@lolotron @Ean Puedo confirmar que efectivamente se aplicará a una transacción de solo lectura. Mi método lanzaba una EmptyResultDataAccessExceptionexcepción en una transacción de solo lectura y obtuve el mismo error. Cambiando mi anotación para @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)solucionar el problema.
cbmeeks
5
Esta respuesta es incorrecta. Spring solo conoce las excepciones que pasan a través del @Transactionalcontenedor de proxy, es decir, no detectadas . Vea otra respuesta de Vojtěch para conocer la historia completa. Puede haber @Transactionalmétodos anidados que pueden marcar su transacción solo como reversión.
Yaroslav Stavnichiy
68

Finalmente entendí el problema:

methodA() {
    methodB()
}

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

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

Lo que sucede es que aunque methodBtiene la anotación correcta, methodCno la tiene. Cuando se lanza la excepción, la segunda @Transactionalmarca la primera transacción como Revertir solo de todos modos.

Vojtěch
fuente
5
El estado de la transacción se almacena en una variable local de subproceso. Cuando el resorte intercepta el método C y establece la bandera como reversión, su transacción ya está marcada para reversión. Cualquier supresión adicional de la excepción no ayudará porque cuando ocurra la confirmación final, obtendrá el error
vive el
@ Vojtěch De alguna manera, hipotéticamente, si methodC tiene, propagation=requires_newentonces methodB no se revertirá?
deFreitas
4
methodCdebe estar en un bean / servicio de Spring diferente o se debe acceder de alguna manera a través del proxy de Spring. De lo contrario, Spring no tendrá la posibilidad de conocer su excepción. La única excepción que pasa por la @Transactionalanotación puede marcar la transacción como solo de reversión.
Yaroslav Stavnichiy
43

Para obtener rápidamente la excepción causante sin la necesidad de volver a codificar o reconstruir , establezca un punto de interrupción en

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

y subir en la pila, generalmente a algún Interceptor. Allí puede leer la excepción causante de algún bloque de captura.

FelixJongleur42
fuente
6
En Hibernate 4.3.11, esorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe
¡Muy bien mi amigo!
Rafael Andrade
¡Gracias! En las versiones más recientes de Hibernate (5.4.17) la clase es org.hibernate.engine.transaction.internal.TransactionImply el método es setRollbackOnly.
Peter Catalin
11

Luché con esta excepción mientras ejecutaba mi aplicación.

Finalmente, el problema estaba en la consulta sql . quiero decir que la consulta es incorrecta.

por favor verifique su consulta. Esta es mi sugerencia

Kumaresan Perumal
fuente
1
Para aclarar: si 1. tiene un error en su sintaxis sql 2. está configurado para revertir en la excepción 3. tiene transacciones readOnly obtendrá este error porque la sintaxis sql causa una excepción que desencadena una reversión que falla porque está en " modo de sólo lectura.
Dave
7

Busque excepciones lanzadas y atrapadas en las ...secciones de su código. Las excepciones de aplicaciones en tiempo de ejecución y reversión provocan una reversión cuando se eliminan de un método comercial, incluso si se detectan en otro lugar.

Puede utilizar el contexto para averiguar si la transacción está marcada para reversión.

@Resource
private SessionContext context;

context.getRollbackOnly();
Mareen
fuente
1
Me parece que encontré la causa, pero no entiendo por qué sucede esto. Un método interno arroja una excepción, que capturo, registro e ignoro. Pero la Transacción está marcada como Revertir solo de todos modos. ¿Cómo puedo prevenirlo? No quiero que las transacciones se vean influenciadas por excepciones que capto correctamente.
Vojtěch
¿Es SessionContextuna clase estándar en primavera? Me parece que es más bien EJB3 y no está contenido en mi aplicación Spring.
Vojtěch
3
Lo malo es que me perdí el hecho de que se trata de Spring. De todos modos debería haber algo como TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()disponible.
Mareen
2

Encontré una buena explicación con soluciones: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) elimine @Transacional del método anidado si realmente no requiere control de transacciones. Entonces, incluso tiene una excepción, simplemente surge y no afecta las transacciones.

O:

2) si el método anidado necesita control de transacciones, hágalo como REQUIRE_NEW para la política de propagación de esa manera, incluso si arroja una excepción y está marcado como solo reversión, la persona que llama no se verá afectada.

aquajach
fuente
1

deshabilite el transactionmanager en su 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 estas líneas y verá la excepción que causa la reversión;)

rémy
fuente
0

aplicar el siguiente código en productRepository

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

mientras esté en la prueba junit, aplique el código siguiente

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

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

}

está funcionando bien para mi código

Asif Raza
fuente