I am fairly new to JPA and want to find best practices when handling persistence exceptions from JPA for things like say, unique constraint violations which can be
I don’t like the idea of doing a query to check if the data can probably be inserted because it adds round-trips for a check the database server does anyway and it does not even work in every case because the database can change between the SELECT
and the INSERT
(though this may depend on how you handle transactions).
Anyway, handling the error looks like the only safe option to me, it’s “free” (no redundant checks, no additional round-trips) and it’s not hard to do. But it depends on your JDBC driver. For example, with PostgreSQL you can do:
try {
em.persist(credentials);
} catch (javax.persistence.PersistenceException ex) {
// use a loop to get the PSQLException
for (Throwable current = ex; current != null; current = current.getCause()) {
if (current instanceof PSQLException) {
final PSQLException psqlEx = (PSQLException) current;
final ServerErrorMessage serverErrorMessage = psqlEx.getServerErrorMessage();
if ("EMAIL_UQ_IDX".equals(serverErrorMessage.getConstraint())) {
// handle duplicate E-Mail address
}
break;
}
}
}
ServerErrorMessage (Javadoc, source code) provides a lot of information (which is used to generate the exception message):
System.out.println(serverErrorMessage.getColumn());
System.out.println(serverErrorMessage.getConstraint());
System.out.println(serverErrorMessage.getDatatype());
System.out.println(serverErrorMessage.getDetail());
System.out.println(serverErrorMessage.getFile());
System.out.println(serverErrorMessage.getHint());
System.out.println(serverErrorMessage.getInternalPosition());
System.out.println(serverErrorMessage.getInternalQuery());
System.out.println(serverErrorMessage.getLine());
System.out.println(serverErrorMessage.getMessage());
System.out.println(serverErrorMessage.getPosition());
System.out.println(serverErrorMessage.getRoutine());
System.out.println(serverErrorMessage.getSQLState());
System.out.println(serverErrorMessage.getSchema());
System.out.println(serverErrorMessage.getSeverity());
System.out.println(serverErrorMessage.getTable());
System.out.println(serverErrorMessage.getWhere());
When you do checks in a trigger, you can set many of these fields yourself by using the USING option = expression
syntax, for example
RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
You typically don't use the low-level exceptions to do that.
Instead, you explicitely check that the email is available (using a query), and perist the email only if it doesn't exist.
Sure, there could be a race condition if two threads do the same check in parallel, but it will be extremely rare, and the database constraint is there to guarantee the uniqueness.
I'm doing something similar in my DB service layer to figure out whether the exception was caused by a conflicting constraint or general DB failure:
try {
....
} catch (final PersistenceException e) {
final Throwable cause = e.getCause();
if (cause instanceof MySQLIntegrityConstraintViolationException) {
throw new ConflictException(cause);
}
throw new ServiceException(e);
}
There are subclasses of PersistenceException: EntityExistsException, EntityNotFoundException, NonUniqueResultException, NoResultException, OptimisticLockException, RollbackException, TransactionRequiredException. Source: http://docs.oracle.com/javaee/5/api/javax/persistence/PersistenceException.html
You can use them. Try to check the type of exception or overload an error handling method (which is better). EntityExistsException I think the error you are searching for in the example you gave above. But you should check "if it exists" yourself. That's the best practice.
The SQL Error should never be needed to shown to the user. That error is always for you. Any data related error which needs informing user must be checked manually.
I use J2EE Web Environment. I just forward the request to error.jsp if there's an exception. I also provide an extra object for error.jsp to clarify the information like user can go back, can go which page after error etc. Of course I automated this, I don't like writing redundant code, because it is hard to update. So I just write send the exception and error message to another class in the catch block.
Integrate your JPA project with Spring and let spring throw the exceptions as DataAccessException
Subclasses of DataAccessException
in org.springframework.dao
class CannotAcquireLockException
Exception thrown on failure to aquire a lock during an update, for example during a "select for update" statement.
class CannotSerializeTransactionException
Exception thrown on failure to complete a transaction in serialized mode due to update conflicts.
class CleanupFailureDataAccessException
Exception thrown when we couldn't cleanup after a data access operation, but the actual operation went OK.
class ConcurrencyFailureException
Exception thrown on concurrency failure.
class DataAccessResourceFailureException
Data access exception thrown when a resource fails completely: for example, if we can't connect to a database using JDBC.
class DataIntegrityViolationException
Exception thrown when an attempt to insert or update data results in violation of an integrity constraint.
class DataRetrievalFailureException
Exception thrown if certain expected data could not be retrieved, e.g.
class DeadlockLoserDataAccessException
Generic exception thrown when the current process was a deadlock loser, and its transaction rolled back.
class EmptyResultDataAccessException
Data access exception thrown when a result was expected to have at least one row (or element) but zero rows (or elements) were actually returned.
class IncorrectResultSizeDataAccessException
Data access exception thrown when a result was not of the expected size, for example when expecting a single row but getting 0 or more than 1 rows.
class IncorrectUpdateSemanticsDataAccessException
Data access exception thrown when something unintended appears to have happened with an update, but the transaction hasn't already been rolled back.
class InvalidDataAccessApiUsageException
Exception thrown on incorrect usage of the API, such as failing to "compile" a query object that needed compilation before execution.
class InvalidDataAccessResourceUsageException
Root for exceptions thrown when we use a data access resource incorrectly.
class OptimisticLockingFailureException
Exception thrown on an optimistic locking violation.
class PermissionDeniedDataAccessException
Exception thrown when the underlying resource denied a permission to access a specific element, such as a specific database table.
class PessimisticLockingFailureException
Exception thrown on a pessimistic locking violation.
class TypeMismatchDataAccessException
Exception thrown on mismatch between Java type and database type: for example on an attempt to set an object of the wrong type in an RDBMS column.
class UncategorizedDataAccessException
Normal superclass when we can't distinguish anything more specific than "something went wrong with the underlying resource": for example, a SQLException from JDBC we can't pinpoint more precisely.