Uploaded image for project: 'WildFly WIP'
  1. WildFly WIP
  2. WFWIP-53

Differences in ORM exception handling in 5.1 vs 5.3

    XMLWordPrintable

Details

    • Bug
    • Resolution: Done
    • Blocker
    • JPA
    • None

    Description

      I see tests failing because org.hibernate.hql.internal.ast.QuerySyntaxException is wrapped by java.lang.IllegalArgumentException. I know this has been discussed on hibernate-dev mailing list but without any outcome.

      http://lists.jboss.org/pipermail/hibernate-dev/2018-May/017654.html

      I've been looking at differences in Hibernate exception handling for
      applications that uses "native" (non-JPA) Hibernate when moving from 5.1 to
      5.3.

      As you know, exception handling changed in 5.2 when hibernate-entitymanager
      was merged into hibernate-core. This change is documented in the 5.2
      migration guide. [1]

      Here is the relevant text:

      "org.hibernate.HibernateException now extends
      javax.persistence.PersistenceExceptions.
      Hibernate methods that "override" methods from their JPA counterparts now
      will also throw various JDK defined RuntimeExceptions (such as
      IllegalArgumentException, IllegalStateException, etc) as required by the
      JPA contract."

      While digging into this, I see that a HibernateException thrown when using
      5.1 may, in 5.3, be unwrapped, or may be wrapped by a PersistenceException
      (or a subclass), IllegalArgumentException, or IllegalStateException,
      depending on the particular operation being performed when the
      HibernateException is thrown.

      The reason why the exceptions may be wrapped or unwrapped is because
      Hibernate converts exceptions (via
      AbstractSharedSessionContract#exceptionConverter)
      for JPA operations which may wrap the HibernateException or throw a
      different exception. Hibernate does not convert exceptions from strictly
      "native" operations, so those exceptions remain unwrapped.

      Here are a couple of examples to illustrate:

      1) A HibernateException (org.hibernate.TransientObjectException) was thrown
      in 5.1. In 5.3, the same condition can result in
      org.hibernate.TransientObjectException
      that is either unwrapped, or wrapped by IllegalStateException. I've pushed
      a test to my fork to illustrate. [2]

      Thrown during Session#save, #saveOrUpdate
      In 5.1: org.hibernate.TransientObjectException (unwrapped)
      In 5.3: org.hibernate.TransientObjectException (unwrapped)

      Thrown during Session#persist, #merge, #flush
      In 5.1, org.hibernate.TransientObjectException (unwrapped)
      In 5.3, org.hibernate.TransientObjectException wrapped by
      IllegalStateException

      2) A HibernateException thrown when using 5.1 may be wrapped by a
      PersistenceException, even though HibernateException already extends
      PersistenceException.

      For example, see TransactionTimeoutTest#testTransactionTimeoutFailure:
      https://github.com/hibernate/hibernate-orm/blob/master/
      hibernate-core/src/test/java/org/hibernate/test/tm/
      TransactionTimeoutTest.java#L60-L82

      The exception thrown by the test indicates that the transaction timed out.
      This exception is important enough that an application might retry the
      operation, or at least log for future investigation.

      Thrown during Session#persist (or when changed to use Session#merge or
      Session#flush):
      In 5.1: org.hibernate.TransactionException (unwrapped)
      In 5.3: org.hibernate.TransactionException wrapped by javax.persistence.
      PersistenceException

      Thrown if the test is changed to use Session#save or #saveOrUpdate instead:
      In 5.1: org.hibernate.TransactionException (unwrapped)
      In 5.3: org.hibernate.TransactionException (unwrapped)

      Similarly, by adding some logging, I see that HibernateException objects
      can be wrapped by PersistenceException when running the 5.3 hibernate-core
      unit tests. Depending on the context, I see that some of the following
      exceptions can be wrapped or unwrapped.

      org.hibernate.exception.ConstraintViolationException
      org.hibernate.exception.DataException
      org.hibernate.exception.GenericJDBCException
      org.hibernate.exception.SQLGrammarException
      org.hibernate.id.IdentifierGenerationException
      org.hibernate.loader.custom.NonUniqueDiscoveredSqlAliasException
      org.hibernate.PersistentObjectException
      org.hibernate.PropertyAccessException
      org.hibernate.PropertyValueException
      org.hibernate.TransactionException

      You can see an example using
      org.hibernate.exception.ConstraintViolationException
      at [3].

      In order to deal with these differences, an application could change the
      following (which was appropriate for 5.1):

      try {
      ...
      }
      catch (HibernateException ex) {
      procressHibernateException( ex );
      }

      to the following for 5.3:

      try {
      ...
      }
      catch (PersistenceException | IllegalStateException |
      IllegalArgumentException ex) {
      if ( HibernateException.class.isInstance( ex ) )

      Unknown macro: { handleHibernateException( (HibernateException) ex ); }

      else if ( HibernateException.class.isInstance( ex.getCause() ) )

      Unknown macro: { handleHibernateException( (HibernateException) ex.getCause() ); }

      }

      IMO, it's a little clumsy having to deal with both wrapped and unwrapped
      exceptions. It would be better if exceptions were consistently wrapped, or
      consistently unwrapped.

      I haven't had much of a chance to think about how we can deal with this,
      but one thing that comes to mind is to allow an application to choose a
      particular ExceptionConverter implementation. Hibernate would need to be
      changed to always convert exceptions (including for strictly native
      operations) before returning to the application.

      For example, a property, hibernate.exception_converter could be added with
      the following values:

      jpa - default for JPA applications (org.hibernate.internal.
      ExceptionConverterImpl)
      native (or legacy?) - default for native applications that does not wrap
      HibernateException
      fully-qualified class name that implements ExceptionConverter

      If users have to make changes to exception handling for 5.3, do you think
      they would be willing to change their application to use JPA exceptions
      (i.e., hibernate.exception_converter=jpa)?

      Comments?

      Regards,
      Gail

      [1] https://github.com/hibernate/hibernate-orm/blob/5.2/migration-guide.adoc
      [2] https://github.com/gbadner/hibernate-core/blob/exception-
      compatibility/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/
      ORMTransientObjectExceptionUnitTestCase.java
      [3] https://github.com/gbadner/hibernate-core/blob/exception-
      compatibility/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/
      ORMConstraintViolationExceptionUnitTestCase.java

      Attachments

        Issue Links

          Activity

            People

              gbadner@redhat.com Gail Badner (Inactive)
              msimka@redhat.com Martin Simka
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: