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ę.



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)

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.


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.


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


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();

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.


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;)


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.