I am developing an EJB application to run on glassfish v3. If I look at the javadoc for the EntityManager class it says that methods like find, persist etc throw exceptions derived from PersistenceException. However, in practice I notice that exceptions derived from org.eclipse.persistence.exceptions.DatabaseException can be thrown if something goes wrong at the database level (a table can't be found for example). So am right in assuming that in addition to the standard persistence exceptions, I also have to handle exceptions thrown by whichever persistence provider I am using? That would tend to imply that I need to write error code specific to the JPA provider I choose and if I change to a different one later I need to change my code to catch a different exception class such as HibernateException.
In my opinion, you should only handle exceptions from the standard JPA exception hierarchy (unless you want to deal with a particular case for which the specification doesn't have a standard exception in which case your application won't be portable - but I can't think of any out of my head). The EJB 3.0 JPA spec (JSR 220) summarize them in the section 3.7:
3.7 Summary of Exceptions
The following is a summary of the exceptions defined by this specification:
PersistenceException
The
PersistenceException
is thrown by the persistence provider when a problem occurs. It may be thrown to report that the invoked operation could not complete because of an unexpected error (e.g., failure of the persistence provider to open a database connection).
All other exceptions defined by this specification are subclasses of thePersistenceException
. All instances ofPersistenceException
except for instances ofNoResultException
andNonUniqueResultException
will cause the current transaction, if one is active, to be marked for rollback.
TransactionRequiredException
The TransactionRequiredException is thrown by the persistence provider when a transaction is required but is not active.
OptimisticLockException
The
OptimisticLockException
is thrown by the persistence provider when an optimistic locking conflict occurs. This exception may be thrown as part of an API call, at flush, or at commit time. The current transaction, if one is active, will be marked for rollback.
RollbackException
The
RollbackException
is thrown by the persistence provider whenEntityTransaction.commit
fails.
EntityExistsException
The
EntityExistsException
may thrown by the persistence provider when the per- sist operation is invoked and the entity already exists. TheEntityExistsException
may be thrown when the persist operation is invoked, or theEntityExistsException
or anotherPersistenceException
may be thrown at commit time.
EntityNotFoundException
The
EntityNotFoundException
is thrown by the persistence provider when an entity reference obtained bygetReference
is accessed but the entity does not exist. It is also thrown by the refresh operation when the entity no longer exists in the database. The current transaction, if one is active, will be marked for rollback.
NoResultException
The
NoResultException
is thrown by the persistence provider whenQuery.getSingleResult
is invoked and there is no result to return. This exception will not cause the current transaction, if one is active, to be marked for roll back.
NonUniqueResultException
The
NonUniqueResultException
is thrown by the persistence provider whenQuery.getSingleResult
is invoked and there is more than one result from the query. This exception will not cause the current transaction, if one is active, to be marked for roll back.
For me, provider-specific exceptions are "internal" stuff most of time used to indicate technical problems i.e. bugs in your application that should be fixed (e.g. if a table is missing, it's a bug, fix it, it doesn't make sense to handle this kind of exception).
This is generally why the Spring framework translates implementation-specific exceptions into implementation-independent exceptions. If you are worried about the coupling of catching "internal" exception types you can write exception proxying code in your persistence layer to catch DatabaseException
and rethrow your own ShanesPersistenceException extends RuntimeException
(or whatever) with the original exception included as the cause. Then if you switch implementations later on you only have to touch the exception proxying to add a catch-and-rethrow for the new vendor's exception.
I did some more experimentation with this. When I switched to Hibernate I found that if I catch the exception in the bean that is calling the EntityManager method as a Throwable then if something happens which is outside of the cases supported by the PersistenceException subclasses (such as a table is missing) then the HibernateException gets wrapped in a plain Persistence Exception, which makes sense. If you want to know if something went wrong with persistence you can just catch PersistenceException.
In the TopLink case I receive the Eclipse DatabaseException directly. This seems like a bug to me - as mentioned by Pascal there should be no need to catch provider specific exceptions. I will make a bug report for Glassfish and post a link to the result here.
In both cases if the exception is not caught at the site of EntityManager call the exception will be caught and rethrown as something more general by the container such as EJBException or TransactionRolledBackException, and it may make more sense to catch these in most cases.