Comportement étrange avec @Transactional (propagation=Propagation.NÉCESSITE UNE NOUVELLE)

Voici mon problème :

j'exécute un lot sur une application Java EE / Spring / Hibernate. Ce lot appelle un method1. Cette méthode appelle method2 qui peut jeter UserException(une classe qui s'étend RuntimeException). Voici à quoi ça ressemble :

@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'exception est piégé que l'exécution se poursuit, mais à la fin de method1 lorsque la transaction est fermée, une RollbackException est lancée.

Voici la trace de la pile :

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)

Quand method2 n'est pas jeter cette exception, il fonctionne bien.

Ce que j'ai essayé:

  • @Transactional(noRollbackFor={UserException.class}))method1
  • Try et catch dans method2

Mais ça n'a rien changé.

comme l'exception est jetée dans une transaction différente où un retour en arrière s'est produit, Je ne comprends pas pourquoi cela ne fonctionne pas. J'ai eu un coup d'oeil à ceci : Jpa transaction javax.persistance.RollbackException: Transaction marqué comme rollbackOnly mais ça ne m'a pas vraiment aidé.

je serai très reconnaissant si quelqu'un pouvait me donner un indice.

mise à Jour

j'ai fait ce travail par le paramètre propagation=Propagation.REQUIRES_NEW sur la méthode appelée par method2 (qui est en fait celui qui envoie l'exception). Cette méthode est définie dans une classe très similaire à mon BatchService. Donc je ne vois pas pourquoi ça fonctionne sur ce et non pas sur method2.

  • j'ai mis method2 aussi publique que l'annotation @Transactional n'est pas prise en compte si la méthode est privée comme indiqué dans la documentation:

l'annotation @Transactional peut être placée devant une interface définition d'une méthode sur une interface, une définition de classe, ou d'un public méthode d'une classe.

  • j'ai aussi essayé d'utiliser Exception au lieu de RuntimeException (car il est plus le cas), mais il ne voulait pas changer quoi que ce soit.

même si elle fonctionne, la question reste ouverte car elle a un comportement étrange et je voudrais comprendre pourquoi elle n'agit pas comme elle devrait l'être.

17
demandé sur Community 2013-04-02 19:20:34

1 réponses

Printemps des transactions, par défaut, le travail en enveloppant le Printemps de haricot avec un proxy qui gère la transaction et les exceptions. Lorsque vous appelez method2()method1(), vous contournez complètement ce mandataire, donc il ne peut pas démarrer une nouvelle transaction, et vous appelez effectivement method2() dans la même transaction que celle ouverte par l'appel à method1().

au contraire, quand vous appelez une méthode d'un autre haricot injecté de method1(), vous appelez en fait une méthode sur un mandataire transactionnel. Donc si cette méthode alien est marquée avec REQUIRES_NEW, une nouvelle transaction est commencée par le proxy, et vous pouvez attraper l'exception dans method1() et reprendre la transaction externe.

ceci est décrit dans la documentation.

36
répondu JB Nizet 2013-04-02 16:38:54