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

JavaSpringHibernateTransactions

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAryaView Question on Stackoverflow
Solution 1 - JavaEan VView Answer on Stackoverflow
Solution 2 - Javalinh đàm quangView Answer on Stackoverflow