如何处理和解析 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());
     }

:)

通常不使用低级异常来执行此操作。

相反,您可以显式检查电子邮件是否可用(使用查询(,并且仅在电子邮件不存在时才保留电子邮件。

当然,如果两个线程并行执行相同的检查,则可能存在争用条件,但这种情况极为罕见,并且数据库约束是为了保证唯一性。

有 PersistenceException 的子类:EntityExistsException、EntityNotFoundException、NonUniqueResultException、NoResultException、OptimisticLockException、RollbackException、TransactionRequiredException。来源: http://docs.oracle.com/javaee/5/api/javax/persistence/PersistenceException.html

您可以使用它们。尝试检查异常类型或重载错误处理方法(哪个更好(。实体存在异常我认为您在上面给出的示例中搜索的错误。但是您应该自己检查"是否存在"。这是最佳实践。

永远不需要向用户显示 SQL 错误。该错误始终适合您。任何需要通知用户的数据相关错误都必须手动检查。

我使用 J2EE Web 环境。如果有异常.jsp我只是将请求转发给错误。我还为错误提供了一个额外的对象.jsp以澄清诸如用户可以返回,错误后可以转到哪个页面等信息。当然,我自动化了这个,我不喜欢编写冗余代码,因为它很难更新。所以我只是写将异常和错误消息发送到 catch 块中的另一个类。

我正在数据库服务层中执行类似操作,以确定异常是由冲突约束还是常规数据库故障引起的:

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

将你的 JPA 项目与 Spring 集成,让 spring 抛出异常作为DataAccessException

org.springframework.daoDataAccessException的子类

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.

我不喜欢执行查询来检查是否可以插入数据的想法,因为它为数据库服务器所做的检查添加了往返行程,并且它甚至不是在所有情况下都有效,因为数据库可以在SELECTINSERT之间更改(尽管这可能取决于您如何处理事务(。

无论如何,处理错误对我来说是唯一安全的选择,它是"免费的"(没有多余的检查,没有额外的往返(,而且不难做到。但这取决于您的 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'

最新更新