Java UnexpectedRollbackException:事务回滚,因为它已被标记为仅回滚
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19349898/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
提问by Arya
I have this scenario:
我有这个场景:
- fetch (read and delete) a record from IncomingMessagetable
- read record content
- insert something to some tables
- if an error (any exception) occurred in steps 1-3, insert an error-record to OutgoingMessagetable
- otherwise, insert an success-record to OutgoingMessagetable
- 从IncomingMessage表中获取(读取和删除)一条记录
- 读取记录内容
- 在某些表中插入一些东西
- 如果在步骤 1-3 中发生错误(任何异常),则将错误记录插入到OutgoingMessage表中
- 否则,将成功记录插入到OutgoingMessage表
So steps 1,2,3,4 should be in a transaction, or steps 1,2,3,5
所以步骤 1,2,3,4 应该在一个事务中,或者步骤 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:
我在 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);
}
}
// ...
}
As i said If any exception occurred in steps 1-3, i want insert an error-record:
正如我所说,如果在步骤 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;
}
It's SqlCommandHandlerServiceImpl.persist() method:
这是 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);
}
}
// ...
}
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:
但是,当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$524c6b.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:
我正在使用 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" />
Thanks in advance.
提前致谢。
采纳答案by Ean V
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).
这是正常行为,原因是您的sqlCommandHandlerService.persist
方法在执行时需要 TX(因为它用@Transactional
注释标记)。但是当它被调用 inside 时processNextRegistrationMessage
,因为有一个可用的 TX,容器不会创建一个新的并使用现有的 TX。因此,如果sqlCommandHandlerService.persist
方法中发生任何异常,它会导致 TX 被设置为rollBackOnly
(即使您在调用者中捕获异常并忽略它)。
To overcome this you can use propagation levels for transactions. Have a look at thisto 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 VERYcareful about using them and do not use them unless you ABSOLUTELYunderstand 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.
在一位同事就类似情况向我提出几个问题之后,我觉得这需要一些澄清。
尽管解决的传播等问题,你应该非常小心使用它们,请不要使用它们,除非你ABSOLUTELY了解他们的工作他们的意思,以及如何。您最终可能会保留一些数据并回滚其他一些您不希望它们以这种方式工作的数据,并且事情可能会出现可怕的错误。
EDIT编辑Link to current version of the documentation链接到当前版本的文档
回答by linh ?àm quang
The answer of Shyam was right. I already faced with this issue before. It's not a problem, it's a SPRING feature. "Transaction rolled back because it has been marked as rollback-only" is acceptable.
Shyam 的回答是正确的。我之前已经遇到过这个问题。这不是问题,这是 SPRING 功能。“事务回滚,因为它已被标记为仅回滚”是可以接受的。
Conclusion
结论
- USE REQUIRES_NEW if you want to commit what did you do before exception (Local commit)
- USE REQUIRED if you want to commit only when all processes are done (Global commit) And you just need to ignore "Transaction rolled back because it has been marked as rollback-only" exception. But you need to try-catch out side the caller processNextRegistrationMessage() to have a meaning log.
- 如果你想提交你在异常之前做了什么(本地提交),请使用 REQUIRES_NEW
- 如果您只想在所有进程完成后提交(全局提交),请使用 REQUIRED 并且您只需要忽略“事务回滚,因为它已被标记为仅回滚”异常。但是您需要尝试捕获调用方 processNextRegistrationMessage() 以获得有意义的日志。
Let's me explain more detail:
让我更详细地解释一下:
Question: How many Transaction we have? Answer: Only one
问题:我们有多少交易?答案:只有一个
Because you config the PROPAGATION is PROPAGATION_REQUIRED so that the @Transaction persist() is using the same transaction with the caller-processNextRegistrationMessage(). Actually, when we get an exception, the Spring will set rollBackOnlyfor the TransactionManager so the Spring will rollback just only one Transaction.
因为您将 PROPAGATION 配置为 PROPAGATION_REQUIRED,以便@Transaction persist() 使用与 caller-processNextRegistrationMessage() 相同的事务。实际上,当我们遇到异常时,Spring 会为 TransactionManager设置rollBackOnly,因此 Spring 只会回滚一个事务。
Question: But we have a try-catch outside (), why does it happen this exception? Answer Because of unique Transaction
问题:但是我们在()外面有一个try-catch,为什么会发生这个异常呢?答案是因为唯一的交易
- When persist() method has an exception
Go to the catch outside
Spring will set the rollBackOnly to true -> it determine we must rollback the caller (processNextRegistrationMessage) also.
The persist() will rollback itself first.
- Throw an UnexpectedRollbackException to inform that, we need to rollback the caller also.
- The try-catch in run() will catch UnexpectedRollbackException and print the stack trace
- 当persist()方法出现异常时
去外面钓点
Spring will set the rollBackOnly to true -> it determine we must rollback the caller (processNextRegistrationMessage) also.
persist() 将首先回滚自身。
- 抛出一个 UnexpectedRollbackException 来通知,我们还需要回滚调用者。
- run() 中的 try-catch 将捕获 UnexpectedRollbackException 并打印堆栈跟踪
Question: Why we change PROPAGATION to REQUIRES_NEW, it works?
问题:为什么我们将 PROPAGATION 改为 REQUIRES_NEW,它有效吗?
Answer: Because now the processNextRegistrationMessage() and persist() are in the different transaction so that they only rollback their transaction.
答:因为现在 processNextRegistrationMessage() 和 persist() 在不同的事务中,所以他们只回滚他们的事务。
Thanks
谢谢