Transakcja oznaczona jako tylko wycofanie: Jak znaleźć przyczynę

94

Mam problemy z zatwierdzeniem transakcji w ramach mojej metody @Transactional:

methodA() {
    methodB()
}

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

Kiedy wywołuję metodę MethodB () z metody MethodA (), metoda kończy się pomyślnie i w moich dziennikach jest wyświetlany komunikat „OK”. Ale wtedy rozumiem

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. W wyjątku całkowicie brakuje kontekstu methodB - co, jak sądzę, jest w porządku?
  2. Coś w metodzie MethodB () oznaczyło transakcję jako tylko wycofanie? Jak mogę się tego dowiedzieć? Czy jest na przykład sposób, aby sprawdzić coś takiego getCurrentTransaction().isRollbackOnly()?- w ten sposób mógłbym przejść przez metodę i znaleźć przyczynę.
Vojtěch
źródło
related: stackoverflow.com/q/25322658/697313
Yaroslav Stavnichiy
Warto zwrócić uwagę na to, że jeśli tabela bazy danych nie istnieje, czasami ten błąd również zostanie wyświetlony.
Ng Sek Long

Odpowiedzi:

101

Kiedy oznaczysz swoją metodę jako @Transactional, wystąpienie jakiegokolwiek wyjątku w metodzie spowoduje oznaczenie otaczającego TX jako tylko wycofania (nawet jeśli je złapiesz). Możesz użyć innych atrybutów @Transactionaladnotacji, aby zapobiec jej wycofywaniu, na przykład:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Ean V
źródło
6
Cóż, próbowałem użyć noRollbackFor=Exception.class, ale wydaje się, że nie ma to żadnego efektu - czy działa w przypadku odziedziczonych wyjątków?
Vojtěch
6
Tak. Patrząc na własną odpowiedź, to prawda (nie podałeś jej methodCw swoim pierwszym poście). Oba methodBi methodCużywają tego samego TX i zawsze @Transactionalużywana jest najbardziej szczegółowa adnotacja, więc gdy methodCzgłasza wyjątek, otaczający TX zostanie oznaczony jako tylko wycofanie. Aby temu zapobiec, możesz również użyć różnych markerów propagacji.
Ean V
1
@lolotron @Ean Mogę potwierdzić, że rzeczywiście będzie to dotyczyło transakcji tylko do odczytu. Moja metoda rzucała EmptyResultDataAccessExceptionwyjątek w transakcji tylko do odczytu i otrzymałem ten sam błąd. Zmieniam adnotację, aby @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)rozwiązać problem.
cbmeeks
5
Ta odpowiedź jest błędna. Wiosna wie tylko o wyjątkach, które przechodzą przez @Transactionalopakowanie proxy, czyli nie są przechwytywane . Zobacz inną odpowiedź od Vojtěcha, aby poznać całą historię. Mogą istnieć zagnieżdżone @Transactionalmetody, które mogą oznaczać tylko wycofanie transakcji.
Yaroslav Stavnichiy 17-17
1
noRollbackFordziała tylko wtedy, gdyglobalRollbackOnParticipationFailure=false
amir110
69

W końcu zrozumiałem problem:

methodA() {
    methodB()
}

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

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

Dzieje się tak, że nawet jeśli methodBma właściwą adnotację, methodCnie. Gdy wyjątek zostanie zgłoszony, druga @Transactionaltransakcja i tak oznacza pierwszą transakcję jako tylko wycofanie.

Vojtěch
źródło
5
Status transakcji jest przechowywany w zmiennej lokalnej wątku. Kiedy sprężyna przechwytuje metodę C i ustawia flagę jako wycofanie, transakcja jest już oznaczona do wycofania. Jakiekolwiek dalsze tłumienie wyjątku nie pomoże, ponieważ kiedy nastąpi ostateczne zatwierdzenie, otrzymasz błąd
żyje
@ Vojtěch W jakiś hipotetyczny sposób, jeśli methodC ma, propagation=requires_newto methodB nie wycofa się?
deFreitas
4
methodCmusi znajdować się w innym elemencie / usłudze Spring lub w jakiś sposób uzyskać do niego dostęp za pośrednictwem serwera proxy Spring. W przeciwnym razie Spring nie będzie mieć możliwości dowiedzenia się o Twoim wyjątku. Jedyny wyjątek, który przechodzi przez @Transactionaladnotację, może oznaczać transakcję jako tylko do wycofania.
Yaroslav Stavnichiy 17-17
To nie jest rozwiązanie. Chodzi tylko o nieporozumienie i niewłaściwe użycie mechanizmu AOP Spring Transaction. Powinieneś używać adnotacji transakcyjnej tylko wtedy, gdy masz pewność, że potrzebujesz osobnego kontekstu transakcji lub propagacji dla działań, które wykonujesz w tym miejscu. W każdym innym przypadku możesz po prostu poprawnie skonfigurować Propagację transakcji. -1
Mykyta Chelombitko
43

Aby szybko pobrać powodujący wyjątek bez konieczności ponownego kodowania lub przebudowywania , ustaw punkt przerwania na

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

i wejdź na stos, zwykle do jakiegoś Interceptora. Tam możesz przeczytać powodujący wyjątek z jakiegoś bloku catch.

FelixJongleur42
źródło
6
W Hibernate 4.3.11 jest toorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe,
Bardzo ładnie mój przyjacielu!
Rafael Andrade
Dzięki! W nowszych wersjach Hibernate (5.4.17) klasa jest, org.hibernate.engine.transaction.internal.TransactionImpla metoda to setRollbackOnly.
Peter Catalin
11

Miałem problem z tym wyjątkiem podczas uruchamiania aplikacji.

Wreszcie problem dotyczył kwerendy sql . mam na myśli, że zapytanie jest błędne.

proszę zweryfikuj swoje zapytanie. To jest moja sugestia

Kumaresan Perumal
źródło
1
Aby wyjaśnić: jeśli 1. masz błąd w składni sql 2. ustawiono wycofywanie zmian w przypadku wyjątku 3. masz transakcje tylko do odczytu, otrzymasz ten błąd, ponieważ składnia sql powoduje wyjątek, który wyzwala wycofywanie zmian, które kończy się niepowodzeniem, ponieważ jesteś w " tylko do odczytu ”.
Dave
7

Poszukaj wyjątków rzucanych i przechwytywanych w ... sekcjach kodu. Wyjątki aplikacji w czasie wykonywania i wycofywania zmian powodują wycofanie, gdy zostaną wyrzucone z metody biznesowej, nawet jeśli zostaną przechwycone w innym miejscu.

Możesz użyć kontekstu, aby dowiedzieć się, czy transakcja jest oznaczona do wycofania.

@Resource
private SessionContext context;

context.getRollbackOnly();
Mareen
źródło
1
Wydaje mi się, że znalazłem przyczynę, ale nie rozumiem, dlaczego tak się dzieje. Metoda wewnętrzna zgłasza wyjątek, który łapię, rejestruję i ignoruję. Ale transakcja jest i tak oznaczona jako tylko wycofanie. Jak mogę temu zapobiec? Nie chcę, aby na transakcje miały wpływ wyjątki, które prawidłowo wychwytuję.
Vojtěch
Czy SessionContextna wiosnę są standardowe zajęcia? Wydaje mi się, że jest to raczej EJB3 i nie ma go w mojej aplikacji wiosennej.
Vojtěch
3
Moja wina, że ​​przegapiłem fakt, że chodzi o wiosnę. Zresztą coś takiego powinno być TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()dostępne.
Mareen
2

Znalazłem dobre wyjaśnienie dzięki rozwiązaniom: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) usuń @Transacional z metody zagnieżdżonej, jeśli tak naprawdę nie wymaga kontroli transakcji. Więc nawet on ma wyjątek, po prostu bulgocze i nie wpływa na transakcje.

LUB:

2) jeśli metoda zagnieżdżona wymaga kontroli transakcji, ustaw ją jako REQUIRE_NEW dla zasad propagacji w ten sposób, nawet jeśli zgłosi wyjątek i zostanie oznaczona jako tylko wycofywanie, nie wpłynie to na obiekt wywołujący.

aquajach
źródło
1

wyłącz menedżera transakcji w pliku 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>

zakomentuj te linie, a zobaczysz wyjątek powodujący wycofanie;)

rémy
źródło
0

zastosuj poniższy kod w productRepository

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

podczas gdy w teście junit zastosuj poniższy kod

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

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

}

działa dobrze dla mojego kodu

Asif Raza
źródło