26

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

:)

4

5 回答 5

6

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

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

当然,如果两个线程并行执行相同的检查,可能会出现竞争条件,但这种情况非常罕见,并且数据库约束可以保证唯一性。

于 2012-05-08T06:36:01.893 回答
3

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

你可以使用它们。尝试检查异常的类型或重载错误处理方法(这样更好)。EntityExistsException 我认为您在上面给出的示例中搜索的错误。但是您应该自己检查“是否存在”。这是最好的做法。

永远不需要向用户显示 SQL 错误。那个错误总是给你的。任何需要通知用户的数据相关错误必须手动检查。

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

于 2012-05-08T06:40:56.837 回答
3

我在我的数据库服务层中做了类似的事情,以确定异常是由冲突的约束还是一般的数据库故障引起的:

try {
....
} catch (final PersistenceException e) {
    final Throwable cause = e.getCause();
    if (cause instanceof MySQLIntegrityConstraintViolationException) {
        throw new ConflictException(cause);
    }
    throw new ServiceException(e);
}
于 2014-05-21T12:39:21.983 回答
3

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

DataAccessExceptionin 的子类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.
于 2014-05-21T12:59:13.503 回答
1

我不喜欢进行查询以检查是否可以插入数据的想法,因为它添加了往返以检查数据库服务器无论如何都会进行检查,而且它甚至在每种情况下都不起作用,因为数据库可以在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'
于 2018-08-13T13:18:45.830 回答