2013-04-02 30 views
17

Ecco il mio problema:Strano comportamento con @Transactional (propagazione = Propagation.REQUIRES_NEW)

Sono in esecuzione di un lotto su un'applicazione Java EE/Primavera/Hibernate. Questo batch chiama uno method1. Questo metodo chiama uno method2 che può generare UserException (una classe che si estende RuntimeException). Ecco come sembra:

@Transactional 
public class BatchService implements IBatchService { 
@Transactional(propagation=Propagation.REQUIRES_NEW) 
public User method2(User user) { 
    // Processing, which can throw a RuntimeException 
} 

public void method1() { 
    // ... 
    try { 
    this.method2(user); 
    } catch (UserException e) { 
    // ... 
    } 
    // ... 
} 
} 

L'eccezione viene catturato come l'esecuzione continua, ma alla fine del method1 quando la transazione è chiusa una RollbackException è gettato.

Ecco la traccia dello stack:

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:476) 
    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.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) 
at $Proxy128.method1(Unknown Source) 
at batch.BatchController.method1(BatchController.java:202) 

Quando method2 non sta gettando questa eccezione, funziona bene.

Quello che ho cercato:

  • Impostazione @Transactional(noRollbackFor={UserException.class})) su method1
  • cercare di prendere in method2

Ma non ha cambiato nulla.

Poiché l'eccezione viene generata in una transazione diversa in cui si è verificato un rollback, non capisco perché non funzioni. Ho dato un'occhiata a questo: Jpa transaction javax.persistence.RollbackException: Transaction marked as rollbackOnly ma non mi è stato di grande aiuto.

Sarò molto grato se qualcuno potrebbe darmi un indizio.

Aggiornamento

Ho fatto il lavoro impostando propagation=Propagation.REQUIRES_NEW sul metodo chiamato da method2 (che è in realtà quello che sta inviando l'eccezione). Questo metodo è definito in una classe molto simile al mio BatchService. Quindi non vedo perché funzioni su questo livello e non su method2.

  • Ho impostato method2 come pubblico come l'annotazione @Transactional non viene presa in considerazione se il metodo è privato, come ha detto nella documentazione:

L'annotazione @Transactional può essere posta prima di un interfaccia definizione, un metodo su un'interfaccia, una definizione di classe o un metodo pubblico su una classe.

  • Ho anche provato ad usare Exception invece di RuntimeException (come è più appropriato) ma anche non ha cambiato nulla.

Anche se funziona la domanda rimane aperta in quanto ha uno strano comportamento e mi piacerebbe capire perché non si comporta come dovrebbe essere.

+0

Vedere http://stackoverflow.com/questions/5152686/self-injection-with-spring/ per possibili soluzioni alternative. – Vadzim

risposta

35

Le transazioni Spring, per impostazione predefinita, funzionano avvolgendo il bean Spring con un proxy che gestisce la transazione e le eccezioni. Quando chiami method1() da , stai bypassando completamente questo proxy, quindi non può iniziare una nuova transazione e stai chiamando efficacemente method2() dalla stessa transazione di quella aperta con la chiamata a method1().

Al contrario, quando si chiama un metodo di un altro bean immesso da method1(), si sta infatti chiamando un metodo su un proxy transazionale. Quindi, se questo metodo alieno è contrassegnato con REQUIRES_NEW, una nuova transazione viene avviata dal proxy e sei in grado di intercettare l'eccezione in method1() e riprendere la transazione esterna.

Questo è descritto in the documentation.

+0

Risposta perfetta con una reale comprensione di ciò che sta accadendo, grazie! – DessDess

+2

Nota che se hai bisogno di queste invocazioni automatiche per lavorare, dovresti usare la modalità AspectJ in primavera. Vedi http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html: "Considera l'uso della modalità AspectJ (vedi l'attributo mode nella tabella seguente) se prevedi l'auto-invocazione in questo caso, non ci sarà un proxy in primo luogo, ma la classe di destinazione verrà tessuta (cioè il suo codice byte verrà modificato) per trasformare @Transactional in runtime comportamento su qualsiasi tipo di metodo. " –

+0

sì, vera comprensione della transazione di primavera ... grazie – Vito