ユーザー・プラグイン実装が例外をスローすると、eXtreme Scale は、throws 契約内に定義された特定の例外をチェックします。 しかし、時にはチェックなし例外に契約例外が含まれたり、例外が適切に契約を監視しなかったりすることがあります。そのため、可能であれば、例えば ExceptionMapper など、例外を契約例外にマップするメカニズムが必要になります。
データベース・サーバーまたはネットワークが作動不可であるときや、データベースがリソースを使い尽くしたときに、JPALoader が LoaderNotAvailableException をスローしなければならない場合を検討します。 しかし、JPA プロバイダー実装は通常、SQL 状態またはエラー・コードがデータベース・サーバー問題またはネットワーク問題を示す可能性のある SQLException を伴う一般 PersistenceException をスローするだけです。 このような問題についてデータベースが異なれば SQL 状態やエラー・コードも異なるということが、状況をさらに複雑にします。 したがって、例外マッピングのメカニズムは、データベースに固有である必要があります。
この問題の解決するために、ExceptionMapper インターフェースが使用されます。このインターフェースには、オリジナルの例外を消費可能な例外にマップする Throwable map(Throwable original) というメソッドがあります。
例えば、上記の問題を解決するために、実装クラスが JPA 例外の中でチェーニングされた java.sql.SQLException の SQL 状態とエラー・コードをイントロスペクトすることもできます。 このようにすると、SQL 状態またはエラー・コードが、データベース・サーバーまたはネットワークが作動不可であったり、データベースがリソースを使い尽くしたりしたことを示した場合に、実装クラスが LoaderNotAvailableException をスローできます。
現在、ExceptionMapper Bean は、JPATxCallback ObjectGrid Bean の中にのみ構成できます。ExceptionMapper Bean は、JPATxCallback から受け取ったすべての例外を、JPALoader Bean または JPAEntityLoader Bean にマップするために使用します。
ExceptionMapper を構成するには、JPATxCallback Bean 内の ExceptionMapper Bean に対して、Spring スタイルの構成を使用する必要があります。
JPALoader に Spring スタイルの構成を使用する方法については、JPA ローダーの構成を参照してください。
次の例では、データベース・サーバー問題やネットワーク問題が示された場合に、JPA 例外を LoaderNotAvailableException にマップしています。この ExceptionMapper 実装は、JPA プロバイダーを MSSQL データベースと一緒に使用するためのものです。 まず、特定のネットワーク問題またはデータベース・サーバー問題を示す、一連の SQL エラー・コードと SQL 状態を定義します。マップ・メソッドの中で、SQL エラー・コードを、最初に既知のエラー・コードのリストに対して、次に SQL 状態に対して、最後にメッセージに対してチェックします。そのうちのいずれか 1 つが一致すれば、LoaderNotAvaliableException をスローします。
public class JPAMSSQLExceptionMapper implements ExceptionMapper {
static Set<Integer> loaderNotAvailableSQLErrorSet = new HashSet<Integer>();
static Set<String> loaderNotAvailableSQLStateSet = new HashSet<String>();
static {
addInitialMaps();
}
public C3P0MSSQLExceptionMapper() {
System.out.println("C3P0MSSQLExceptionMapper is constructed");
}
/**
* @internal
* This method is used to add intial maps to the hash, this is used
* internally, and it is not for public view
*/
private static void addInitialMaps() {
// http://msdn.microsoft.com/en-us/library/cc645603.aspx
loaderNotAvailableSQLErrorSet.add(new Integer(230));
loaderNotAvailableSQLErrorSet.add(new Integer(6002));
// http://white-box.us/2009/03/08/sql-92-sqlstate-codes/
/*
* 08001 SQL client unable to establish SQL connection
* 08002 connection name in use
* 08003 connection does not exist
* 08004 SQL server rejected SQL connection
* 08006 connection failure
* 08007 transaction resolution unknown
*/
loaderNotAvailableSQLStateSet.add("08000");
loaderNotAvailableSQLStateSet.add("08001");
loaderNotAvailableSQLStateSet.add("08002");
loaderNotAvailableSQLStateSet.add("08003");
loaderNotAvailableSQLStateSet.add("08004");
loaderNotAvailableSQLStateSet.add("08006");
loaderNotAvailableSQLStateSet.add("08007");
// http://msdn.microsoft.com/en-us/library/ms714687.aspx
loaderNotAvailableSQLStateSet.add("08S01");
loaderNotAvailableSQLStateSet.add("HY000");
}
private static Pattern[] sqlServerMessagePatterns = new Pattern[] {
Pattern.compile("The TCP/IP connection to the host .* has failed.*"), Pattern.compile(".*Connection reset.*") };
private static Pattern[] sqlExceptionMessagePatterns = new Pattern[] { Pattern
.compile(".*Connections could not be acquired from the underlying database.*") };
private static String connection_reset = "Connection reset";
public Throwable map(Throwable originalEx) {
Throwable cause = originalEx;
while (cause != null) {
// keep looping to check the next chained exception
if (cause instanceof SQLException) {
// Only check if the exception is an SQLException
SQLException sqle = (SQLException) cause;
// If the loader not available SQL state set contains this SQL state, then
// we return a LoaderNotAvailableException with the original exception chained in it.
if (loaderNotAvailableSQLStateSet.contains(sqle.getSQLState())) {
return new LoaderNotAvailableException(originalEx);
}
// If the loader not available SQL error code set contains this error code, then
// we return a LoaderNotAvailableException with the original exception chained in it
if (loaderNotAvailableSQLErrorSet.contains(new Integer(sqle.getErrorCode()))) {
return new LoaderNotAvailableException(originalEx);
}
String msg = sqle.getMessage();
for (int i=0; i<sqlExceptionMessagePatterns.length; i++) {
if (sqlExceptionMessagePatterns[i].matcher(msg).matches()) {
return new LoaderNotAvailableException(originalEx);
}
}
} else if (cause.getClass().getName().equals("com.microsoft.sqlserver.jdbc.SQLServerException")) {
String msg = cause.getMessage();
for (int i=0; i<sqlServerMessagePatterns.length; i++) {
if (sqlServerMessagePatterns[i].matcher(msg).matches()) {
return new LoaderNotAvailableException(originalEx);
}
}
System.out.println("msg " + msg + " does not match");
}
// Get the next chained exception
Throwable newCause = cause.getCause();
// Safe-guard check to avoid indefinite loop if the exception chains itself
if (newCause == cause) {
// Always return the original exception if cannot map it.
return originalEx;
} else {
cause = newCause;
}
}
// Always retrun the original exception if cannot map it.
return originalEx;
}