itsource

예기치 않은 롤백예외:트랜잭션이 롤백 전용으로 표시되어 롤백되었습니다.

mycopycode 2023. 9. 19. 21:04
반응형

예기치 않은 롤백예외:트랜잭션이 롤백 전용으로 표시되어 롤백되었습니다.

저는 다음과 같은 시나리오를 가지고 있습니다.

  1. 수신 메시지 테이블에서 레코드 가져오기(읽기 및 삭제)
  2. 레코드 내용 읽기
  3. 어떤 테이블에 무엇을 끼우다
  4. 1-3단계에서 오류(예외)가 발생한 경우 Excuting Message 테이블에 error-record를 삽입합니다.
  5. 그렇지 않으면 Excuting Message 테이블에 성공 레코드를 삽입합니다.

따라서 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;
}

Sql CommandHandler Service Impl입니다.지속 () 메서드:

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);
        }
    }
// ...
}

하지만 sql CommandHandler Service의 경우.persist () throws 예외(여기서는 org. hibernate).예외.제약 조건 위반예외), OutgoingMessage 테이블에 오류 레코드를 삽입한 후 트랜잭션을 커밋하려는 경우 예기치 않은 롤백이 표시됩니다.예외.어디가 문제인지 알 수가 없네요.

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 x.y.z.ReceiveMessagesJob$$EnhancerByCGLIB$$63524c6b.run(<generated>)
    at x.y.z.JobScheduler$ScheduledJobThread.run(JobScheduler.java:132)

hibernate-4.1.0-Final을 사용하고 있으며 데이터베이스는 Oracle이며 트랜잭션 관리자는 다음과 같습니다.

<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" />

그의 입니다.sqlCommandHandlerService.persist때때 TX다입니다)).@Transactional주석). 그러나 그것이 안으로 불려질 때라고 불릴 때.processNextRegistrationMessage사용 는 새 합니다.한 TX TX 를 TX 를합니다 TX 를합니다.한다면,sqlCommandHandlerService.persist하면 TX가드, TX다로 rollBackOnly(발신자에서 예외를 포착하고 무시하더라도)

이를 극복하기 위해 트랜잭션에 전파 수준을 사용할 수 있습니다.요구 사항에 가장 적합한 전파 방법을 알아보려면 이 항목을 살펴보십시오.

업데이트; 읽어보세요!

한 동료가 비슷한 상황에 대해 몇 가지 질문을 하고 나서, 저는 이것에 대해 좀 더 해명이 필요하다고 생각합니다.
비록 전파가 그러한 문제들을 해결하지만, 여러분은 그것들을 사용하는 것에 매우 조심해야 하고, 그것들이 의미하는 것과 그것들이 어떻게 작용하는지를 절대적으로 이해하지 않는 한 그것들을 사용하지 말아야 합니다.데이터가 그런 식으로 작동하지 않을 것으로 예상되는 일부 데이터를 유지하고 다른 데이터를 롤백하게 되어 일이 크게 잘못될 수 있습니다.


편집 현재 버전의 설명서 링크

Shyam의 대답이 옳았습니다.저는 이미 전에 이 문제에 직면했습니다.그건 문제가 아니라 스프링 기능입니다."롤백 전용으로 표시되었기 때문에 트랜잭션 롤백"을 사용할 수 있습니다.

결론

  • 예외 이전에 수행한 작업을 커밋하려면 USE REQUIRES_NEW(로컬 커밋)
  • 모든 프로세스가 완료된 경우에만 커밋하려면 Use REQUIRED(사용 필수) 그리고 "Transaction rollback(롤백 전용으로 표시되었기 때문에 트랜잭션 롤백)" 예외만 무시하면 됩니다.그러나 의미 로그를 가지려면 호출자 프로세스NextRegistrationMessage() 밖에서 시도해봐야 합니다.

좀 더 자세히 설명해 드리겠습니다.

질문:.거래가 얼마나 됩니까?답변:단 하나.

@Transaction persist()이 caller-processNextRegistrationMessage()와 동일한 트랜잭션을 사용하도록 PROPATION을 구성하기 때문에 PROPATION은 PROPATION_REQUARED입니다.실제로 예외가 발생하면 Spring에서 트랜잭션 관리자에 대해 rollBackOnly를 설정하여 Spring에서 트랜잭션 하나만 롤백합니다.

질문:.하지만 외부에서 시도해 볼 수 있는 기회가 있습니다. () 왜 이런 예외가 발생하는 것일까요?고유한 트랜잭션으로 인한 답변

  1. persist() 메서드에 예외가 있는 경우
  2. 밖에 있는 잡이로 가세요.

    Spring will set the rollBackOnly to true -> it determine we must 
    rollback the caller (processNextRegistrationMessage) also.
    
  3. persist()가 먼저 롤백합니다.

  4. 예기치 않은 롤백 던지기그 사실을 알리기 위해서는 발신자도 롤백해야 합니다.
  5. run()에서 Try-catch가 Unexpected Rollback을 잡습니다.스택 트레이스를 예외적으로 인쇄합니다.

질문:.전파를 REQUIRES_NEW로 변경하는 이유는 무엇입니까?

답변: 이제 프로세스 NextRegistrationMessage()와 persist()가 다른 트랜잭션에 있으므로 트랜잭션만 롤백하기 때문입니다.

감사해요.

언급URL : https://stackoverflow.com/questions/19349898/unexpectedrollbackexception-transaction-rolled-back-because-it-has-been-marked

반응형