63

我有这种情况:

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

提前致谢。

4

2 回答 2

65

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

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

更新; 读这个!

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


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

于 2013-10-14T06:19:24.873 回答
20

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

结论

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

让我更详细地解释一下:

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

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

问题:但是我们在()外面有一个try-catch,为什么会发生这个异常?答案因为独特的交易

  1. 当persist()方法出现异常时
  2. 去外面抓鱼

    Spring will set the rollBackOnly to true -> it determine we must 
    rollback the caller (processNextRegistrationMessage) also.
    
  3. persist() 将首先回滚自己。

  4. 抛出一个 UnexpectedRollbackException 来通知我们,我们还需要回滚调用者。
  5. run() 中的 try-catch 将捕获 UnexpectedRollbackException 并打印堆栈跟踪

问题:为什么我们将 PROPAGATION 更改为 REQUIRES_NEW,它有效?

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

谢谢

于 2018-04-22T01:28:15.903 回答