Is JPA exception translation too simple? Can you chain other translators after it?
Hi,
I am rather dissappointed by the way JPA exception translation works. For example: when violating a unique contraint (i.e. @Column(unique = true)), I get a generic "JpaSystemException" when I would have hoped for a "DataIntegrityViolationException".
I had a look at the Spring 3.0.5 code to check under the hood and found that it tries to perform JPA exception translation using the method jpaconvertJpaAccessExceptionIfPossible() from class
org.springframework.orm.EntityManagerFactoryUtils. In this method, all it does for "generic" persistence exception is:
Code:
// If we have another kind of PersistenceException, throw it.
if (ex instanceof PersistenceException) {
return new JpaSystemException((PersistenceException) ex);
}
To be honest, I was expecting some more effort in exception translation. At the very least a unique constraint violation should end up being a DataIntegrityViolationException.
In this specific case, one could argue that it's also the fault of the JPA spec (Hibernate is throwing a "PersistenceException" which is the only JPA exception that would make sense; the actual cause inside is wrapped within the generic PersistenceException using the hibernate-specific "org.hibernate.exception.ConstraintViolationExcept ion").
I seriously doubt the usefulness of this approach.
1) Does anyone practically rely on this to handle errors?
2) Is it possible to configure a "hibernate-specific" exception translator to be invoked in the case of a PersistenceException; I mean an exception translator that will unwrap the PersistenceException.getCause() and try to translate the actual Hibernate error?
P.S. I'm using JPA (implemented by Hibernate, with MySQL as the database).
Ok, so what could be wrong?
So, here's my setup. Let me know if you see something missing.
I'm using AspectJ with compile-time-weaving. The JpaExceptionTranslatorAspect automatically kicks in to perform the translation. For example, here is a stack trace of a domain entity that threw the exception:
Code:
Caused by: org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not insert: [model.Account]; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not insert: [model.Account]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:311)
at org.springframework.orm.jpa.aspectj.JpaExceptionTranslatorAspect.ajc$afterThrowing$org_springframework_orm_jpa_aspectj_JpaExceptionTranslatorAspect$1$18a1ac9(JpaExceptionTranslatorAspect.aj:15)
at assets.persistence.repository.GenericDaoImpl.save(GenericDaoImpl.java:64)
at assets.persistence.model.AggregateRoot.save(AggregateRoot.java:21)
at model.Account.submitForRegistration(Account.java:103)
at webui.registration.RegisterForm.onSubmit(RegisterForm.java:158)
at org.apache.wicket.markup.html.form.Form$10.component(Form.java:1164)
at org.apache.wicket.markup.html.form.Form$10.component(Form.java:1159)
at org.apache.wicket.util.visit.Visits.visitPostOrderHelper(Visits.java:276)
at org.apache.wicket.util.visit.Visits.visitPostOrder(Visits.java:247)
at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1157)
at org.apache.wicket.markup.html.form.Form.process(Form.java:810)
at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:742)
at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:684)
... 34 more
My entity manager is configured as follows (I use the HibernateJpaVendorAdapter to indicate that Spring should adjust itself to the Hibernate JPA implementation):
Code:
<bean
id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name=" value="MY_PU" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="${database.platform}" />
<property name="showSql" value="${database.showSql}" />
<property name="generateDdl" value="${database.generateDdl}" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.ejb.naming_strategy">
org.hibernate.cfg.ImprovedNamingStrategy
</prop>
<prop key="hibernate.hbm2ddl.auto">
${database.hibernate.hbm2ddl}
</prop>
<prop key="hibernate.transaction.manager_lookup_class">
com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
</prop>
</props>
</property>
</bean>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
Note that afaik, for AspectJ there is no need to register the "PersistenceExceptionTranslationPostProcessor" as it does not apply to @Configurable classes.
In any case, I tried both with and without it (in the context above it is shown), with the same results.