Подтвердить что ты не робот

Как вы обрабатываете и разбираете исключения сохраняемости JPA для предоставления значимого сообщения пользователю

Я новичок в JPA и хочу найти лучшие практики при обработке исключений сохранения из JPA для таких вещей, как say, уникальные нарушения ограничений, которые могут быть устранены пользователем. Существует множество примеров того, как писать приложения 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)=([email protected]) already exists.

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

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


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

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

:)

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Ответ 3

Интегрируйте проект 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.

Ответ 4

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

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

Ответ 5

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

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'