Транзакція, позначена лише як відкат: Як знайти причину


93

У мене виникають проблеми із здійсненням транзакції в рамках мого методу @Transactional:

methodA() {
    methodB()
}

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

Коли я викликаю methodB () з methodA (), метод успішно проходить, і я бачу "OK" у своїх журналах. Але тоді я отримую

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. Контекст methodB повністю відсутній у винятку - що гаразд, я гадаю?
  2. Щось у методіB () позначило транзакцію лише як відкат? Як я можу це дізнатись? Чи є, наприклад, спосіб перевірити щось на кшталт getCurrentTransaction().isRollbackOnly()?- ось так, я міг би перейти через метод і знайти причину.



Цікаві речі, на які слід звернути увагу, якщо ваша таблиця бази даних не існує, колись ця помилка також буде показана.
Нг Сек Лонг

Відповіді:


101

Коли ви позначаєте свій метод як @Transactional, виникнення будь-якого винятку всередині вашого методу буде позначати навколишню передачу лише як відкат (навіть якщо ви їх зловите). Ви можете використовувати інші атрибути @Transactionalанотації, щоб запобігти її відкоту, наприклад:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)

6
Ну, я намагався використовувати noRollbackFor=Exception.class, але, здається, це не має ефекту - чи працює це для успадкованих винятків?
Войтех

6
Так. Дивлячись на власну відповідь, це правильно (ви не вказали methodCу своєму першому дописі). Обидва methodBі methodCвикористовують один і той же TX, і завжди використовується найбільш конкретна @Transactionalанотація, тому, коли видається methodCвиняток, оточуючий TX буде позначений як лише відкат. Ви також можете використовувати різні маркери розповсюдження, щоб запобігти цьому.
Ean V

@Ean будь-який виняток усередині вашого методу буде позначати навколишній TX лише як відкат. Чи стосується це також для транзакцій readOnly?
Marko Vranjkovic

1
@lolotron @Ean Я можу підтвердити, що це дійсно стосуватиметься транзакції лише для читання. Мій метод кидав EmptyResultDataAccessExceptionвиняток для транзакції лише для читання, і я отримав ту ж помилку. Змінивши анотацію на @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)виправлену проблему.
cbmeeks

5
Ця відповідь неправильна. Spring знає лише про винятки, які проходять через @Transactionalпроксі-обгортку, тобто не спіймані . Дивіться іншу відповідь від Vojtěch для повного оповідання. Можуть бути вкладені @Transactionalметоди, які можуть позначити лише відкат вашої транзакції.
Ярослав Ставнічий

68

Я нарешті зрозумів проблему:

methodA() {
    methodB()
}

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

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

Що трапляється, так це те, що, хоча у них methodBє правильна анотація, у них methodCнемає. Коли виникає виняток, другий @Transactionalу будь-якому випадку позначає першу транзакцію як відкат.


5
Статус транзакції зберігається в локальній змінній потоку. Коли пружина перехоплює методC і встановлює прапор як відкат, ваша транзакція вже позначена як відкат. Будь-яке подальше придушення винятку не допоможе, оскільки коли відбудеться остаточне фіксація, ви отримаєте помилку
живе

@ Vojtěch Будь-який спосіб гіпотетично, якщо методC має, propagation=requires_newто методB не відмовиться?
deFreitas

4
methodCповинен знаходитись в іншому компоненті Spring / службі або якось отримувати доступ через проксі-сервер Spring. В іншому випадку Весна не матиме можливості дізнатися про ваш виняток. Тільки виняток, який проходить через @Transactionalанотацію, може позначити транзакцію лише як відкат.
Ярослав Ставнічий

43

Щоб швидко отримати виняток, що викликає, без необхідності перекодувати чи перебудовувати , встановіть точку зупинки

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

і підніміться в стопку, зазвичай до якогось перехоплювача. Там ви можете прочитати викликаючий виняток з якогось блоку catch.


6
У Hibernate 4.3.11 цеorg.hibernate.jpa.internal.TransactionImpl
Вім Деблауве

Дуже мило, друже!
Рафаель Андраде

Дякую! У новіших версіях Hibernate (5.4.17) клас є org.hibernate.engine.transaction.internal.TransactionImplі метод є setRollbackOnly.
Пітер Каталін

11

Я боровся з цим винятком під час запуску програми.

Нарешті, проблема була в запиті sql . я маю на увазі, що запит помилковий.

будь ласка, підтвердьте свій запит. Це моя пропозиція


1
Для уточнення: якщо у вас 1. є помилка у вашому синтаксисі sql 2. ви налаштовані на відкат за винятком 3. маєте readOnly транзакції, ви отримаєте цю помилку, оскільки синтаксис sql викликає виняток, який викликає відкат, який не вдається, оскільки ви перебуваєте в " режим "лише для читання".
Дейв

7

Шукайте винятки, які викидають і потрапляють у ...розділи вашого коду. Винятки виконання та відкочування додатків спричиняють відкат, коли їх викидають із ділового методу, навіть якщо вони потрапляють в інше місце.

За допомогою контексту можна з’ясувати, чи позначено транзакцію для відкоту.

@Resource
private SessionContext context;

context.getRollbackOnly();

1
Мені здається, я знайшов причину, але я не розумію, чому це відбувається. Внутрішній метод видає виняток, який я ловлю, реєструю та ігнорую. Але Транзакція в будь-якому випадку позначена лише як відкат. Як я можу це запобігти? Я не хочу, щоб на транзакції впливали винятки, які я належним чином вловлюю.
Войтех

Це SessionContextстандартний клас навесні? Мені здається, це скоріше EJB3, і він не міститься у моїй Весняній заявці.
Войтех

3
Шкода, я пропустив той факт, що мова йде про Весну. У будь-якому випадку, там повинно бути щось на зразок TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()доступного.
Mareen

2

Знайшов гарне пояснення з рішеннями: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) видаліть @Transacional із вкладеного методу, якщо він насправді не вимагає контролю транзакцій. Тож навіть у нього є виняток, він просто з’являється і не впливає на транзакційні речі.

АБО:

2) якщо вкладений метод потребує керування транзакціями, зробіть його REQUIRE_NEW для політики розповсюдження таким чином, навіть якщо видасть виняток і позначений як відкат, це не вплине на абонента.


1

вимкніть менеджер транзакцій у вашому 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>

прокоментуйте ці рядки, і ви побачите виняток, що спричиняє відкат;)


0

застосувати наведений нижче код у productRepository

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

в той час як в тесті junit застосовуйте код нижче

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

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

}

це добре працює для мого коду

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.