Как вы обрабатываете и анализируете исключительные ситуации JPA, чтобы предоставить пользователю полезное сообщение

Я довольно новичок в JPA и хочу найти лучшие практики при обработке исключений постоянства из JPA для таких вещей, как, например, уникальные нарушения ограничений, которые могут быть устранены пользователем. Существует множество примеров того, как писать приложения на JPA, но почти ничего не говорится о том, как передавать исключенные ими исключения.:/

Например, при регистрации пользователя человек вводит адрес электронной почты, который уже активно используется системой, и получает нарушение ограничения:

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

которая выдает эту ошибку при добавлении дубликата электронного письма:

WARNING: SQL Error: 0, SQLState: 23505
SEVERE: ERROR: duplicate key value violates unique constraint "EMAIL_UQ_IDX"
  Detail: Key (email)=(testuser@xyz.com) already exists.

Как я могу получить значимый ответ обратно пользователю? Например, что-то вроде: К сожалению, похоже, что кто-то уже использует этот адрес электронной почты. Вы уверены, что не регистрировались ранее? Есть ли встроенное средство для анализа этого или мне нужно будет запускать регулярные выражения для сообщения об исключении в (возможно, серии) операторе if?

А как насчет того, чтобы оно попало на бизнес-уровень... каковы наилучшие способы доведения его до уровня представления... как я уже говорил, чтобы пользователю было предоставлено "приятное" сообщение.


Добавлено для ясности: просто чтобы люди знали, я имел, имел и все еще смотрю на все различные типы исключений для постоянства, и вот некоторые из исследований, которые я проводил, которые я не включил в "оператор try" пример, который я включил выше:

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

:)

5 ответов

Решение

Обычно вы не используете низкоуровневые исключения для этого.

Вместо этого вы явно проверяете, что электронная почта доступна (используя запрос), и портите электронную почту, только если она не существует.

Конечно, может быть условие состязания, если два потока выполняют одну и ту же проверку параллельно, но это будет крайне редко, и существует ограничение базы данных, чтобы гарантировать уникальность.

Я делаю нечто подобное на уровне службы БД, чтобы выяснить, было ли исключение вызвано конфликтующим ограничением или общим отказом БД:

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

Существуют подклассы PersistenceException: EntityExistsException, EntityNotFoundException, NonUniqueResultException, NoResultException, OptimisticLockException, RollbackException, TransactionRequiredException. Источник: http://docs.oracle.com/javaee/5/api/javax/persistence/PersistenceException.html

Вы можете использовать их. Попробуйте проверить тип исключения или перегрузить метод обработки ошибок (что лучше). EntityExistsException Я думаю, что ошибка, которую вы ищете в приведенном выше примере. Но вы должны проверить "если он существует" самостоятельно. Это лучшая практика.

Ошибка SQL никогда не должна показываться пользователю. Эта ошибка всегда для вас. Любая ошибка, связанная с данными, которая требует информирования пользователя, должна быть проверена вручную.

Я использую J2EE Web Environment. Я просто пересылаю запрос в error.jsp, если есть исключение. Я также предоставляю дополнительный объект для error.jsp, чтобы уточнить информацию о том, как пользователь может вернуться назад, может ли перейти на какую страницу после ошибки и т. Д. Конечно, я автоматизировал это, мне не нравится писать избыточный код, потому что его сложно обновить. Поэтому я просто пишу отправить исключение и сообщение об ошибке в другой класс в блоке catch.

Интегрируйте свой проект JPA с Spring и позвольте Spring выбросить исключения как DataAccessException

Подклассы DataAccessException в 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.

Мне не нравится идея сделать запрос, чтобы проверить, могут ли данные быть вставлены, потому что он добавляет обходные пути для проверки, выполняемой сервером базы данных, и даже не работает в каждом случае, потому что база данных может меняться между SELECT и INSERT (хотя это может зависеть от того, как вы обрабатываете транзакции).

В любом случае, обработка ошибки выглядит для меня как единственная безопасная опция: она "бесплатная" (без лишних проверок, без дополнительных обходов) и ее нетрудно сделать. Но это зависит от вашего драйвера JDBC. Например, с PostgreSQL вы можете сделать:

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 предоставляет много информации (которая используется для генерации сообщения об исключении):

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

Когда вы делаете проверки в триггере, вы можете установить многие из этих полей самостоятельно, используя USING option = expression синтаксис, например

RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
Другие вопросы по тегам