意外回滚异常: 事务回滚,因为它已被标记为仅回滚

我有这样一个场景:

  1. 收到信息表中提取(读取和删除)一条记录
  2. 阅读记录内容
  3. 在一些桌子上插入一些东西
  4. 如果在步骤1-3中出现错误(任何异常) ,向 发送信息表插入错误记录
  5. 否则,向 发送信息表插入一条成功记录

因此步骤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;
}

它的方法是 SqlCommandHandlerServiceImp.keep () :

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抛出异常时(这里是 org.hibernate.eption)。在 OutgoingMessage 表中插入错误记录后,当事务需要提交时,我得到了意外的 RollbackException 异常。我不知道我的问题出在哪里:

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

先谢谢你。

233403 次浏览

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

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

更新,看看这个!

在一位同事就类似的情况向我提出几个问题之后,我觉得这需要一点澄清。
尽管传播可以解决这些问题,但是在使用它们时应该非常小心,除非 当然理解它们的含义和工作原理,否则不要使用它们。您可能最终会持久化某些数据,并回滚其他一些数据,而您并不希望它们按照这种方式工作,这样做可能会导致严重的错误。


EDIT < a href = “ https://docs.spring.io/spring/docs/current/javadoc-api/org/springFramework/action/notation/ rel = “ noReferrer”> 链接到当前版本的文档

希亚姆的回答是正确的。我以前就遇到过这个问题。这不是问题,这是春季特色。“事务回滚,因为它已被标记为仅回滚”是可以接受的。

结论

  • 如果您想提交在异常之前所做的操作(本地提交) ,请使用 REQUIRES _ NEW
  • 如果您只想在所有进程都完成时提交(全局提交) ,那么使用必要参数,并且您只需要忽略“ Transaction rollback because it has been mark as rollback-only”异常。但是,您需要在调用方 processNextRegistrationMessage ()之外尝试-catch 以获得含义日志。

让我解释更多细节:

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

因为您将 PROPAGATION 配置为 PROPAGATION _ REQUIRED,所以@Transaction keep ()与调用者 processNextRegistrationMessage ()使用相同的事务。实际上,当我们得到一个异常时,Spring 将为 TransactionManager 设置 仅限 rollBackOnly,因此 Spring 将只回滚一个 Transaction。

问: 但是我们有一个 try-catch out () ,为什么会发生这种异常? 回答因为唯一事务

  1. 方法有异常时
  2. 去外面接球

    Spring will set the rollBackOnly to true -> it determine we must
    rollback the caller (processNextRegistrationMessage) also.
    
  3. The persist() will rollback itself first.

  4. Throw an UnexpectedRollbackException to inform that, we need to rollback the caller also.
  5. The try-catch in run() will catch UnexpectedRollbackException and print the stack trace

Question: Why we change PROPAGATION to REQUIRES_NEW, it works?

Answer: Because now the processNextRegistrationMessage() and persist() are in the different transaction so that they only rollback their transaction.

Thanks