Supporting Custom Isolation Levels With JPA

This post provides a workaround for supporting custom isolation levels with JPA. If you use the HibernateJpaDialect (provided by spring)  and try to set an isolation level different from the default one you would get an exception “Standard JPA does not support custom isolation levels – use a special JpaDialect”

The above error would be seen when your configuration is as below

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" lazy-init="true">
   <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
          lazy-init="true">
        <property name="persistenceUnitName" value="spmsoftwareunit"/>
        <property name="persistenceXmlLocation" value="classpath:com/spmsoftware/sampleapp/conf/persistence.xml"/>
        <property name="dataSource" ref="oracleDataSourceFactoryBean"/>
        <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.id.new_generator_mappings">true</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
                <prop key="hibernate.jdbc.batch_size">3</prop>
                <prop key="hibernate.jdbc.batch_versioned_data">true</prop>
                <prop key="hibernate.jdbc.factory_class">com.spmsoftware.sampleapp.persistence.OracleBatchingBatcherFactory</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

and the transaction is applied as

@Transactional(isolation=Isolation.SERIALIZABLE, propagation=Propagation.REQUIRED)

WorkAround

We can extend HibernateJpaDialect to modify the connection instance before the transaction starts

import org.hibernate.Session;
import org.hibernate.jdbc.Work;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;

public class HibernateExtendedJpaDialect extends HibernateJpaDialect {

    private Logger logger = LoggerFactory.getLogger(HibernateExtendedJpaDialect.class);

    /**
     * This method is overridden to set custom isolation levels on the connection
     * @param entityManager
     * @param definition
     * @return
     * @throws PersistenceException
     * @throws SQLException
     * @throws TransactionException
     */
    @Override
    public Object beginTransaction(final EntityManager entityManager,
            final TransactionDefinition definition) throws PersistenceException,
            SQLException, TransactionException {
        Session session = (Session) entityManager.getDelegate();
        if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
            getSession(entityManager).getTransaction().setTimeout(definition.getTimeout());
        }

        entityManager.getTransaction().begin();
        logger.debug("Transaction started");

        session.doWork(new Work() {

            public void execute(Connection connection) throws SQLException {
                logger.debug("The connection instance is {}", connection);
                logger.debug("The isolation level of the connection is {} and the isolation level set on the transaction is {}",
                        connection.getTransactionIsolation(), definition.getIsolationLevel());
                DataSourceUtils.prepareConnectionForTransaction(connection, definition);
            }
        });

        return prepareTransaction(entityManager, definition.isReadOnly(), definition.getName());
    }

}

The above implementation takes care of setting the isolation level specified on the transaction definition by calling DataSourceUtils.prepareConnectionForTransaction(connection, definition);

Since the isolation level is changed on the connection instance, resetting it is important. This would have to be done in the data source since the connection gets closed as soon as the transaction is committed. There is no way to intercept the JpaTransactionManager code flow to reset the isolation level after commit is done.
By decorating the data source and setting the isolation level before the connection is given by the connection pool, one can achieve resetting of the isolation level on the connection instance to default (which in most of the cases is read committed).

Posted on May 31, 2011, in hibernate, jpa, spring and tagged , , , . Bookmark the permalink. 9 Comments.

  1. This implementation is not account for the clean up stuff, I have implemented a similar solution but that accounts for the clean up as well.
    That solution can be found here:
    http://shahzad-mughal.blogspot.com/2012/04/spring-jpa-hibernate-support-for-custom.html

    • It does :). Have a look at the last paragraph. One of the solutions I implemented was to reset the isolation level when the connection is requested from the pool. This is done by decorating the data source.

      There were some issues I had faced by following the approach you mentioned i.e. overriding cleanupTransaction() but I am not able to completely recollect what the errors were. But if it works for you, well and good 🙂

  2. Can you please show me how to decorate the datasource to reset isolation level? Would be a great help if you can provide the code for this as i’m currently stuck here… Thanks.

    • Decorating a data source would just mean implementing the decorator design pattern Just override the getConnection() method of the original data source and add more behavior to it. In this case, we would need to check the transaction isolation level of the connection and see if it is not the default isolation level (normally – read committed). If it is not, just set it and then return the connection to the application.

      • Is the datasource decorating already done in the HibernateExtendedJpaDialect class. I could not figure it out where. Can you be more clear on where it is done in the above code?

  3. Amit, you have given configuration before the HibernateExtendedJpaDialect class implementation, can I know the configuration changes you made after extended dialect class implementation. I am assuming you just change the jpaDialect to the new class? If not kindly give the configuration changes later.

  4. Amit one other quick question, if I set Transaction level at one particular method and as this HibernateExtendedJpaDialect class will be invoked for every transaction. If I do a proper cleanup it should not affect the other transactions where isolation level is not set, right? As setting SERIALIZABLE isolation level is performance hit right. Please suggest and thank you for your help.

  5. Sorry the code didn’t get posted. May be the datasource can be wrapped like this:

Leave a comment