Transazione contrassegnata solo come rollback: come trovo la causa


93

Ho problemi con il commit di una transazione con il mio metodo @Transactional:

methodA() {
    methodB()
}

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

Quando chiamo methodB () da methodA (), il metodo passa correttamente e posso vedere "OK" nei miei log. Ma poi capisco

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. Il contesto del metodoB è completamente mancante nell'eccezione - che va bene suppongo?
  2. Qualcosa all'interno del metodoB () ha contrassegnato la transazione solo come rollback? Come posso scoprirlo? Ad esempio, esiste un modo per verificare qualcosa del genere getCurrentTransaction().isRollbackOnly()?: in questo modo, potrei seguire il metodo e trovare la causa.



Le cose interessanti da notare è che, se la tabella del database non esiste, a volte verrà mostrato anche questo errore.
Ng Sek Long,

Risposte:


101

Quando contrassegni il tuo metodo come @Transactional, il verificarsi di qualsiasi eccezione all'interno del tuo metodo segnerà il TX circostante solo come rollback (anche se li prendi). Puoi utilizzare altri attributi di @Transactionalannotazione per evitare che si ripristini come:

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

6
Bene, ho provato a usare noRollbackFor=Exception.class, ma sembra non avere alcun effetto: funziona per le eccezioni ereditate?
Vojtěch

6
Sì lo fa. Guardando la tua risposta, è vero (non l'avevi fornita methodCnel tuo primo post). Entrambi methodBe methodCutilizzano lo stesso TX e viene sempre utilizzata l' @Transactionalannotazione più specifica , quindi quando methodCgenera l'eccezione, il TX circostante verrà contrassegnato come solo rollback. È inoltre possibile utilizzare diversi marker di propagazione per evitare ciò.
Ean V

@ Qualsiasi eccezione all'interno del tuo metodo segnerà il TX circostante come solo roll-back Questo vale anche per le transazioni di sola lettura?
Marko Vranjkovic

1
@lolotron @Ean Posso confermare che verrà effettivamente applicato a una transazione di sola lettura. Il mio metodo stava generando EmptyResultDataAccessExceptionun'eccezione su una transazione di sola lettura e ho ricevuto lo stesso errore. Modifica la mia annotazione per @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)risolvere il problema.
cbmeeks

5
Questa risposta è sbagliata. Spring conosce solo le eccezioni che passano attraverso @Transactionalproxy wrapper, cioè non rilevate . Vedi l'altra risposta da Vojtěch per la storia completa. Potrebbero essere presenti @Transactionalmetodi annidati che possono contrassegnare la transazione solo come rollback.
Yaroslav Stavnichiy

68

Finalmente ho capito il problema:

methodA() {
    methodB()
}

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

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

Quello che succede è che anche se methodBha l'annotazione giusta, methodCnon lo fa. Quando viene generata l'eccezione, la seconda @Transactionalcontrassegna comunque la prima transazione solo come Rollback.


5
Lo stato della transazione è memorizzato in una variabile locale del thread. Quando la molla intercetta il metodo C e imposta il flag come rollback, la transazione è già contrassegnata per il rollback. Qualsiasi ulteriore soppressione dell'eccezione non aiuterà perché quando si verifica il commit finale, riceverai l'errore
vive il

@ Vojtěch In qualche modo ipoteticamente se methodC ha propagation=requires_newallora methodB non effettuerà il rollback?
deFreitas

4
methodCdeve essere in un bean / servizio Spring diverso o in qualche modo accessibile tramite il proxy Spring. Altrimenti Spring non avrà la possibilità di sapere della tua eccezione. L'unica eccezione che passa attraverso l' @Transactionalannotazione può contrassegnare la transazione come solo rollback.
Yaroslav Stavnichiy

43

Per recuperare rapidamente l'eccezione che causa senza la necessità di ricodificare o ricostruire , impostare un punto di interruzione su

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

e sali nella pila, di solito da qualche Interceptor. Lì puoi leggere l'eccezione che causa da qualche blocco catch.


6
In Hibernate 4.3.11, èorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe

Molto bene amico mio!
Rafael Andrade

Grazie! Nelle versioni più recenti di Hibernate (5.4.17) la classe è org.hibernate.engine.transaction.internal.TransactionImple il metodo è setRollbackOnly.
Peter Catalin il

11

Ho lottato con questa eccezione durante l'esecuzione della mia applicazione.

Infine il problema riguardava la query sql . voglio dire che la query è sbagliata.

verifica la tua richiesta. Questo è il mio suggerimento


1
Per chiarire: se 1. hai un errore nella sintassi sql 2. sei impostato per il rollback sull'eccezione 3. hai transazioni readOnly riceverai questo errore perché la sintassi sql causa un'eccezione che attiva un rollback che non riesce perché sei in " readonly ".
Dave

7

Cerca le eccezioni che vengono lanciate e catturate nelle ...sezioni del tuo codice. Le eccezioni dell'applicazione di runtime e di rollback causano il rollback quando vengono espulse da un metodo aziendale anche se catturate da qualche altra parte.

È possibile utilizzare il contesto per scoprire se la transazione è contrassegnata per il rollback.

@Resource
private SessionContext context;

context.getRollbackOnly();

1
Mi sembra di aver trovato la causa, ma non capisco perché stia succedendo. Un metodo interno genera un'eccezione, che prendo, registro e ignoro. Tuttavia, la transazione è contrassegnata solo come rollback. Come posso prevenirlo? Non voglio che le transazioni siano influenzate da eccezioni che ho rilevato correttamente.
Vojtěch

È SessionContextuna lezione standard in primavera? Mi sembra che sia piuttosto EJB3 e non è contenuto nella mia applicazione Spring.
Vojtěch

3
Colpa mia, mi è mancato il fatto che si tratti di primavera. Comunque dovrebbe esserci qualcosa di simile TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()disponibile.
Mareen

2

Ho trovato una buona spiegazione con le soluzioni: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) rimuovere @Transacional dal metodo annidato se non richiede realmente il controllo delle transazioni. Quindi anche se ha un'eccezione, si limita a bolle e non influisce sulle cose transazionali.

O:

2) se il metodo annidato necessita del controllo delle transazioni, impostalo come REQUIRE_NEW per la politica di propagazione in questo modo anche se genera un'eccezione e contrassegnato solo come rollback, il chiamante non verrà influenzato.


1

disabilita il transactionmanager nel tuo file 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>

commentate queste righe e vedrete l'eccezione che causa il rollback;)


0

applica il codice seguente in productRepository

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

mentre nel test junit applicare sotto il codice

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

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

}

funziona bene per il mio codice

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.