Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

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.

like image 887
Arya Avatar asked Oct 13 '13 20:10

Arya


People also ask

What is transactional rollback?

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.

How do you handle UnexpectedRollbackException?

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.


2 Answers

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
like image 173
Ean V Avatar answered Sep 25 '22 13:09

Ean V


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

  • 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.

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

  1. When persist() method has an exception
  2. Go to the catch outside

    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

like image 41
linh đàm quang Avatar answered Sep 24 '22 13:09

linh đàm quang