26

I have been trying this:

@Transactional(isolation=Isolation.SERIALIZABLE, 
               rollbackFor={Exception.class}, 
               propagation=Propagation.REQUIRES_NEW)

on my service methods, but spring complains saying:

Standard JPA does not support custom isolation levels - use a special JpaDialect

How can I resolve this?

Alex
  • 8,093
  • 6
  • 49
  • 79
Ikthiander
  • 3,917
  • 8
  • 37
  • 54
  • +1: I've had this too. But the problem did not show up in my unit tests, despite using `AbstractTransactionalJUnit4SpringContextTests`, which was odd. – Raedwald May 22 '12 at 22:28
  • http://amitstechblog.wordpress.com/2011/05/31/supporting-custom-isolation-levels-with-jpa/ – user590444 Jul 05 '12 at 11:33
  • I tried the solution on Amit's blog but it made all my database connections read-only. I manage my transactions using AOP in my persistence config. – John Mark Jul 18 '13 at 15:08

6 Answers6

11

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

Shahzad Mughal
  • 314
  • 4
  • 3
  • 1
    +1 for thorough investigating and a complete solution including doing your housekeeping and cleaning up after yourself ;) – Stefan Haberl Nov 09 '12 at 14:40
  • It does not take into account readOnly flag. If we execute readonly transaction on the connection and then goes rw transaction, it fails, since connection is left in readonly state. – tuxSlayer Dec 04 '14 at 12:16
  • Just to be clear, I think this is different than setting up the org.hibernate.dialect, for example, PostgreSQL82Dialect? – Joel Mar 25 '16 at 17:13
10

No custom isolation levels are supported by JPA. You can extend the HibernateJpaDialect class and override connection-related methods so that you can set custom isolation levels on the Connection

Here's something that I've written, but have not tested yet:

public class HibernateExtendedJpaDialect extends HibernateJpaDialect {

    @Override
    public Object beginTransaction(EntityManager entityManager,
            TransactionDefinition definition) throws PersistenceException,
            SQLException, TransactionException {

        Session session = (Session) entityManager.getDelegate();
        DataSourceUtils.prepareConnectionForTransaction(session.connection(), definition);

        entityManager.getTransaction().begin();

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

}

And you define this as a property of your EntityManagerFactory:

<property name="jpaDialect">
    <bean class="com.company.util.HibernateExtendedJpaDialect" />
</property>
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • hi @Bozho could you kindly explain a bit more, you mean its got nothing to do with jpadialects as the other people are suggesting? – Ikthiander Mar 08 '11 at 15:24
  • @user582862 well, try it, but as far as I know JPA does not support isolation levels. The underlying JDBC does, however, so you can play with that. – Bozho Mar 08 '11 at 15:27
  • hmm i think you are correct. here is the complain about this: https://jira.springsource.org/browse/SPR-3812 – Ikthiander Mar 08 '11 at 15:35
  • 3
    From what I understand the idea behind the above solution is to call DataSourceUtils to set the isolation level. One problem with the above code is that the session.connection() method is deprecated by hibernate and will be removed from Hibernate 4.x (as per the java docs). I am looking out for better alternatives. – Andy Dufresne May 19 '11 at 04:35
  • 2
    session.doWork() is the suggested alternative for connection() – Andy Dufresne May 19 '11 at 06:52
  • Ideally this dialect implementation to reset the isolation level back to the previous one by overriding the cleanupTransaction() method. There are two issues in overriding this method. 1. The cleanup method does not have an handle to the connection to call DataSourceUtils.resetConnectionAfterTransaction() method. 2. Storing the previous isolation level so that it is available in the clean up method. The second issue can be resolved by creating a wrapper object around the object returned to beginTransaction. Need to think about the first point. – Andy Dufresne May 19 '11 at 11:11
  • The JpaTransactionManager javadocs gives an hint on using TransactionAwareDataSourceProxy. – Andy Dufresne May 19 '11 at 11:49
  • @Bozho how would it work in hibernate 4.0 There is no way to get the definition to execute ... – Jeremy S. Jan 18 '12 at 13:51
  • I don't know about hibernate 4 :( – Bozho Jan 18 '12 at 14:22
  • You should get the connection `this.getJdbcConnection(entityManager, definition.isReadOnly()).getConnection()` – ssedano Jun 04 '12 at 09:14
  • @AndyDufresne posted an answer that I believe addresses your concerns. – Gus Jul 16 '13 at 14:28
3

Drawing on Bozho's answer and considering the comments thereon, The following seems to be a complete (Hibernate 4 compatible) solution addressing the need reset the connection. Best I can tell, the spring layer will guarantee call the cleanupTransaction method, but if that isn't actually guaranteed, this may need to be re-thought due to the potential for a permGen memory leak and post request side effects on the connection object.

public class HibernateExtendedJpaDialect extends HibernateJpaDialect {

  ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
  ThreadLocal<Integer> originalIsolation = new ThreadLocal<>();

  @Override
  public Object beginTransaction(EntityManager entityManager,
                                 TransactionDefinition definition) 
      throws PersistenceException, SQLException, TransactionException {

    boolean readOnly = definition.isReadOnly();
    Connection connection = 
        this.getJdbcConnection(entityManager, readOnly).getConnection();
    connectionThreadLocal.set(connection);
    originalIsolation.set(DataSourceUtils
        .prepareConnectionForTransaction(connection, definition));

    entityManager.getTransaction().begin();

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

  /*

   We just have to trust that spring won't forget to call us. If they forget,
   we get a thread-local/classloader memory leak and messed up isolation 
   levels. The finally blocks on line 805 and 876 of 
   AbstractPlatformTransactionManager (Spring 3.2.3) seem to ensure this, 
   though there is a bit of logic between there and the actual call to this 
   method.

   */
  @Override
  public void cleanupTransaction(Object transactionData) {
    try {
      super.cleanupTransaction(transactionData);
      DataSourceUtils.resetConnectionAfterTransaction(
          connectionThreadLocal.get(), originalIsolation.get());
    } finally {
      connectionThreadLocal.remove();
      originalIsolation.remove();
    }
  }
}
Gus
  • 6,719
  • 6
  • 37
  • 58
  • I think that you should have a ThreadLocal>>> instead of two thread locals. – Nick Hristov Oct 18 '13 at 21:57
  • In some cases no connection provided to resetConnectionAfterTransaction, and in this case IllegalArgumentException throw in Assert.notNull(con, "No Connection specified");. Can u say me, i need to check if con not null first, or something else? – Ruslan Nov 21 '13 at 15:14
0

@Shahzad Mughal I left you two points ;-) Your answer should be accepted as the correct one. The accepted answer will originate the below issue randomly miss leading developers to think there are bugs with mysql driver for example:

WARN [org.hibernate.util.JDBCExceptionReporter] - SQL Error: 0, SQLState: S1009 ERROR [org.hibernate.util.JDBCExceptionReporter] - Connection is read-only. Queries leading to data modification are not allowed

You can read more about our adventure with this issue at http://thinkinginsoftware.blogspot.com/2013/10/connection-is-read-only-queries-leading.html

Nestor Urquiza
  • 2,821
  • 28
  • 21
-1

You can also wrap the "datasource" bean with the "IsolationLevelDataSourceAdapter", by simply doing this:

<bean id="dataSource" class="org.springframework.jdbc.datasource.IsolationLevelDataSourceAdapter">
    <property name="isolationLevelName" value="ISOLATION_READ_COMMITTED"/>
    <property name="targetDataSource" ref="_dataSource"/>
</bean>

where "_dataSource" is a ref to an actual datasource.

polesen
  • 713
  • 1
  • 6
  • 18
  • This is a terrible solution. Any connection and query that you issue then will be with serializable isolation. – Nick Hristov Feb 07 '14 at 22:30
  • In some cases this solution may apply - for example in my project I have multiple datasources, and I need to set a specific isolation level on one of them. This solution is much more elegant for this case than extending the JpaDialect. – Andrei Socaciu Aug 15 '14 at 11:21
-1

When specifying the JpaTransactionManager did you specify a JPADialect? By default I think it uses the DefaultJpaDialect and you need the HibernateJpaDialect.

Robby Pond
  • 73,164
  • 16
  • 126
  • 119
  • 2
    It has nothing to do with that. HibernateJpaDialect's beginTransaction calls super.beginTransaction(entityManager, definition) (super=DefaultJpaDialect) that's why the InvalidIsolationLevelException is thrown from DefaultJpaDialect. – Juan Carlos Blanco Martínez May 26 '11 at 15:42