[java] 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.

This question is related to java spring hibernate transactions

The answer is


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


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


Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to spring

Are all Spring Framework Java Configuration injection examples buggy? Two Page Login with Spring Security 3.2.x Access blocked by CORS policy: Response to preflight request doesn't pass access control check Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified Spring Data JPA findOne() change to Optional how to use this? After Spring Boot 2.0 migration: jdbcUrl is required with driverClassName The type WebMvcConfigurerAdapter is deprecated No converter found capable of converting from type to type

Examples related to hibernate

Hibernate Error executing DDL via JDBC Statement How does spring.jpa.hibernate.ddl-auto property exactly work in Spring? Error creating bean with name 'entityManagerFactory' defined in class path resource : Invocation of init method failed JPA Hibernate Persistence exception [PersistenceUnit: default] Unable to build Hibernate SessionFactory Disable all Database related auto configuration in Spring Boot Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] HikariCP - connection is not available Hibernate-sequence doesn't exist How to find distinct rows with field in list using JPA and Spring? Spring Data JPA and Exists query

Examples related to transactions

sql try/catch rollback/commit - preventing erroneous commit after rollback Laravel: Using try...catch with DB::transaction() UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only Transaction marked as rollback only: How do I find the cause Transaction isolation levels relation with locks on table Spring transaction REQUIRED vs REQUIRES_NEW : Rollback Transaction When to use SELECT ... FOR UPDATE? Correct use of transactions in SQL Server MySQL : transaction within a stored procedure issue ORA-00001: unique constraint violated coming in INSERT/UPDATE