web-dev-qa-db-ja.com

JPA永続性例外を処理および解析して、ユーザーに意味のあるメッセージを提供する方法

私はJPAにかなり慣れていないため、たとえばcanでユーザーが対処できる一意の制約違反など、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());
     }

:)

25
Bill Rosmus

通常、そのために低レベルの例外を使用しません。

代わりに、(クエリを使用して)メールが利用可能であることを明示的に確認し、メールが存在しない場合にのみメールを永続化します。

確かに、2つのスレッドが同じチェックを並行して実行すると競合状態が発生する可能性がありますが、非常にまれであり、データベースの制約によって一意性が保証されます。

6
JB Nizet

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ブロックの別のクラスに例外とエラーメッセージを送信するように書くだけです。

3
Ata S.

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.
3
FiguringLife

例外の原因が競合する制約によるものか、一般的なDBの障害によるものかを把握するために、DBサービスレイヤーで同様のことを行っています。

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

クエリを実行してデータを挿入できるかどうかを確認するのは好きではありません。データベースサーバーが実行するチェックに往復が追加され、データベースが変更される可能性があるため、すべてのケースで機能しません。 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( 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());

トリガーでチェックを行う場合、USING option = expression構文を使用して、これらのフィールドの多くを自分で set できます。次に例を示します。

RAISE integrity_constraint_violation USING CONSTRAINT = 'EMAIL_UQ_IDX'
0
Martin