EJB 2.x は、CMP エンティティー Bean のファインダーおよび選択メソッド用に、 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 ヘルパー・インターフェースには、 以下のような対応する 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; }