2013-04-02 12 views
17

Oto mój problem:Dziwne zachowanie z @Transactional (propagacja = Propagation.REQUIRES_NEW)

biegnę partię na aplikacji Java EE/wiosna/hibernacji. Ta partia wywołuje numer method1. Ta metoda wywołuje method2, która może rzucić UserException (klasa rozszerzająca RuntimeException). Oto jak to wygląda:

@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) { 
    // ... 
    } 
    // ... 
} 
} 

Wyjątkiem jest przechwycony jako realizacja trwa, ale na koniec method1 kiedy transakcja zostanie zamknięta na RollbackException jest wyrzucane.

Oto ślad stosu:

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) 

Kiedy method2 nie rzuca ten wyjątek, to działa dobrze.

Co próbowałem:

  • Ustawianie @Transactional(noRollbackFor={UserException.class})) na method1
  • spróbować połowu w method2

Ale to niczego nie zmieni.

Ponieważ wyjątek jest zgłaszany w innej transakcji, w której nastąpiło wycofanie, nie rozumiem, dlaczego nie działa. Spojrzałem na to: Jpa transaction javax.persistence.RollbackException: Transaction marked as rollbackOnly, ale tak naprawdę nie pomogło mi to.

Będę bardzo wdzięczny, jeśli ktoś da mi wskazówkę.

Aktualizacja

Zrobiłem to działa poprzez ustawienie propagation=Propagation.REQUIRES_NEW na metodzie zwanej przez method2 (co jest rzeczywiście taki, który wysyła wyjątek). Ta metoda jest zdefiniowana w klasie bardzo podobnej do mojej BatchService. Więc nie rozumiem, dlaczego działa na tym poziomie, a nie na method2.

  • mam ustawione method2 jako publiczny jako adnotacja @Transactional nie jest brany pod uwagę, jeśli metoda jest prywatny jak powiedział w dokumentacji:

@Transactional adnotacja może być umieszczony zanim interfejs definicja, metoda na interfejsie, definicja klasy lub publiczna metoda na klasie.

  • Próbowałem również użyć Exception zamiast RuntimeException (jak to jest bardziej odpowiednie), ale to też nic nie zmieni.

Nawet jeśli działa, pytanie pozostaje otwarte, ponieważ ma dziwne zachowanie i chciałbym zrozumieć, dlaczego nie zachowuje się tak, jak powinno być.

+0

Zobacz http://stackoverflow.com/questions/5152686/self-injection-with-spring/ dla możliwych obejść. – Vadzim

Odpowiedz

35

Wiosenne transakcje domyślnie działają poprzez zawijanie komponentu Spring bean za pomocą serwera proxy, który obsługuje transakcję i wyjątki. Gdy zadzwonisz pod numer method2() z method1(), całkowicie pomijasz ten serwer proxy, więc nie możesz rozpocząć nowej transakcji i skutecznie wywołujesz method2() z tej samej transakcji, która została otwarta przez połączenie z numerem method1().

W przeciwieństwie do tego, gdy wywołuje się metodę innej wstrzykniętej fasoli z method1(), w rzeczywistości wywołuje się metodę na proxy transakcyjnym. Jeśli więc ta obca metoda zostanie oznaczona jako REQUIRES_NEW, nowa transakcja zostanie uruchomiona przez serwer proxy, a Ty możesz złapać wyjątek w method1() i wznowić zewnętrzną transakcję. Jest to opisane w the documentation.

+0

Doskonała odpowiedź z prawdziwym zrozumieniem tego, co się dzieje, dziękuję! – DessDess

+2

Pamiętaj, że jeśli potrzebujesz tych invokacji do pracy, powinieneś użyć trybu AspectJ na wiosnę. Zobacz http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html: "Rozważ użycie trybu AspectJ (zobacz atrybut mode w tabeli poniżej), jeśli spodziewasz się samo-wywołania do zawijania również transakcji, w tym przypadku nie będzie proxy, zamiast tego, klasa docelowa zostanie utkana (to znaczy, kod bajtu zostanie zmodyfikowany), aby zmienić @Transactional w środowisko wykonawcze zachowanie na jakiejkolwiek metodzie. " –

+0

tak, prawdziwe zrozumienie wiosennej transakcji ... dziękuję – Vito