UnexpectedRollbackException:事务已回滚,因为它已标记为仅回滚

时间:2013-10-13 20:26:32

标签: java spring hibernate transactions

我有这种情况:

  1. IncomingMessage 表中提取(读取和删除)记录
  2. 阅读记录内容
  3. 在某些表格中插入内容
  4. 如果步骤1-3中发生错误(任何异常),请将错误记录插入 OutgoingMessage
  5. 否则,将成功记录插入 OutgoingMessage
  6. 所以步骤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" />
    

    提前致谢。

2 个答案:

答案 0 :(得分:37)

这是正常行为,原因是您的sqlCommandHandlerService.persist方法在执行时需要TX(因为它标有@Transactional注释)。但是当它在processNextRegistrationMessage内被调用时,因为有一个TX可用,所以容器不会创建新的并使用现有的TX。因此,如果sqlCommandHandlerService.persist方法中发生任何异常,则会导致TX设置为rollBackOnly(即使您在调用者中捕获异常并忽略它)。

要解决此问题,您可以使用传播级别进行交易。请查看this,了解哪种传播最符合您的要求。

更新;读这个!

在一位同事向我提出有关类似情况的几个问题之后,我觉得这需要一些澄清。
尽管传播解决了这些问题,但您应该非常小心使用它们,除非绝对明白它们的含义以及它们的工作原理,否则不要使用它们。你最终可能会持久保存一些数据并回滚其他一些你并不期望它们以这种方式工作的数据,而且事情可能会出现严重错误。

<小时/> 编辑 Link to current version of the documentation

答案 1 :(得分:3)

Shyam的答案是对的。我之前已经遇到过这个问题。这不是问题,它是一个SPRING功能。 “事务已回滚,因为它已被标记为仅回滚”是可以接受的。

<强> 结论

  • 如果你想提交你在异常(本地提交)之前所做的事情,请使用REQUIRES_NEW
  • 如果您只想在所有进程完成时提交,则需要使用(全局提交)并且您只需要忽略“事务已回滚,因为它已被标记为仅回滚”异常。但是你需要尝试捕获调用者processNextRegistrationMessage()以获得含义日志。

让我解释一下更多细节:

问题:我们有多少笔交易? Anser:只有一个

因为你配置PROPAGATION是PROPAGATION_REQUIRED,所以@Transaction persist()使用与caller-processNextRegistrationMessage()相同的事务。实际上,当我们得到异常时,Spring会为TransactionManager设置 rollBackOnly ,因此Spring只会回滚一个Transaction。

问题:但是我们在外面尝试了一下(),为什么会发生这种异常呢?  答案由于独特的交易

  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并打印堆栈跟踪
  6. 问题:为什么我们将PROPAGATION更改为REQUIRES_NEW,它有效吗?

    Anser:因为现在processNextRegistrationMessage()和persist()在不同的事务中,所以他们只回滚他们的事务。

    由于