I'd really rather not get into supporting arbitrary ExceptionConverter
strategies.. JPA-compliant and then "native"-compliant are plenty.
I thought we had added a `hibernate.jpa.compliance.exception` setting, but
I see it is not there.
On Tue, May 15, 2018 at 1:52 PM Gail Badner <gbadner(a)redhat.com> wrote:
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...
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 ) ) {
handleHibernateException( (HibernateException) ex );
}
else if ( HibernateException.class.isInstance( ex.getCause() ) ) {
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/or...
[3]
https://github.com/gbadner/hibernate-core/blob/exception-compatibility/or...