web-dev-qa-db-fra.com

UnexpectedRollbackException: transaction annulée car elle a été marquée comme une restauration uniquement

J'ai ce scénario:

  1. chercher (lire et supprimer) un enregistrement de IncomingMessage table
  2. lire le contenu de l'enregistrement
  3. insérer quelque chose dans certaines tables
  4. si une erreur (une exception) se produit aux étapes 1 à 3, insérez un enregistrement d'erreur dans la table OutgoingMessage
  5. sinon, insérez un enregistrement de réussite dans la table OutgoingMessage

Ainsi, les étapes 1, 2, 3, 4 devraient être dans une transaction ou les étapes 1, 2, 3, 5

Mon processus commence à partir d'ici (c'est une tâche planifiée):

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

Ma fonction principale (processNextRegistrationMessage) dans 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);
        }
    }
// ...
}

Comme je l'ai dit, si une exception s'est produite aux étapes 1 à 3, je veux insérer un enregistrement d'erreur:

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;
}

C'est la méthode 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);
        }
    }
// ...
}

Mais quand sqlCommandHandlerService.persist () lève une exception (ici une exception org.hibernate.exception.ConstraintViolationException), après l'insertion d'un enregistrement d'erreur dans la table OutgoingMessage, lorsque la transaction doit être validée, j'obtiens UnexpectedRollbackException. . Je ne sais pas où est mon problème:

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)

J'utilise hibernate-4.1.0-Final, ma base de données est Oracle et voici mon bean de gestionnaire de transactions:

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

Merci d'avance.

32
Jaqen H'ghar

La réponse de Shyam était juste. J'ai déjà fait face à ce problème avant. Ce n'est pas un problème, c'est une fonctionnalité SPRING. "La transaction a été annulée car elle a été marquée comme étant une annulation uniquement" est acceptable.

Conclusion

  • USE REQUIRES_NEW si vous voulez commettre qu'avez-vous fait avant une exception (validation locale)
  • USE REQUIRED si vous souhaitez valider uniquement lorsque tous les processus sont terminés (validation globale). Et vous devez simplement ignorer l'exception "Transaction annulée car elle a été marquée comme étant une annulation uniquement". Mais vous devez essayer d'attraper le processus appelant processNextRegistrationMessage () pour avoir un journal de signification.

Expliquons-moi plus en détail:

Question: Combien de transactions avons-nous? Anser: Un seul

Parce que vous configurez la propriété PROPAGATION est PROPAGATION_REQUIRED, de sorte que la @Transaction persist () utilise la même transaction avec caller-processNextRegistrationMessage (). En fait, lorsque nous obtenons une exception, le ressort définira rollBackOnly pour le gestionnaire de transactions, de sorte que le ressort annulera une seule transaction.

Question: Mais nous avons un try-catch outside (), pourquoi cette exception se produit-elle? Réponse en raison d'une transaction unique

  1. Lorsque la méthode persist () a une exception
  2. Aller à la pêche dehors

    Spring will set the rollBaclOnly to true -> it determine we must 
    rollback the caller (processNextRegistrationMessage) also.
    
  3. Persist () se rétablira tout d'abord.

  4. Lancez une exception UnexpectedRollbackException pour indiquer que nous devons également annuler l'appelant.
  5. Le try-catch dans run () va attraper UnexpectedRollbackException et imprimer la trace de la pile

Question: Pourquoi nous changeons PROPAGATION en REQUIRES_NEW, ça marche?

Réponse: Parce que maintenant, les processus processNextRegistrationMessage () et persist () sont dans la transaction différente de sorte qu’ils annulent uniquement leur transaction.

Merci

5
linh đàm quang