EJB 2.x は、CMP エンティティー Bean のファインダーおよび select メソッドの EJB 照会言語 (EJB QL) と呼ばれる照会構文を提供しています。 finder メソッドは、データベースから 1 つ以上のエンティティー Bean インスタンスを取得するものであり、 ホーム・インターフェースにおいて定義されます。
findByPrimaryKey(key) を除くすべての finder メソッドについて、<query> 要素は、 デプロイメント記述子における finder メソッド用の照会を定義するために使用されます。 デプロイメント記述子で指定された照会は、デプロイメント時に SQL にコンパイルされます。 この照会の構文は、<query> 要素の <ejb-ql> 要素に収められます。
<query>
<query-method>
<method-name></method-name>
<method-params>
<method-param></method-param>
</method-params>
</query-method>
<result-type-mapping></result-type-mapping>
<ejb-ql></ejb-ql>
</query>
詳細については、EJB QL を使用して finder メソッドを EJB 2.x Bean に追加 を参照してください。
EJB QL のさらに詳しい情報が必要な場合は、WebSphere Application Server インフォメーション・センターにアクセスして、 "EJB QL" というキーワードで検索してください。
現在サポートされていて、組み合わせて使用できる EJB カスタム・ファインダーには、次の 3 つの型があります。
EJB ホーム・インターフェースに定義されている finder メソッド (ただし findByPrimaryKey と、アソシエーションをサポートするために生成される finder メソッドを除く) ごとに、 以下の照会ストリングまたは宣言のいずれかをファインダー・ヘルパー・インターフェース (beanClassNameFinderHelper.java ファイル) 内に定義しておく必要があります。
ホーム・インターフェースのファインダーに対する戻りの型 java.util.Enumeration または java.util.Collection は、このファインダーが複数の Bean を返す場合があることを意味します。 リモート・インターフェースを戻りの型として使用するということは、単一 Bean が戻されることを示しています。 このことは、カスタム・ファインダーのサポートされるすべての型に当てはまります。 パーシスター内に生成されたコードはこの違いを処理します。
重要な留意点としては、既存の EJB 1.0 JAR ファイルを使用して作業している場合、 引き続きファインダー・ヘルパー・インターフェースで SQL 照会ストリング、 またはメソッド宣言を定義できることが挙げられます。(SQL 照会ストリングは事実上、 完全な SELECT ステートメントまたは WHERE 節を記述するインターフェース上のフィールドです。) ただし、新規開発作業で 1.0 より高いレベルの EJB JAR ファイルを使用して作業しなければならない場合は、ファインダー・ヘルパー・インターフェースではなく拡張ドキュメントを使用して、照会およびメソッド宣言を定義する必要があります。これについては、このトピックにおいて後ほど説明します。
異種データベース間での SQL の互換性の維持
SELECT、WHERE、およびメソッド・カスタム・ファインダーでは、finder メソッドが異なるデータベースにアクセスする場合があります。 この場合には、SQL の互換性が異なるデータベース間で保持されていることを確認する必要があります。 例えば、それぞれのデータベースで使用される SQL 構文が異なる場合があります。 このような状態では、JDBC によって定義された SQL 拡張子を使用して、データベースの相違を解決します。
例えば、「タイム・スタンプ」/「日付」フィールドを含む finder メソッドを必要とする CMP エンティティー Bean を開発しているとします。また、この Bean を DB2® および Oracle データベースにデプロイするとします。 問題は、DB2 と Oracle のタイム・スタンプ/日付フィールドの形式が異なっているため、 ある WHERE 文節を定義してもそれを DB2 と Oracle の両方で使用することが困難だということです。 こうした特定の問題を解決するために、SQL エスケープ・シーケンスを使用します。
SELECT、WHERE、およびメソッド・カスタム・ファインダーの追加情報については、 以下のセクションを参照してください。
注: カスタム・ファインダーで作業するときは、NULL を渡さないでください。
SELECT カスタム・ファインダーを使用することで、SQL 照会を定義する SELECT ステートメント全体をファインダー・ヘルパー・インターフェースに入力することができます。
SELECT カスタム・ファインダー・ファインダーは、以前のリリースと互換性を保つためにサポートされているものです。本リリース以降のリリースでは、SELECT カスタム・ファインダーの使用は推奨されません。
フィルター操作用の WHERE 節のみをファインダー・ヘルパー・インターフェースに入力するカスタム・ファインダーのことを、 WHERE カスタム・ファインダーといいます。例えば、VAPGarage CMP エンティティー Bean を持っていて、これを CAPACITY という列を持つデータベース・テーブルにマップする場合、 ファインダー・ヘルパー・インターフェースは以下のようになります。
public interface VapGarageBeanFinderHelper {
public final static String
findCapacityGreaterThanWhereClause =
"T1.CAPACITY > ?";
}
結果の形状に関する依存関係はストリングから削除されていることに注意してください。それでも 2 つの依存関係が残っています。
列名は、それを変更するためのアクションを実行した場合にのみ変更されます。
テーブルの別名は、テーブルがマッピングに追加されない限り、またはマッピングから削除されない限り、世代に関係なく同じになります。単一テーブルの場合、このことは重要でない可能性があります (別名は常に T1 です)。 複数のテーブルが使用される場合は、これが非常に重要になります。
手書きの SQL コードにあるテーブル参照は、genericFindSqlString フィールドに設定したテーブル別名と一致していなければなりません。 これは、エンタープライズ Bean の生成済みのパーシスターで宣言されます。
SELECT カスタム形式の場合と同様、ファインダー・パラメーターの数は WHERE 節の挿入点 (? 文字) の数と一致しなければなりません。また SELECT 形式の場合と同様、パラメーターの型は、どの java.sql.PreparedStatement 設定呼び出しを使用して各パラメーターを挿入するのかを決定するために使用されます。 パラメーターの型は列型と互換性がなければなりません。型がオブジェクト型で、パラメーターが NULL の場合は、setNull 呼び出しが使用されます。
例えば、ホーム・インターフェースには、以下のメソッドを含めることができます。
public java.util.Enumeration findGreaterThan (int threshold) throws
java.rmi.RemoteException, javax.ejb.FinderException;
WHERE カスタム・ファインダーは、このファインダー・ヘルパー・インターフェースで、ユーザーによる指定が可能な形式の 1 つです。 以下にそのサンプルを示します (資料のため、改行してあります)。
public static final String findGreaterThanWhereClause =
"T1.VALUE > ?";
ただし、WHERE 文節が含まれていない SQL ステートメント (例えば、SELECT * FROM MYTABLE) には、常に true に評価される照会ストリングを使用しなければならないので、注意してください。例えば:
public static final String findALLWhereClause = "1 = 1";
メソッド・シグニチャーをファインダー・ヘルパー・インターフェースに入力するカスタム・ファインダーのことを、 メソッド・カスタム・ファインダーといいます。これはカスタム・ファインダーの中では最も柔軟な型になります。 ただし、より多くの作業が必要になります。 前と同じガレージのサンプルを使用すると、 ファインダー・ヘルパー・インターフェースは次のようになります。
public interface VapGarageBeanFinderHelper {
public java.sql.PreparedStatement findCapacityGreaterThan(int threshold)
throws Exception;
}
ただし、SELECT および WHERE 形式とは異なり、これはメソッド・カスタム・ファインダー用には不十分です。 このメソッドを実装する必要があります。メソッドの実装を、以下の規則に従うクラスに提供する必要があります。
ここで取り上げたサンプルを仕上げると、ファインダー・オブジェクトは次のようになります。
/**
* Implementation class for methods in
* VapGarageBeanFinderHelper.
*/
public class VapGarageBeanFinderObject extends
com.ibm.vap.finders.VapEJSJDBCFinderObject implements
VapGarageBeanFinderHelper {
public java.sql.PreparedStatement
findCapacityGreaterThan(int threshold)
throws Exception {
PreparedStatement ps = null;
int mergeCount = getMergedWhereCount();
int columnCount = 1;
ps = getMergedPreparedStatement("T1.CAPACITY > ?");
for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) {
ps.setInt(i+1, threshold);
}
return ps;
}
}
メソッド・カスタム・ファインダーの場合は、生成されたパーシスターは、 実装を使用して実行すべき PreparedStatement を作成します。パーシスターは、PreparedStatement を実行し、その結果を処理します。 実装は、照会の結果セットが正しい形状をしていることを保証するために、パーシスターからの支援を受ける必要があります。 com.ibm.vap.finders.VapEJSJDBCFinderObject 基本クラスは、上記のサンプルでいくつか示されているように、 いくつかの重要な helper メソッドを提供します。以下のテーブルで、helper メソッドの完全セットをリストして説明します。
| メソッド | 説明 |
|---|---|
| getMergedPreparedStatement | WHERE 節を取り、 WHERE 節を適切な位置にマージした PreparedStatement を戻します。この PreparedStatement には、正しい結果セット形状が含まれています。 |
| getMergedWhereCount | WHERE 節が PreparedStatement にマージされる回数を戻します。 このメソッドは、照会パラメーターを PreparedStatement に挿入する回数を知るのに必要です。 |
| getPreparedStatement | 完全な照会ストリングを取り、PreparedStatement を戻します。 これは、何らかの理由で独自に WHERE 文節のマージを行う必要が発生した場合に使用することができます。 これはきわめてまれな場合です。次の 2 つの関数は、これらの極端な場合に役立つよう提供されているものです。 |
| getGenericFindSqlString | WHERE 節がマージされている照会ストリングを戻します。 |
| getGenericFindInsertPoints | WHERE 節がマージされている、 getGenericFindSqlString によって戻された照会ストリング内の点を定義する整数の配列を戻します。 配列の最初の点が、ストリングの最後の点になります。一般的には、最後のマージでは照会ストリング内でそれより前にあるマージ位置が変更されないため、照会ストリングの最後からマージする方法が最適な方法です。 配列のサイズは、getMergedWhereCount から戻された値と同じです。 |
これはかなり単純な場合で、WHERE カスタム・ファインダーでより効率的に処理することができます。 WHERE カスタム・ファインダーでは簡単には扱えないさらに複雑なサンプルも考えられます。 例えば、より複雑なオブジェクトを取り、それを WHERE 節の複数の列に挿入するファインダーが必要だとします。 finder メソッドは、最終的には次のような形式になります。
public java.sql.PreparedStatement
findWithComplexObject(BigObject big) throws Exception {
PreparedStatement ps = null;
int mergeCount = getMergedWhereCount();
int columnCount = 3;
int anInt = big.getAnInt();
String aString = big.getAString();
String aLongAsString =
com.ibm.vap.converters.VapStringToLongConverter.
singleton().dataFrom(big.getLongObject());
ps = getMergedPreparedStatement("(T1.ANINT > ?) AND
(T1.ASTRING = ?) AND (T2.ALONGSTR < ?)");
for (int i=0; i<(columnCount*mergeCount); i=i+columnCount) {
ps.setInt(i+1, anInt);
if (aString == null)
ps.setNull(1, java.sql.Types.VARCHAR);
else
ps.setString(i+2, aString);
if (aLongAsString == null)
ps.setNull(1, java.sql.Types.VARCHAR);
else
ps.setString(i+3, aLongAsString);
}
return ps;
}
さらに複雑なサンプルも考えられます。 例えば、データに加えて WHERE 節 (あるいはそれを作成するための手順) を含むオブジェクトを渡すことがあります。 あるいは、複数のパラメーターが存在し、 それぞれが WHERE 節で異なる条件を表す場合もあります。
サンプル: 複雑なメソッド・カスタム・ファインダー
EJB デプロイメント・ツールではアソシエーションが作成されませんが、 複雑なメソッド・カスタム・ファインダーを使用して多対多アソシエーションを確立することは可能です。 その方法を論理的に表記したのが以下のサンプルです。 このサンプルは、中間 Bean (ProdCustLink) および 2 つの 1:m アソシエーションを使用した、 製品およびカスタマー Bean 間の多対多アソシエーションを使用します。

ユーザーは、メソッド・カスタム・ファインダーを作成し、1 回のメソッド呼び出しで、 どちらの方向でも関係を確立することができます。このサンプルでは、1 つの方向だけを考慮します。 すなわち、指定された製品キーと関連付けられたすべてのカスタマー・インスタンスを検索するカスタマー内のファインダーです。
カスタマーのホーム・インターフェースには、以下のような適切なメソッド・シグニチャーが含まれています。
java.util.Enumeration
findCustomersByProduct(prod.cust.code.ProductKey inKey)
throws java.rmi.RemoteException, javax.ejb.FinderException;
カスタマーのファインダー・ヘルパー・インターフェースには、以下のような対応する finder メソッドのシグニチャーが含まれています。
public java.sql.PreparedStatement
findCustomersByProduct(prod.cust.code.ProductKey inKey)
throws Exception;
ファインダー・オブジェクト (CustomerBeanFinderObject) は、finder メソッドを実装するだけでなく、 そのファインダーの照会ストリングをビルドおよびキャッシュします。
public class CustomerBeanFinderObject
extends com.ibm.vap.finders.VapEJSJDBCFinderObject
implements CustomerBeanFinderHelper {
private String cachedFindCustomersByProductQueryString = null;
.
.
.
}
ファインダー・オブジェクト内の遅延初期化で、照会ストリング・フィールドの accessor メソッドは、 最初に WHERE 条件を照会テンプレートにマージし、次に中間テーブルへの参照を FROM 文節に追加します。 これによって照会ストリングが作成されます。
accessor メソッドの前半は、各 WHERE 節を位置指定して更新するために、 genericFindInsertPoints 配列を使用します。 次に、メソッドの後半では、各 FROM 文節の最初からカウントし、 中間テーブルへの参照を必要に応じて照会ストリングに挿入し、照会ストリング・フィールドを更新します。
protected String getFindCustomersByProductQueryString() {
if (cachedFindCustomersByProductQueryString == null) {
// Do the WHERE first
// so that the genericFindInsertPoints are correct.
int i;
int[] genericFindInsertPoints = getGenericFindInsertPoints();
StringBuffer sb = new StringBuffer(getGenericFindSqlString());
for (i = 0; i < genericFindInsertPoints.length; i++) {
sb.insert(genericFindInsertPoints[i],
"(T1.id = T2.Customer_id) AND (T2.Product_id = ?)");
}
// Make sure to update every FROM clause.
String soFar = sb.toString();
int fromOffset = soFar.indexOf(" FROM ");
while (fromOffset != -1) {
sb.insert((fromOffset+5)," ProdCustLink T2, ");
soFar = sb.toString();
fromOffset = soFar.indexOf(" FROM ", (fromOffset+5));
}
cachedFindCustomersByProductQueryString = sb.toString();
}
return cachedFindCustomersByProductQueryString;
}
このメソッドの呼び出し後、照会ストリングは以下のようになります。
SELECT <columns> FROM ProdCustLink T2, CUSTOMER T1
WHERE((T1.id = T2.Customer_id) AND (T2.Product_id = ?))
ファインダー・オブジェクトでも、実装されたファインダーは照会ストリングを使用して PreparedStatement を作成します。 最後に重要なこととして、製品 ID 値が、反復ループでスーパークラス・メソッド getMergedWhereCount() を使用して、 各 WHERE 節に追加されています。
public java.sql.PreparedStatement
findCustomersByProduct(ProductKey inKey)
throws java.lang.Exception {
// Get the full query string and make a PreparedStatement.
java.sql.PreparedStatement ps =
getPreparedStatement(getFindCustomersByProductQueryString());
// Inject the product id parameter into each merged WHERE clause.
for (int i = 0; i > getMergedWhereCount(); i++) {
if (inKey != null)
ps.setInt(i+1, inKey.id);
else
ps.setNull(i+1, 4);
}
return ps;
}