UnexpectedRollbackException: транзакция отменена, поскольку она была отмечена как откатная - программирование
Подтвердить что ты не робот

UnexpectedRollbackException: транзакция отменена, поскольку она была отмечена как откатная

У меня такой сценарий:

  • выборка (чтение и удаление) записи из таблицы IncomingMessage
  • чтение содержимого записи
  • вставить что-то в некоторые таблицы
  • если ошибка (любое исключение) возникла на этапах 1-3, вставьте запись об ошибке в таблицу OutgoingMessage
  • в противном случае вставьте успешную запись в таблицу OutgoingMessage

Таким образом, шаги 1,2,3,4 должны быть в транзакции или шаги 1,2,3,5

Мой процесс начинается здесь (это запланированная задача):

public class ReceiveMessagesJob implements ScheduledJob {
// ...
    @Override
    public void run() {
        try {
            processMessageMediator.processNextRegistrationMessage();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
// ...
}

Моя основная функция (processNextRegistrationMessage) в ProcessMessageMediator:

public class ProcessMessageMediatorImpl implements ProcessMessageMediator {
// ...
    @Override
    @Transactional
    public void processNextRegistrationMessage() throws ProcessIncomingMessageException {
        String refrenceId = null;
        MessageTypeEnum registrationMessageType = MessageTypeEnum.REGISTRATION;
        try {
            String messageContent = incomingMessageService.fetchNextMessageContent(registrationMessageType);
            if (messageContent == null) {
                return;
            }
            IncomingXmlModel incomingXmlModel = incomingXmlDeserializer.fromXml(messageContent);
            refrenceId = incomingXmlModel.getRefrenceId();
            if (!StringUtil.hasText(refrenceId)) {
                throw new ProcessIncomingMessageException(
                        "Can not proceed processing incoming-message. refrence-code field is null.");
            }
            sqlCommandHandlerService.persist(incomingXmlModel);
        } catch (Exception e) {
            if (e instanceof ProcessIncomingMessageException) {
                throw (ProcessIncomingMessageException) e;
            }
            e.printStackTrace();
            // send error outgoing-message
            OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId,
                    ProcessResultStateEnum.FAILED.getCode(), e.getMessage());
            saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
            return;
        }
        // send success outgoing-message
        OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId, ProcessResultStateEnum.SUCCEED.getCode());
        saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
    }

    private void saveOutgoingMessage(OutgoingXmlModel outgoingXmlModel, MessageTypeEnum messageType)
            throws ProcessIncomingMessageException {
        String xml = outgoingXmlSerializer.toXml(outgoingXmlModel, messageType);
        OutgoingMessageEntity entity = new OutgoingMessageEntity(messageType.getCode(), new Date());
        try {
            outgoingMessageService.save(entity, xml);
        } catch (SaveOutgoingMessageException e) {
            throw new ProcessIncomingMessageException("Can not proceed processing incoming-message.", e);
        }
    }
// ...
}

Как я сказал Если какое-либо исключение произошло на этапах 1-3, я хочу вставить запись об ошибке:

catch (Exception e) {
    if (e instanceof ProcessIncomingMessageException) {
        throw (ProcessIncomingMessageException) e;
    }
    e.printStackTrace();
    //send error outgoing-message
    OutgoingXmlModel outgoingXmlModel = new OutgoingXmlModel(refrenceId,ProcessResultStateEnum.FAILED.getCode(), e.getMessage());
    saveOutgoingMessage(outgoingXmlModel, registrationMessageType);
    return;
}

Метод SqlCommandHandlerServiceImpl.persist():

public class SqlCommandHandlerServiceImpl implements SqlCommandHandlerService {
// ...
    @Override
    @Transactional
    public void persist(IncomingXmlModel incomingXmlModel) {
        Collections.sort(incomingXmlModel.getTables());
        List<ParametricQuery> queries = generateSqlQueries(incomingXmlModel.getTables());
        for (ParametricQuery query : queries) {
            queryExecuter.executeQuery(query);
        }
    }
// ...
}

Но когда sqlCommandHandlerService.persist() генерирует исключение (здесь исключение org.hibernate.exception.ConstraintViolationException), после внесения записи об ошибке в таблицу OutgoingMessage, когда транзакция хочет быть зафиксирована, Я получаю UnexpectedRollbackException. Я не могу понять, где моя проблема:

Exception in thread "null#0" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:394)
    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 ir.tamin.branch.insuranceregistration.services.schedular.ReceiveMessagesJob$$EnhancerByCGLIB$$63524c6b.run(<generated>)
    at ir.asta.wise.core.util.timer.JobScheduler$ScheduledJobThread.run(JobScheduler.java:132)

Я использую hibernate-4.1.0-Final, моя база данных оракула, и вот мой менеджер транзакций bean:

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager"
    proxy-target-class="true" />

Спасибо заранее.

4b9b3361

Ответ 1

Это нормальное поведение, и причина в том, что вашему методу sqlCommandHandlerService.persist требуется TX при выполнении (потому что он помечен аннотацией @Transactional). Но когда он вызывается внутри processNextRegistrationMessage, поскольку имеется доступный TX, контейнер не создает новый и использует существующий TX. Поэтому, если в методе sqlCommandHandlerService.persist возникает какое-либо исключение, это приводит к тому, что для TX устанавливается значение rollBackOnly (даже если вы перехватываете исключение в вызывающей программе и игнорируете его).

Чтобы преодолеть это, вы можете использовать уровни распространения транзакций. Просмотрите этот, чтобы узнать, какое распространение лучше всего соответствует вашим требованиям.

Обновить; Прочитайте это!

Когда коллега пришел ко мне с парой вопросов о похожей ситуации, я чувствую, что это нужно немного прояснить.
Хотя пропаганда решает такие проблемы, вы должны быть ОЧЕНЬ осторожны с их использованием и не использовать их, если вы АБСОЛЮТНО не понимаете, что они означают и как они работают. Вы можете в конечном итоге сохранить некоторые данные и откатить некоторые другие, если вы не ожидаете, что они будут работать таким образом, и все может пойти ужасно неправильно.


РЕДАКТИРОВАТЬ Ссылкана текущую версию документации

Ответ 2

Ответ шиама был верным. Я уже сталкивался с этой проблемой раньше. Это не проблема, это особенность ВЕСНЫ. "Откат транзакции, поскольку он был помечен как только для отката", является приемлемым.

Заключение

  • ИСПОЛЬЗУЙТЕ REQUIRES_NEW, если вы хотите зафиксировать то, что вы делали до исключения (локальная фиксация)
  • ИСПОЛЬЗОВАТЬ ТРЕБУЕТСЯ, если вы хотите зафиксировать только тогда, когда все процессы завершены (Глобальная фиксация) И вам просто нужно игнорировать исключение "Откат транзакции, потому что он был помечен как исключение только для отката". Но вам нужно попытаться отследить вызывающую процедуру processNextRegistrationMessage(), чтобы получить журнал значений.

Позвольте мне объяснить более подробно:

Вопрос: сколько транзакций у нас есть? Anser: только один

Поскольку вы конфигурируете PROPAGATION, PROPAGATION_REQUIRED, так что @Transaction persist() использует ту же транзакцию с caller-processNextRegistrationMessage(). На самом деле, когда мы получаем исключение, Spring установит rollBackOnly для TransactionManager, поэтому Spring откатит только одну транзакцию.

Вопрос: Но у нас есть try-catch outside(), почему это исключение происходит?  Ответ из-за уникальной транзакции

  1. Когда метод persist() имеет исключение
  2. Перейти к улову на улице

    Spring will set the rollBaclOnly to true -> it determine we must 
    rollback the caller (processNextRegistrationMessage) also.
    
  3. Функция persist() сначала откатится сама.

  4. Создайте исключение UnexpectedRollbackException, чтобы сообщить, что нам также нужно откатить вызывающую сторону.
  5. Try-catch в run() перехватит UnexpectedRollbackException и напечатает трассировку стека

Вопрос: Почему мы меняем PROPAGATION на REQUIRES_NEW, это работает?

Ответ: потому что теперь processNextRegistrationMessage() и persist() находятся в разных транзакциях, поэтому они только откатывают свою транзакцию.

Спасибо