本文介绍了UnexpectedRollbackException:事务回滚,因为它已被标记为仅回滚的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个场景:

  1. IncomingMessage表中获取(读取和删除)一条记录
  2. 读取记录内容
  3. 向某些表插入内容
  4. 如果在步骤 1-3 中发生错误(任何异常),将错误记录插入OutgoingMessage
  5. 否则,将成功记录插入OutgoingMessage

所以步骤 1、2、3、4 应该在一个事务中,或者步骤 1、2、3、5

我的流程从这里开始(是定时任务):

公共类ReceiveMessagesJob实现ScheduledJob {//...@覆盖公共无效运行(){尝试 {processMessageMediator.processNextRegistrationMessage();} 捕获(异常 e){e.printStackTrace();}}//...}

我在ProcessMessageMediator中的主要功能(processNextRegistrationMessage):

 公共类 ProcessMessageMediatorImpl 实现 ProcessMessageMediator {//...@覆盖@交易public void processNextRegistrationMessage() 抛出 ProcessIncomingMessageException {String refenceId = null;MessageTypeEnum registrationMessageType = MessageTypeEnum.REGISTRATION;尝试 {String messageContent =comingMessageService.fetchNextMessageContent(registrationMessageType);if (messageContent == null) {返回;}IncomingXmlModelcomingXmlModel =comingXmlDeserializer.fromXml(messageContent);refrenceId =incomingXmlModel.getRefrenceId();如果 (!StringUtil.hasText(refrenceId)) {抛出新的 ProcessIncomingMessageException(无法继续处理传入消息.参考代码字段为空.");}sqlCommandHandlerService.persist(incomingXmlModel);} 捕获(异常 e){if (e instanceof ProcessIncomingMessageException) {抛出(ProcessIncomingMessageException)e;}e.printStackTrace();//发送错误传出消息OutgoingXmlModel OutgoingXmlModel = new OutgoingXmlModel(refrenceId,ProcessResultStateEnum.FAILED.getCode(), e.getMessage());saveOutgoingMessage(outgoingXmlModel, registrationMessageType);返回;}//发送成功传出消息OutgoingXmlModel OutgoingXmlModel = new OutgoingXmlModel(refrenceId, ProcessResultStateEnum.SUCCEED.getCode());saveOutgoingMessage(outgoingXmlModel, registrationMessageType);}私人无效 saveOutgoingMessage(OutgoingXmlModel 传出XmlModel,MessageTypeEnum messageType)抛出 ProcessIncomingMessageException {String xml = OutgoingXmlSerializer.toXml(outgoingXmlModel, messageType);OutgoingMessageEntity entity = new OutgoingMessageEntity(messageType.getCode(), new Date());尝试 {传出MessageService.save(entity, xml);} catch (SaveOutgoingMessageException e) {throw new ProcessIncomingMessageException("无法继续处理传入消息.", e);}}//...}

正如我所说,如果在步骤 1-3 中发生任何异常,我想插入一个错误记录:

catch (Exception e) {if (e instanceof ProcessIncomingMessageException) {抛出(ProcessIncomingMessageException)e;}e.printStackTrace();//发送错误传出消息OutgoingXmlModel OutgoingXmlModel = new OutgoingXmlModel(refrenceId,ProcessResultStateEnum.FAILED.getCode(), e.getMessage());saveOutgoingMessage(outgoingXmlModel, registrationMessageType);返回;}

这是 SqlCommandHandlerServiceImpl.persist() 方法:

公共类SqlCommandHandlerServiceImpl实现SqlCommandHandlerService {//...@覆盖@交易公共无效坚持(IncomingXmlModelincomingXmlModel){Collections.sort(incomingXmlModel.getTables());列表查询 = generateSqlQueries(incomingXmlModel.getTables());对于(参数查询查询:查询){queryExecuter.executeQuery(query);}}//...}

但是当 sqlCommandHandlerService.persist() 抛出异常(这里是 org.hibernate.exception.ConstraintViolationException 异常),在 OutgoingMessage 表中插入错误记录后,当事务想要提交时,我得到 UnexpectedRollbackException.我不知道我的问题出在哪里:

线程null#0" org.springframework.transaction.UnexpectedRollbackException 中的异常:事务回滚,因为它已被标记为仅回滚在 org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)在 org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:394)在 org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)在 org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)在 ir.tamin.branch.insuranceregistration.services.schedular.ReceiveMessagesJob$$EnhancerByCGLIB$$63524c6b.run(<generated>)在 ir.asta.wise.core.util.timer.JobScheduler$ScheduledJobThread.run(JobScheduler.java:132)

我使用的是 hibernate-4.1.0-Final,我的数据库是 oracle,这是我的事务管理器 bean:

<property name="sessionFactory" ref="sessionFactory"/></bean><tx:annotation-driven transaction-manager="transactionManager"proxy-target-class="true"/>

提前致谢.

解决方案

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

为了克服这个问题,您可以对交易使用传播级别.看看this 找出哪种传播最适合您的要求.

更新;阅读这个!

一位同事就类似情况向我提出了几个问题后,我觉得需要澄清一下.
尽管传播解决了这些问题,但您应该非常小心使用它们,除非您绝对理解它们的含义以及它们的工作原理,否则不要使用它们.您最终可能会保留一些数据并回滚其他一些您不希望它们以这种方式工作的数据,并且事情可能会出现可怕的错误.


编辑 链接到当前版本的文档

I have this scenario:

  1. fetch (read and delete) a record from IncomingMessage table
  2. read record content
  3. insert something to some tables
  4. if an error (any exception) occurred in steps 1-3, insert an error-record to OutgoingMessage table
  5. otherwise, insert an success-record to OutgoingMessage table

So steps 1,2,3,4 should be in a transaction, or steps 1,2,3,5

My process starts from here (it is a scheduled task):

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

My main function (processNextRegistrationMessage) in 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);
        }
    }
// ...
}

As i said If any exception occurred in steps 1-3, i want insert an error-record:

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

It's SqlCommandHandlerServiceImpl.persist() method:

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

But when sqlCommandHandlerService.persist() throws exception (here a org.hibernate.exception.ConstraintViolationException exception), after inserting an error-record in OutgoingMessage table, when the transaction want to be committed , i get UnexpectedRollbackException. I can't figure out where is my problem:

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)

I'm using hibernate-4.1.0-Final, My database is oracle, and Here is my transaction-manager 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" />

Thanks in advance.

解决方案

This is the normal behavior and the reason is that your sqlCommandHandlerService.persist method needs a TX when being executed (because it is marked with @Transactional annotation). But when it is called inside processNextRegistrationMessage, because there is a TX available, the container doesn't create a new one and uses existing TX. So if any exception occurs in sqlCommandHandlerService.persist method, it causes TX to be set to rollBackOnly (even if you catch the exception in the caller and ignore it).

To overcome this you can use propagation levels for transactions. Have a look at this to find out which propagation best suits your requirements.

Update; Read this!

Well after a colleague came to me with a couple of questions about a similar situation, I feel this needs a bit of clarification.
Although propagations solve such issues, you should be VERY careful about using them and do not use them unless you ABSOLUTELY understand what they mean and how they work. You may end up persisting some data and rolling back some others where you don't expect them to work that way and things can go horribly wrong.


EDIT Link to current version of the documentation

这篇关于UnexpectedRollbackException:事务回滚,因为它已被标记为仅回滚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-11 16:49