如何处理和解析JPA持久性异常以向用户提供有意义的消息

问题描述 投票:25回答:5

我是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)=([email protected]) already exists.

如何才能将有意义的答案反馈给用户?例如:Oops看起来好像有人正在使用该电子邮件地址,您确定以前没有注册吗?是否有内置工具来解析这个或者我是否需要在(可能是一系列)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());
     }

:)

java parsing exception jpa exception-handling
5个回答
5
投票

您通常不使用低级别例外来执行此操作。

相反,您明确检查电子邮件是否可用(使用查询),并仅在电子邮件不存在时对其进行priest。

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


3
投票

存在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块中的另一个类。


3
投票

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

DataAccessExceptionorg.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.

1
投票

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

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

0
投票

我不喜欢进行查询以检查是否可以插入数据的想法,因为它为数据库服务器执行的检查添加了往返,并且它甚至不能在每种情况下都起作用,因为数据库可以在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(Javadocsource code)提供了大量信息(用于生成异常消息):

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

当您在触发器中进行检查时,您可以使用set语法自己USING option = expression这些字段中的许多字段,例如

RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
© www.soinside.com 2019 - 2024. All rights reserved.