Spring Framework
  1. Spring Framework
  2. SPR-5012

HibernateJpaDialect does not support setting a specific isolation level per transaction

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 2.5.2
    • Fix Version/s: General Backlog
    • Component/s: Core
    • Labels:
      None
    • Last commented by a User:
      true

      Description

      Hi,

      our problem is in unit testing. When we deploy under Weblogic and use the Weblogic transaction manager, everything is ok.
      But for unit testing, we use a LocalContainerEntityManagerFactoryBean with a HibernateJpaVendorAdapter injected into a JpaTransactionManager.

      When isolation level other than DEFAULT is required for a transaction (setted through annotations), an InvalidIsolationLevelException is thrown.

      I have solved this problem by writing a CustomHibernateJpaDialect in which I just delete the throw exception :

      <i>
      public class CustomHibernateJpaDialect
      extends HibernateJpaDialect
      {
      public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
      throws PersistenceException, SQLException,
      TransactionException

      { entityManager.getTransaction().begin(); return null; }

      public void cleanupTransaction(Object transactionData)

      { // This line throws a NullPointerException. Got no time to go deeper but we never change the flush mode so I let it commented //((SessionTransactionData) transactionData).resetFlushMode(); }

      }
      </i>

      and a CustomHibernateJpaVendorAdapter in which I use my new CustomHibernateJpaDialect :

      <i>
      public class CustomHibernateJpaVendorAdapter extends AbstractJpaVendorAdapter
      {
      private final PersistenceProvider persistenceProvider = new HibernatePersistence();

      private final JpaDialect jpaDialect = new CustomHibernateJpaDialect();

      public PersistenceProvider getPersistenceProvider()

      { return this.persistenceProvider; }

      public Map getJpaPropertyMap() {
      Properties jpaProperties = new Properties();

      if (getDatabasePlatform() != null)

      { jpaProperties.setProperty(Environment.DIALECT, getDatabasePlatform()); }

      else if (getDatabase() != null) {
      Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());
      if (databaseDialectClass != null)

      { jpaProperties.setProperty(Environment.DIALECT, databaseDialectClass.getName()); }

      }

      if (isGenerateDdl())

      { jpaProperties.setProperty(Environment.HBM2DDL_AUTO, "update"); }

      if (isShowSql())

      { jpaProperties.setProperty(Environment.SHOW_SQL, "true"); }

      return jpaProperties;
      }

      /**

      • Determine the Hibernate database dialect class for the given target database.
      • @param database the target database
      • @return the Hibernate database dialect class, or <code>null<code> if none found
        */
        protected Class determineDatabaseDialectClass(Database database)
        Unknown macro: { switch (database) { case DB2: return DB2Dialect.class; case HSQL: return HSQLDialect.class; case INFORMIX: return InformixDialect.class; case MYSQL: return MySQLDialect.class; case ORACLE: return Oracle9Dialect.class; case POSTGRESQL: return PostgreSQLDialect.class; case SQL_SERVER: return SQLServerDialect.class; case SYBASE: return SybaseDialect.class; default: return null; } }

      public Class<? extends EntityManager> getEntityManagerInterface()

      { return HibernateEntityManager.class; }

      public JpaDialect getJpaDialect()

      { return this.jpaDialect; }

      }
      </i>

      I can't believe it's all we have to do to make it work correctly but I can go ahead in my unit testing.

      Is my isolation level really set (I want SERIALIZABLE) ?

      Thank you

        Issue Links

          Activity

          Hide
          Juergen Hoeller added a comment -

          This is a known limitation: All current JPA providers make it pretty hard to set a specific isolation level for a native transaction. This is caused by the general JPA assumption that JDBC Connections will always be retrieved on demand, not necessarily held for the lifetime of a transaction... For a native transaction, the provider will have to hold a Connection per transaction eventually, but it may fetch it lazily on first access and it may release it right after transaction commit. This makes it hard to set an isolation level at the JDBC Connection level, and particularly hard to reset the original isolation level before returning the Connection to the pool!

          In any case, this should be better documented. The InvalidIsolationLevelException message could be more specific as well. I'll revise the doc bits for Spring 2.5.6.

          Juergen

          Show
          Juergen Hoeller added a comment - This is a known limitation: All current JPA providers make it pretty hard to set a specific isolation level for a native transaction. This is caused by the general JPA assumption that JDBC Connections will always be retrieved on demand, not necessarily held for the lifetime of a transaction... For a native transaction, the provider will have to hold a Connection per transaction eventually, but it may fetch it lazily on first access and it may release it right after transaction commit. This makes it hard to set an isolation level at the JDBC Connection level, and particularly hard to reset the original isolation level before returning the Connection to the pool! In any case, this should be better documented. The InvalidIsolationLevelException message could be more specific as well. I'll revise the doc bits for Spring 2.5.6. Juergen
          Hide
          Samuel Gaiffe added a comment -

          Thank you Juergen.

          I know all of this but I wanted to know if I get the desired isolation level by using this workaround or if it just bypass the isolation level setting ?

          Samuel

          Show
          Samuel Gaiffe added a comment - Thank you Juergen. I know all of this but I wanted to know if I get the desired isolation level by using this workaround or if it just bypass the isolation level setting ? Samuel
          Hide
          Juergen Hoeller added a comment -

          I'm afraid this workaround will just bypass the isolation level setting...

          Juergen

          Show
          Juergen Hoeller added a comment - I'm afraid this workaround will just bypass the isolation level setting... Juergen
          Hide
          Samuel Gaiffe added a comment -

          I'll make some test to verify it and i'll let you know.

          Thank you again.

          Samuel

          Show
          Samuel Gaiffe added a comment - I'll make some test to verify it and i'll let you know. Thank you again. Samuel
          Hide
          Gaetan Pitteloud added a comment -

          In the case when JPA is used in local mode, which means it is configured with a DataSource, would a JpaDialect implementation that's implemented like the corresponding transaction manager do the job ?

          More precisely, I use Hibernate as the JPA provider and JPA is local (not JTA); would a JPA Dialect implementation that does exactly what HibernateTransactionManager does in its doBegin(), in addition to HibernateJpaDialect, do the job ?

          Thanks for the reply.

          Show
          Gaetan Pitteloud added a comment - In the case when JPA is used in local mode, which means it is configured with a DataSource, would a JpaDialect implementation that's implemented like the corresponding transaction manager do the job ? More precisely, I use Hibernate as the JPA provider and JPA is local (not JTA); would a JPA Dialect implementation that does exactly what HibernateTransactionManager does in its doBegin(), in addition to HibernateJpaDialect, do the job ? Thanks for the reply.
          Hide
          Gaetan Pitteloud added a comment -

          I guess the problem comes in this call : Connection con = session.connection();
          However, I suppose that with hibernate implementing local JPA, the JDBC connection returned by this call is the one used for the duration of the (local) transaction. But this is Hibernate stuff, right ?

          Show
          Gaetan Pitteloud added a comment - I guess the problem comes in this call : Connection con = session.connection(); However, I suppose that with hibernate implementing local JPA, the JDBC connection returned by this call is the one used for the duration of the (local) transaction. But this is Hibernate stuff, right ?
          Hide
          Stevo Slavić added a comment -

          Just to note, IMO this issue is related to SPR-3812, so maybe these could be linked.

          Show
          Stevo Slavić added a comment - Just to note, IMO this issue is related to SPR-3812 , so maybe these could be linked.
          Hide
          Eugen Paraschiv added a comment -

          This seems to be a significant limitation with JPA, and one that could potentially be fixed at least when not using JTA transactions. Is there any way to handle that case by improving the HibernateJpaDialect?
          Also, and without any concrete numbers, it seems to me that configuring Spring with JPA but not with JTA transactions is a very common use case, so handling it specifically makes a lot of sense, especially considering that the limitation is severe, and also considering that Spring with Hibernate directly should work just fine.

          Show
          Eugen Paraschiv added a comment - This seems to be a significant limitation with JPA, and one that could potentially be fixed at least when not using JTA transactions. Is there any way to handle that case by improving the HibernateJpaDialect? Also, and without any concrete numbers, it seems to me that configuring Spring with JPA but not with JTA transactions is a very common use case, so handling it specifically makes a lot of sense, especially considering that the limitation is severe, and also considering that Spring with Hibernate directly should work just fine.

            People

            • Assignee:
              Juergen Hoeller
              Reporter:
              Samuel Gaiffe
              Last updater:
              Trevor Marshall
            • Votes:
              6 Vote for this issue
              Watchers:
              9 Start watching this issue

              Dates

              • Created:
                Updated:
                Days since last comment:
                2 years, 17 weeks, 4 days ago