I have this scenario:
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.
Rolls back an explicit or implicit transaction to the beginning of the transaction, or to a savepoint inside the transaction. You can use ROLLBACK TRANSACTION to erase all data modifications made from the start of the transaction or to a savepoint. It also frees resources held by the transaction.
Hence the UnexpectedRollbackException. To prevent this, the only thing you need to do is catch (and swallow, if you really want to ignore this problem) the original ConstraintViolationException WITHIN the transaction. Inside the saveMyData() for example would be ok.
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.
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.
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.
Conclusion
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 rollBackOnly for the TransactionManager so the Spring will rollback just only one Transaction.
Question: But we have a try-catch outside (), why does it happen this exception? Answer Because of unique Transaction
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With