web-dev-qa-db-ja.com

UnexpectedRollbackException:ロールバックのみとしてマークされているため、トランザクションはロールバックされました

私はこのシナリオを持っています:

  1. IncomingMessageテーブルからレコードを取得(読み取りおよび削除)します
  2. 記録内容を読む
  3. いくつかのテーブルに何かを挿入する
  4. 手順1〜3でエラー(例外)が発生した場合、エラーレコードをOutgoingMessageテーブルに挿入します。
  5. それ以外の場合、成功レコードを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();
        }
    }
// ...
}

ProcessMessageMediatorのメイン関数(processNextRegistrationMessage):

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を使用しています。データベースはOracleであり、トランザクションマネージャ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" />

前もって感謝します。

32
Jaqen H'ghar

Shyamの答えは正しかった。私はすでにこの問題に直面していました。これは問題ではなく、スプリング機能です。 「トランザクションはロールバック専用としてマークされているため、ロールバックされました」は受け入れ可能です。

結論

  • 例外の前に何をしたかをコミットする場合は、REQUIRES_NEWを使用します(ローカルコミット)
  • すべてのプロセスが完了したときにのみコミットする場合は、REQUIREDを使用します(グローバルコミット)。「ロールバックのみとしてマークされているため、ロールバックされたトランザクション」例外を無視する必要があります。ただし、意味のあるログを取得するには、呼び出し元のprocessNextRegistrationMessage()の外側でキャッチする必要があります。

詳細を説明しましょう。

質問:トランザクションはいくつありますか?Anser:1つのみ

PROPAGATIONはPROPAGATION_REQUIREDに設定されているため、@ Transaction persist()はcaller-processNextRegistrationMessage()で同じトランザクションを使用しています。実際、例外が発生すると、SpringはTransactionManagerにrollBackOnlyを設定するため、Springは1つのトランザクションのみをロールバックします。

質問:()の外側にtry-catchがありますが、なぜこの例外が発生するのですか?一意のトランザクションのために答えてください

  1. Persist()メソッドに例外がある場合
  2. 外の漁場に行く

    Spring will set the rollBaclOnly to true -> it determine we must 
    rollback the caller (processNextRegistrationMessage) also.
    
  3. Persist()は最初にそれ自体をロールバックします。

  4. それを通知するためにUnexpectedRollbackExceptionをスローし、呼び出し元もロールバックする必要があります。
  5. Run()のtry-catchはUnexpectedRollbackExceptionをキャッチし、スタックトレースを出力します

質問:なぜPROPAGATIONをREQUIRES_NEWに変更するのですか、それは機能しますか?

Anwser:processNextRegistrationMessage()とpersist()が異なるトランザクションにあるため、トランザクションのみをロールバックするようになりました。

ありがとう

5