web-dev-qa-db-fra.com

comment gérer et analyser les exceptions de persistance JPA pour fournir un message significatif à l'utilisateur

Je suis assez nouveau dans JPA et souhaite trouver les meilleures pratiques en matière de gestion des exceptions de persistance de JPA, par exemple pour des violations de contraintes uniques pouvant/ pouvant être résolues par l'utilisateur. Il existe une multitude d'exemples sur la manière d'écrire des applications JPA, mais presque rien sur la manière de transmettre des exceptions expulsées par celles-ci. : / 

Par exemple, lors de l'enregistrement d'un utilisateur, la personne entre une adresse électronique déjà utilisée par le système et obtient une violation de contrainte:

try {
     em.persist(credentials);
} catch (javax.persistence.PersistenceException ex) {

qui produit cette erreur quand un email dupliqué est ajouté:

WARNING: SQL Error: 0, SQLState: 23505
SEVERE: ERROR: duplicate key value violates unique constraint "EMAIL_UQ_IDX"
  Detail: Key (email)=([email protected]) already exists.

Comment puis-je obtenir une réponse significative à l'utilisateur? Par exemple, quelque chose comme: Oops ressemble à quelqu'un utilise déjà cette adresse e-mail, êtes-vous sûr de ne pas vous être enregistré auparavant? Existe-t-il une fonction intégrée permettant d'analyser cette information ou dois-je exécuter des expressions rationnelles avec le message d'exception dans une (éventuellement une série) d'instructions if?

Et que se passe-t-il si cela se trouve au niveau de l'entreprise ... Quelles sont les meilleures pratiques pour passer au niveau de la présentation ... comme je l'ai déjà dit, afin qu'un message "Nice" puisse être fourni à l'utilisateur.


Ajoutons pour plus de clarté: Pour que les gens sachent, j’ai eu, et j’examine toujours tous les différents types d’exceptions de persistance, et voici quelques-unes des recherches que j’ai effectuées que je n’ai pas incluses dans les " try statement "exemple que j'ai inclus ci-dessus:

try {
     em.persist(credentials);
     } catch (javax.persistence.PersistenceException ex) {
         System.out.println("EXCEPTION CLASS NAME: " + ex.getClass().getName().toString());
         System.out.println("THROWABLE CLASS NAME: " + ex.getCause().getClass().getName().toString());
                Throwable th = ex.getCause();
         System.out.println("THROWABLE INFO: " + th.getCause().toString());
         Logger.getLogger(CredentialsControllerImpl.class
              .getName()).log(Level.INFO, "Credentials Controller "
                  + "persistence exception "
                      + "EXCEPTION STRING: {0}", ex.toString());
         Logger.getLogger(CredentialsControllerImpl.class
              .getName()).log(Level.INFO, "Credentials Controller "
                  + "persistence exception "
                      + "THROWABLE MESSAGE: {0}", th.getMessage());
         Logger.getLogger(CredentialsControllerImpl.class
              .getName()).log(Level.INFO, "Credentials Controller "
                  + "persistence exceptions "
                      + "THROWABLE STRING: {0}", th.toString());
     }

:)

24
Bill Rosmus

Vous n'utilisez généralement pas les exceptions de bas niveau pour le faire. 

Au lieu de cela, vous vérifiez explicitement que le courrier électronique est disponible (à l'aide d'une requête) et ne le périst que s'il n'existe pas. 

Bien sûr, il pourrait y avoir une situation de concurrence critique si deux threads effectuent la même vérification en parallèle, mais ce sera extrêmement rare et la contrainte de la base de données est là pour garantir l'unicité.

5
JB Nizet

Il existe des sous-classes de PersistenceException: EntityExistsException, EntityNotFoundException, NonUniqueResultException, NoResultException, OptimisticLockException, OptimalLockException, RollbackException, TransactionRequiredException . Source: http://docs.Oracle.com/javaee/5/api/pavatar

Vous pouvez les utiliser. Essayez de vérifier le type d'exception ou de surcharger une méthode de traitement des erreurs (ce qui est préférable). EntityExistsException Je pense que l'erreur que vous recherchez dans l'exemple que vous avez donné ci-dessus. Mais vous devriez vérifier "s'il existe" vous-même. C'est la meilleure pratique.

L'erreur SQL ne devrait jamais être nécessaire pour être montrée à l'utilisateur. Cette erreur est toujours pour vous. Toute erreur liée aux données nécessitant d’informer l’utilisateur doit être vérifiée manuellement.

J'utilise l'environnement Web J2EE. Je viens de transmettre la demande à error.jsp s'il y a une exception. Je fournis également un objet supplémentaire pour error.jsp afin de clarifier les informations telles que l'utilisateur peut revenir en arrière, quelle page après erreur, etc. Bien sûr, j'ai automatisé cela, je n'aime pas écrire du code redondant, car il est difficile à mettre à jour. Donc, je viens d'écrire envoyer l'exception et le message d'erreur à une autre classe dans le bloc catch.

3
Ata S.

Intégrez votre projet JPA à Spring et laissez spring lancer les exceptions sous la forme DataAccessException

Sous-classes de DataAccessException dans 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.
3
FiguringLife

Je fais quelque chose de similaire dans ma couche de service de base de données pour déterminer si l'exception est due à une contrainte en conflit ou à un échec général de la base de données:

try {
....
} catch (final PersistenceException e) {
    final Throwable cause = e.getCause();
    if (cause instanceof MySQLIntegrityConstraintViolationException) {
        throw new ConflictException(cause);
    }
    throw new ServiceException(e);
}
1
Pepster

Je n'aime pas l'idée de faire une requête pour vérifier si les données peuvent probablement être insérées, car cela ajoute de toute façon des allers-retours pour un contrôle effectué par le serveur de base de données. SELECT et la INSERT (bien que cela puisse dépendre de la façon dont vous traitez les transactions).

Quoi qu’il en soit, gérer l’erreur me semble être la seule option sûre, c’est «gratuit» (pas de contrôle redondant, pas d’aller-retour supplémentaire) et ce n’est pas difficile. Mais cela dépend de votre pilote JDBC. Par exemple, avec PostgreSQL, vous pouvez faire:

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 , code source ) fournit de nombreuses informations (utilisées pour générer le message d'exception):

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());

Lorsque vous archivez un déclencheur, vous pouvez définir plusieurs de ces champs vous-même en utilisant la syntaxe USING option = expression, par exemple

RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
0
Martin