web-dev-qa-db-ger.com

jPA-Persistenzausnahmen, um den Benutzer eine aussagekräftige Nachricht zu übermitteln

Ich bin relativ neu bei JPA und möchte Best Practices beim Umgang mit Persistenz-Ausnahmen von JPA finden, zum Beispiel bei eindeutigen Einschränkungen, die can vom Benutzer angesprochen werden können. Es gibt unzählige Beispiele, wie man JPA-Apps schreibt, aber fast nichts darüber, wie man Ausnahmen ausgibt, die von ihnen weggeschmissen werden. : / 

Wenn Sie beispielsweise einen Benutzer registrieren, gibt die Person eine E-Mail-Adresse ein, die bereits vom System verwendet wird, und erhält eine Beschränkungsverletzung:

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

was zu diesem Fehler führt, wenn eine doppelte E-Mail hinzugefügt wird:

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

Wie bekomme ich eine sinnvolle Antwort an den Benutzer? Zum Beispiel: Oops sieht so aus, als würde jemand diese E-Mail-Adresse bereits verwenden. Sind Sie sicher, dass Sie sich noch nicht registriert haben? Gibt es eine eingebaute Funktion, um dies zu analysieren, oder muss ich Regex für die Ausnahmemeldung in einer (möglicherweise einer Reihe von) if-Anweisung ausführen?

Und wie sieht es aus, wenn es auf der Business-Ebene hängen geblieben ist ... welche bewährten Methoden es gibt, um es auf die Präsentations-Ebene zu bringen ... wie ich schon sagte, so dass dem Benutzer eine 'Nice'-Nachricht bereitgestellt werden kann.


Zur Klarheit hinzugefügt: Nur damit die Leute wissen, ich hatte, habe und betrachte immer noch die verschiedenen Arten von Ausnahmen für die Persistenz, und hier sind einige der Untersuchungen, die ich gemacht habe und die ich nicht mit einbezogen habe Das Beispiel "try statement", das ich oben eingefügt habe:

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

Sie verwenden in der Regel nicht über die Low-Level-Ausnahmen zu tun. 

Stattdessen überprüfen Sie ausdrücklich, dass die E-Mail (mit einer Abfrage) zur Verfügung steht, und perist die E-Mail nur dann, wenn sie noch nicht existiert. 

Sicher, es könnte eine Race-Bedingung sein, wenn zwei Threads die gleiche Prüfung parallel durchführen. Dies ist jedoch äußerst selten, und die Datenbankeinschränkung dient dazu, die Eindeutigkeit zu gewährleisten.

5
JB Nizet

Es gibt Unterklassen von PersistenceException. EntityExistsException, EntityNotFoundException, NonUniqueResultException, NoResultException, OptimisticLockException, Rollback, TransactionRequiredException [. .____] Quelle: http://docs.Oracle.com/javaee/5/api/javax/persistence/ PersistenceException.html

Sie können sie verwenden. Versuchen Sie, den Ausnahmetyp zu überprüfen oder eine Fehlerbehandlungsmethode zu überladen (was besser ist). EntityExistsException Ich denke, der Fehler, den Sie in dem obigen Beispiel suchen. Sie sollten jedoch selbst prüfen, ob es existiert. Das ist die beste Praxis.

Der SQL-Fehler sollte niemals für den Benutzer angezeigt werden. Dieser Fehler ist immer für Sie. Jeder datenbezogene Fehler, der den Benutzer informieren muss, muss manuell geprüft werden.

Ich verwende J2EE Web Environment. Ich leite die Anfrage einfach an error.jsp weiter, wenn eine Ausnahme vorliegt. Ich stelle auch ein zusätzliches Objekt für error.jsp zur Verfügung, um die Informationen zu klären, wie der Benutzer zurückgehen kann, welche Seite nach einem Fehler angezeigt werden kann. Natürlich habe ich dies automatisiert. Ich mag es nicht, redundanten Code zu schreiben, da er schwer zu aktualisieren ist. Ich schreibe also einfach die Ausnahme- und Fehlermeldung an eine andere Klasse im catch-Block.

3
Ata S.

Integrieren Sie Ihr JPA-Projekt mit Spring und lassen Sie die Ausnahmen als Frühling als DataAccessException werfen.

Subklassen von 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.
3
FiguringLife

Ich mache etwas Ähnliches in meiner DB-Service-Schicht, um herauszufinden, ob die Ausnahme durch eine Konfliktbedingung oder einen allgemeinen DB-Fehler verursacht wurde:

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

Ich mag die Idee nicht, eine Abfrage durchzuführen, um zu prüfen, ob die Daten wahrscheinlich eingefügt werden können, weil dadurch Roundtrips für eine Überprüfung hinzugefügt werden, die der Datenbankserver trotzdem macht, und es funktioniert nicht in jedem Fall, da die Datenbank zwischen den Datenbanken wechseln kann SELECT und INSERT (dies kann jedoch davon abhängen, wie Sie Transaktionen abwickeln).

Wie auch immer, die Fehlerbehandlung scheint für mich die einzige sichere Option zu sein, sie ist "kostenlos" (keine redundanten Überprüfungen, keine zusätzlichen Roundtrips) und es ist nicht schwer zu tun. Das hängt jedoch von Ihrem JDBC-Treiber ab. Mit PostgreSQL können Sie beispielsweise Folgendes tun:

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 , Quellcode ) enthält viele Informationen (die zum Generieren der Ausnahmemeldung verwendet werden):

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

Wenn Sie einen Trigger einchecken, können Sie set viele dieser Felder selbst festlegen, indem Sie beispielsweise die USING option = expression-Syntax verwenden

RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
0
Martin