Транзакция помечена только как откат: как узнать причину

95

У меня возникли проблемы с совершением транзакции в моем методе @Transactional:

methodA() {
    methodB()
}

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

Когда я вызываю methodB () из methodA (), метод проходит успешно, и я вижу «ОК» в моих журналах. Но потом я получаю

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()?этого - я мог бы пройти через метод и найти причину.
Войтех
источник
связанные: stackoverflow.com/questions/33277563/…
Габ
связанные: stackoverflow.com/q/25322658/697313
Ярослав
Интересно отметить, что если ваша таблица базы данных не существует, иногда эта ошибка также будет отображаться.
Нг Сек Лонг

Ответы:

101

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

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Ean V
источник
6
Ну попробовал использовать noRollbackFor=Exception.class, но вроде не действует - для унаследованных исключений работает?
Vojtěch
6
Да. Глядя на ваш собственный ответ, это верно (вы не предоставили его methodCв своем первом посте). Оба methodBи methodCиспользовать тот же TX и всегда наиболее специфичные @Transactionalиспользуются аннотации, поэтому , когда methodCбросает исключение, окружая TX будет отмечен как откат только. Вы также можете использовать разные маркеры распространения, чтобы предотвратить это.
Ean V
1
@lolotron @Ean Я могу подтвердить, что это действительно применимо к транзакции только для чтения. Мой метод генерировал EmptyResultDataAccessExceptionисключение для транзакции только для чтения, и я получил ту же ошибку. Изменение моей аннотации для @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)устранения проблемы.
cbmeeks
5
Это неверный ответ. Spring знает только об исключениях, которые проходят через @Transactionalоболочку прокси, то есть не перехвачены . Смотрите другой ответ Войтеха, чтобы узнать больше. Могут существовать вложенные @Transactionalметоды, которые могут пометить только откат вашей транзакции.
Ярослав
1
noRollbackForработает только еслиglobalRollbackOnParticipationFailure=false
amir110
69

Я наконец понял проблему:

methodA() {
    methodB()
}

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

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

Что происходит, так это то, что даже если у объекта methodBесть правильная аннотация, у methodCнего нет. Когда генерируется исключение, вторая @Transactionalв любом случае отмечает только первую транзакцию как откат.

Войтех
источник
5
Статус транзакции хранится в локальной переменной потока. Когда пружина перехватывает methodC и устанавливает флаг отката, ваша транзакция уже помечена для отката. Любое дальнейшее подавление исключения не поможет, потому что, когда произойдет финальная фиксация, вы получите сообщение об ошибке
живет
@ Vojtěch В любом случае, гипотетически, если у methodC есть, propagation=requires_newто methodB не откатится ?
deFreitas
4
methodCдолжен находиться в другом компоненте / службе Spring или каким-либо образом доступен через прокси-сервер Spring. В противном случае Spring не сможет узнать о вашем исключении. Только исключение, которое проходит через @Transactionalаннотацию, может пометить транзакцию как откатную.
Ярослав
Это не решение. Речь идет только о непонимании и неправильном использовании механизма AOP Spring Transaction. Аннотацию транзакции следует использовать только в том случае, если вы уверены, что вам нужен отдельный контекст транзакции или распространение для действий, которые вы применяете в этом месте. В любом другом случае вы можете просто правильно настроить распространение транзакции. -1
Никита Челомбитько
43

Чтобы быстро получить вызывающее исключение без необходимости перекодировать или перестраивать , установите точку останова на

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

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

FelixJongleur42
источник
6
В Hibernate 4.3.11 этоorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe
Очень мило, друг мой!
Рафаэль Андраде,
Благодарность! В более новых версиях Hibernate (5.4.17) класс org.hibernate.engine.transaction.internal.TransactionImplи метод есть setRollbackOnly.
Питер Каталин
11

Я боролся с этим исключением при запуске своего приложения.

Наконец, проблема была в запросе sql . я имею в виду, что запрос неверен.

пожалуйста, подтвердите свой запрос. Это мое предложение

Кумаресан Перумал
источник
1
Чтобы уточнить: если у вас 1. есть ошибка в синтаксисе sql 2. настроен откат при исключении 3. есть транзакции только для чтения, вы получите эту ошибку, потому что синтаксис sql вызывает исключение, которое запускает откат, который не выполняется, потому что вы находитесь в " "только для чтения".
Дэйв
7

Ищите исключения, которые выбрасываются и попадают в ... разделах вашего кода. Исключения времени выполнения и отката приложений вызывают откат, когда они выбрасываются из бизнес-метода, даже если они обнаружены в другом месте.

Вы можете использовать контекст, чтобы узнать, помечена ли транзакция для отката.

@Resource
private SessionContext context;

context.getRollbackOnly();
Марин
источник
1
Мне кажется, я нашел причину, но не понимаю, почему это происходит. Внутренний метод генерирует исключение, которое я перехватываю, регистрирую и игнорирую. Но Транзакция все равно помечена как Откат. Как я могу это предотвратить? Я не хочу, чтобы на транзакции влияли исключения, которые я правильно улавливаю.
Vojtěch
Является ли SessionContextстандартный класс весной? Мне кажется, что это скорее EJB3, и его нет в моем приложении Spring.
Vojtěch
3
Плохо, что я упустил тот факт, что речь идет о весне. В любом случае должно быть что-то вроде TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()доступного.
Mareen
3

Нашел хорошее объяснение с решениями: 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);

}

он отлично работает для моего кода

Асиф Раза
источник