Oracle Fusion Middleware Oracle Application Development Framework Fusion開発者ガイド 11gリリース1 (11.1.1.7.0) B52028-05 |
|
前 |
次 |
この章では、ADFビジネス・コンポーネント・データ・モデル・プロジェクトのADFエンティティ・オブジェクトで使用する高度な手法について説明します。
この章の内容は次のとおりです。
注意: この章で説明する例を試すには、2.4.4項「AdvancedEntityExamplesアプリケーション・ワークスペースのスタンドアロン・アプリケーション」の説明に従ってFusion Order Demoアプリケーションの |
複数のエンティティ・オブジェクトで似た属性値に対して同じ妥当性検査を繰り返し行っている場合は、この検証をカプセル化した独自のデータ型を作成することで、時間と労力を節約できます。たとえば、ビジネス・ドメイン・レイヤーに、電子メール・アドレスを表す文字列を格納する大量のエンティティ・オブジェクト属性がある場合を考えてみてください。ビジネス・ドメイン・レイヤーのどこでもエンド・ユーザーが有効な電子メール・アドレスを入力できるように保証する方法としては、次のようなものがあります。
各属性に対して基本のString
データ型を使用
Javaコードを使用して属性レベルのメソッド・バリデータを追加し、属性ごとにString値の形式が有効な電子メール・アドレスであることを確認
しかし、このような方法は、大規模なアプリケーションでは急速に冗漫になります。ADFビジネス・コンポーネントで提供されている代替手段を使用すると、電子メール・アドレスを表す独自のEmailAddress
データ型を作成できます。電子メール・アドレスの値に関するすべての妥当性検査をこの新しいカスタム・データ型にまとめた後は、アプリケーション内にある電子メール・アドレスを表すすべての属性の型として、EmailAddress
を使用できます。このようにすると、属性値の意図が他の開発者に対していっそう明確になり、検証処理が1か所にまとまるためアプリケーションのメンテナンスが簡単になります。ADFビジネス・コンポーネントでは、これを開発者作成のデータ型ドメインと呼びます。
ドメインとは、String
、Number
、Date
などの基本データ型を拡張するJavaクラスであり、候補値が関連する妥当性検査に合格することを保証するコンストラクタ時の検証が追加されています。基本データ型検証、書式設定、カスタム・メタデータ・プロパティなどの横断的動作を備えたカスタム・データ型を定義する手段を、ドメインを属性のJava型として使用する任意のエンティティ・オブジェクトまたはビュー・オブジェクトによって継承される方法で提供します。
注意: この項の例では、Fusion Order Demoアプリケーションの |
ドメインを作成するには、ドメインの作成ウィザードを使用します。このウィザードは、「新規ギャラリ」の「ADFビジネス・コンポーネント」カテゴリで使用できます。
ドメインを作成するには:
アプリケーション・ナビゲータで、ドメインを作成するプロジェクトを右クリックし、「新規」を選択します。
「新規ギャラリ」で、「ビジネス層」を展開し、「ADFビジネス・コンポーネント」を選択します。次に「ドメイン」を選択し、「OK」をクリックします。
これにより、ドメイン作成ウィザードが起動します。
「名前」ページで、ドメインの名前とドメインが存在するパッケージを指定します。単純なJava型に基づくドメインを作成する場合は、「Oracleオブジェクト型のドメイン」を選択しないでおきます。
「次へ」をクリックします。
「設定」ページで、ドメインのベース型と、マップするデータベース列の型を指定します。たとえば、8文字の短い電子メール・アドレスを保持するShortEmailAddress
という名前のドメインを作成する場合は、ベース型にString
を設定し、「データベース列の型」にVARCHAR2(8)
を設定します。このパネルでは、他の共通属性も設定できます。
「終了」をクリックしてドメインを作成します。
ドメインを作成すると、プロジェクトのソース・パスのサブディレクトリに、選択したパッケージ名に対応するXMLコンポーネント定義が作成されます。たとえば、devguide.advanced.domains
パッケージにShortEmailAddress
ドメインを作成すると、./devguide/advanced/domains
サブディレクトリにShortEmailAddress.xml
ファイルが作成されます。ドメインには常に対応するJavaクラスがあり、ドメインが存在するパッケージのcommon
サブパッケージに作成されます。つまり、devguide.advanced.domains.common
パッケージにShortEmailAddress.java
クラスが作成されます。ドメインのJavaクラスは、組込みデータ型と同じように動作する適切なコードを備えて自動的に生成されます。
ここでは、ドメインを使用する際に理解しておく必要のある事項について説明します。
プロジェクトでドメインを作成すると、エンティティ・オブジェクトおよびビュー・オブジェクトのウィザードとダイアログで「属性」の「型」ドロップダウン・リストに、使用可能なデータ型の1つとして表示されます。特定の属性の型としてドメインを使用するには、リストから選択します。
注意: エンティティ・ベースのビュー・オブジェクトにおけるエンティティ・マップの属性は、対応する基礎のエンティティ・オブジェクト属性からデータ型を継承するので、エンティティ属性でドメイン型を使用すると、対応するビュー・オブジェクトの属性もその型になります。ビュー・オブジェクトの一時属性またはSQL導出属性の場合は、基礎のエンティティから継承しないため、ドメインを使用するように型を直接設定できます。 |
通常、ドメインについてユーザーが行う必要のある唯一のコーディング作業は、生成されるvalidate()
メソッドの内部のカスタム・コードを記述することです。validate()
メソッドの実装では、設定している候補値の妥当性検査を行い、検証が失敗した場合はoracle.jbo
パッケージでDataCreationException
をスローします。
翻訳可能な例外メッセージをスローするには、例38-1で示されているようなメッセージ・バンドル・クラスを作成できます。このクラスは、ドメイン・クラス自体と同じcommon
パッケージに作成します。メッセージ・バンドルは、{
MessageKeyString
、
TranslatableMessageString
}
というペアの配列を返します。
例38-1 ドメイン例外メッセージのカスタム・メッセージ・バンドル・クラス
package devguide.advanced.simpledomains.common; import java.util.ListResourceBundle; public class ErrorMessages extends ListResourceBundle { public static final String INVALID_SHORTEMAIL = "30002"; public static final String INVALID_EVENNUMBER = "30003"; private static final Object[][] sMessageStrings = new String[][] { { INVALID_SHORTEMAIL, "A valid short email address has no @-sign or dot."}, { INVALID_EVENNUMBER, "Number must be even."} }; /** * Return String Identifiers and corresponding Messages * in a two-dimensional array. */ protected Object[][] getContents() { return sMessageStrings; } }
String
はJDKのベース型であるため、String
に基づくドメインは、private mData String
メンバー・フィールドを集約して、ドメインが表す値を保持します。その後、クラスはADFランタイムが期待するDomainInterface
とともにSerializable
インタフェースも実装し、ADFコンポーネントのカスタム・クライアント・インタフェースのメソッドの引数または戻り型でドメインを使用できるようにします。
例38-2は、簡単なShortEmailAddress
ドメイン・クラスのvalidate()
メソッドを示しています。このメソッドは、mData
値にアットマークまたはドットが含まれないていないことを確認し、含まれている場合は、翻訳可能なエラー・メッセージの適切なメッセージ・バンドルとメッセージ・キーを参照するDataCreationException
をスローします。
例38-2 カスタム検証を含む簡単なShortEmailAddress文字列ベース・ドメイン型
public class ShortEmailAddress extends Object implements DomainInterface, Serializable { private String mData; // . . . /**Implements domain validation logic and throws a JboException on error. */ protected void validate() { int atpos = mData.indexOf('@'); int dotpos = mData.lastIndexOf('.'); if (atpos > -1 || dotpos > -1) { throw new DataCreationException(ErrorMessages.class, ErrorMessages.INVALID_SHORTEMAIL,null,null); } } // . . . }
oracle.jbo.domain
パッケージの組込み型に基づく他の簡単なドメインは、例38-3に示すように、ベース型を拡張します。この例では、偶数を表すEvenNumber
という名前のNumberベースのドメインに対するvalidate()
メソッドが示されています。
例38-3 カスタム検証を含む簡単なEvenNumber Numberベース・ドメイン型
public class EvenNumber extends Number { // . . . /** * Validates that value is an even number, or else * throws a DataCreationException with a custom * error message. */ protected void validate() { if (getValue() % 2 == 1) { throw new DataCreationException(ErrorMessages.class, ErrorMessages.INVALID_EVENNUMBER,null,null); } } // . . . }
基本データ型に基づいて作成された単純なドメインは、不変なクラスになります。つまり、次のようにして新しいインスタンスを作成します。
ShortEmailAddress email = new ShortEmailAddress("emailaddress1");
このようにして作成したインスタンスの値は変更できません。別の短い電子メール・アドレスを参照する場合は、別のインスタンスを作成します。
ShortEmailAddress email = new ShortEmailAddress("emailaddress2");
これは新しい概念ではなく、String
、Number
、Date
などのクラスと同じ動作です。
Oracleデータベースでは、データベースでユーザー定義型を作成できます。たとえば、次のDDL文を使用してPOINT_TYPE
という名前の型を作成できます。
create type point_type as object ( x_coord number, y_coord number );
POINT_TYPE
のようなユーザー定義型を使用する場合は、その型に基づくドメインを作成したり、オブジェクト型の列を含む表をリバース・エンジニアリングしてドメインを自動的に作成したりできます。
自分でドメインを作成するには、ドメイン作成ウィザードで次の手順を実行します。
アプリケーション・ナビゲータで、ドメインを作成するプロジェクトを右クリックし、「新規」を選択します。
「新規ギャラリ」で、「ビジネス層」を展開し、「ADFビジネス・コンポーネント」を選択します。次に「ドメイン」を選択し、「OK」をクリックします。
これにより、ドメイン作成ウィザードが起動します。
「名前」ページで、「Oracleオブジェクト型のドメイン」チェック・ボックスを選択し、「使用可能な型」リストからドメインを作成するオブジェクト型を選択します。
「次へ」をクリックします。
「設定」ページで、「属性」ドロップダウン・リストを使用して複数のドメイン・プロパティを切り替え、適切に設定します。
「終了」をクリックしてドメインを作成します。
オブジェクト型ドメインを手動で作成する以外にも、表からのビジネス・コンポーネント・ウィザードを使用してOracleオブジェクト型の列を含む表を選択すると、JDeveloperによってそれらのオブジェクト型のドメインがリバース・エンジニアリング・プロセスの一部として作成されます。たとえば、POINT_TYPE
型の列を含む次のような表を作成するものとします。
create table interesting_points( id number primary key, coordinates point_type, description varchar2(20) );
表からのビジネス・コンポーネント・ウィザードでINTERESTING_POINTS
表に対するエンティティ・オブジェクトを作成すると、InterestingPoints
エンティティ・オブジェクトとPointTypeドメインの両方を取得できます。後者がPOINT_TYPE
オブジェクト型に基づいて自動的に作成されるのは、InterestingPoints
エンティティ・オブジェクトのCoordinates
属性のデータ型として必要であるためです。
単純なドメインとは異なり、オブジェクト型ドメインは可変です。オブジェクト型の構造体の各要素に対し、ドメイン・クラスにgetterメソッドとsetterメソッドが生成されます。ドメインのプロパティを変更した後、そのドメインをビュー・オブジェクトまたはエンティティ・オブジェクトの属性として設定するとき、ドメインは1つの単位として扱われます。ADFは、ドメインのプロパティの変更は追跡せず、ドメインが値として使用される属性値の変更のみを追跡します。
注意: Oracleオブジェクト型に基づくドメインは、基礎となる型がOracleオブジェクト型であるデータをプログラムで処理するときに便利です。また、ストアド・プロシージャとの間での構造体情報の受け渡しも簡単になります。ただし、ADFバインディング・レイヤーでのオブジェクト型ドメインの処理のサポートは完全であるため、宣言的にデータバインドされたユーザー・インタフェースでオブジェクト・ドメインを値とする属性を使用することは容易ではありません。 |
アプリケーション・ナビゲータでドメインを選択した後、次のいずれかの方法で実装クラスに素早く移動できます。
アプリケーション・ナビゲータでドメインを右クリックして、コンテキスト・メニューから「ドメイン・クラスに移動」を選択します。
構造ウィンドウで、ドメイン・クラスをダブルクリックします。
37.6項「再利用可能なビジネス・コンポーネントのライブラリでの作業」で説明されているように、ビジネス・コンポーネント・アーカイブを作成すると、プロジェクトのソース・パスの*.commonサブディレクトリにあるドメイン・クラスとメッセージ・バンドル・ファイルは、*CSCommon.jar
にパッケージ化されます。これらは、サポートする必要のある中間層アプリケーション・サーバーと最終的なリモート・クライアントの両方に共通のクラスです。
ドメインではカスタム・メタデータ・プロパティを定義できます。そのようなドメインに基づくエンティティ・オブジェクトまたはビュー・オブジェクトの属性は、属性自体で定義されている場合と同じように、これらのカスタム・プロパティを継承します。エンティティ・オブジェクトまたはビュー・オブジェクトの属性で同じカスタム・プロパティが定義されている場合は、その設定が、ドメインから継承する値より優先されます。
JDeveloperでは、ドメイン定義レベルで適用されている宣言的な設定を施行します。この設定は、ドメイン型に基づく属性のエンティティ・オブジェクトまたはビュー・オブジェクトでは制限を緩和できません。たとえば、ドメインの「更新可能」プロパティを「新規の間」に設定した場合、そのドメインをエンティティ・オブジェクト属性のJava型として使用するときに、「更新可能」を「なし」(より厳しい制限)に設定することはできますが、「常に」に設定することはできません。同様に、ドメインを「永続的」に定義した場合、後でそれを一時的なものにすることはできません。アプリケーションで使用するときは、ドメインの宣言的プロパティはできるかぎり緩く設定し、後で必要に応じて厳しくできるようにします。
監査のため、表に追加した行を表から物理的に削除できない場合があります。そのような場合は、エンド・ユーザーがユーザー・インタフェースで行を削除する際に、DELETED
列の値をN
からY
に変更し、削除済としてマークします。この処理を行うために、2つのメソッドのオーバーライドを使用して、エンティティ・オブジェクトのデフォルト動作を変更できます。たとえば、Fusion Order DemoアプリケーションのProducts
エンティティをこのように動作するように変更できます。
行が削除されたときに削除済フラグを更新するには、エンティティ・オブジェクトのカスタムJavaクラスを有効にし、remove()
メソッドをオーバーライドして、super.remove()
メソッドを呼び出す前に削除済フラグを設定するようにします。例38-4は、エンティティ・オブジェクトのカスタムJavaクラスが、この変更でどのようになるかを示しています。削除された行の属性を設定しようとするとDeadEntityAccessException
が発生するため、super.remove()
を呼び出す前に属性を設定することが重要です。
この例では、PRODUCTS
表を変更してDELETED
列を追加し、Products
エンティティとデータベースの同期を取って、対応するDeleted
属性を追加してあるものとします。
例38-4 Productエンティティの行が削除されたときの削除済フラグの更新
// In your custom Java entity class public void remove() { setDeleted("Y"); super.remove(); }
行は行セットから削除されますが、エンティティ・キャッシュでDeleted
フラグの値はY
に変更されています。この動作の実装の第2の部分は、DML操作の実行を要求されたときに、エンティティにDELETE
のかわりにUPDATE
の実行を強制することです。完全なソリューションのためには、両方の部分を実装する必要があります。
エンティティ・オブジェクトを削除のかわりに更新するには、doDML()
メソッドをオーバーライドし、operation
フラグを条件付きで変更するコードを記述します。操作フラグがDML_DELETE
の場合は、このコードでかわりにDML_UPDATE
に変更します。例38-5は、エンティティ・オブジェクトのカスタムJavaクラスが、この変更でどのようになるかを示しています。
この例では、PRODUCTS
表を変更してDELETED
列を追加し、Products
エンティティとデータベースの同期を取って、対応するDeleted
属性を追加してあるものとします。
例38-5 削除のかわりの更新DML操作の強制
// In your custom Java entity class protected void doDML(int operation, TransactionEvent e) { if (operation == DML_DELETE) { operation = DML_UPDATE; } super.doDML(operation, e); }
オーバーライドしたdoDML()
メソッドを配置し、38.2.1項で説明したオーバーライドされたremove()
メソッドを補完することで、対応するエンティティ・オブジェクトの慣用名を持つビュー・オブジェクトからエンティティを削除しようとすると、行が物理的に削除されるかわりに、列DELETED
が更新されます。削除された製品がビュー・オブジェクトの問合せ結果に表示されることを防ぐため、WHERE DELETED = 'N'
の製品のみを含むようにWHERE
句を変更する必要があります。
バッチ更新を使用すると、複数のエンティティ変更の際に発行されるDML文の数を減らすことができます。
デフォルトで、ADFビジネス・コンポーネント・フレームワークは、指定されたエンティティ定義型の変更済エンティティ1つにつき、1つのDML文(INSERT
、UPDATE
、DELETE
)を実行します。たとえば、Employeeエンティティ・オブジェクト型があり、アプリケーションの通常使用でその複数のインスタンスが変更されたとします。2つのインスタンスが作成され、3つの既存のインスタンスが変更され、4つの既存のインスタンスが削除された場合、それらの変更を保存するために、トランザクション・コミット時間に、フレームワークにより9つのDML文(2つのINSERT
文、3つのUPDATE
文および4つのDELETE
文)が発行されます。
トランザクションで、ある型の複数のエンティティを頻繁に更新する場合、そのエンティティ定義型に対してバッチ更新機能を使用することを検討してください。この例では、バッチ更新(しきい値は1)でフレームワークが発行するDML文は、2件の挿入を処理する1つの一括INSERT
文、3件の更新を処理する1つの一括UPDATE
文および4件の削除を処理する1つの一括DELETE
文の3つのみです。
注意: 次のいずれかの場合、バッチ更新機能は無効になります。
|
エンティティに対してバッチ更新を有効化する手順:
概要エディタで目的のエンティティ・オブジェクトを開きます。
アプリケーション・ナビゲータで、適切なエンティティをダブルクリックし、概要エディタで開きます。
概要エディタの「一般」ページで「チューニング」セクションを展開し、「バッチ更新を使用」チェック・ボックスを選択して、適切なしきい値を指定します。
これにより、それを超えるとADFが一括DML操作を使用して変更を処理する、バッチ処理のしきい値が指定されます。
ここでは、エンティティ・オブジェクト間のアソシエーションの処理に関する高度な手法について説明します。
対応する属性の等価性のみに基づく関連より複雑なエンティティ間の関連を表す必要があるときは、アソシエーションのSQL句を変更して、さらに複雑な条件を組み込むことができます。たとえば、2つのエンティティ間の関連が有効期間に依存する場合があります。Product
をSupplier
と関連付けることができても、サプライヤの名前が時間とともに変化する場合は、その製品行が使用されている(または、使用されていた)期間を記録するEFFECTIVE_FROM
列とEFFECTIVE_UNTIL
列が、SUPPLIERS
表の各行に追加されている可能性があります。その場合、Product
とそれが関連付けられるSupplier
の間の関係は、SupplierId
属性の一致と、製品のRequestDate
がサプライヤのEffectiveFrom
の日付とEffectiveUntil
の日付の間にあるという条件の組合せによって記述されます。
このような複雑な関係は、概要エディタで、アソシエーションに対して設定できます。まず、「関係」ページで、必要な追加属性のペアを追加します。この例では、(EffectiveFrom
、RequestDate
)のペアと、(EffectiveUntil
、RequestDate
)のペアです。次に、「問合せ」ページで「WHERE」フィールドを編集して、WHERE句を次のように変更できます。
(:Bind_SupplierId = Product.SUPPLIER_ID) AND (Product.REQUEST_DATE BETWEEN :Bind_EffectiveFrom AND :Bind_EffectiveUntil)
アソシエーションの作成の詳細は、4.3項「アソシエーションの作成および構成」を参照してください。
2つのエンティティ・ベースのビュー・オブジェクト間のビュー・リンクを作成する際、「ビュー・リンク・プロパティ」ページに、ビュー・オブジェクト・レベルとエンティティ・オブジェクト・レベルの両方でビュー・リンク・アクセッサ属性を公開するオプションがあります。デフォルトでは、ビュー・リンク・アクセッサは、リンク先ビュー・オブジェクトのビュー・オブジェクト・レベルでのみ公開されます。「エンティティ・オブジェクト: {0}SourceEntityName」チェック・ボックスまたは「エンティティ・オブジェクト: {0}DestinationEntityName」チェック・ボックスから適切なものを選択することで、リンク元またはリンク先のエンティティ・オブジェクトの一方または両方にビュー・リンク属性を含めることができます。これにより、エンティティ・オブジェクトが関連するビュー行のセットにアクセスするための便利な方法が提供されます。特に、行を生成するための問合せが現在の行の属性にのみ依存する場合に有効です。
デフォルトでは、エンティティ・アソシエーション・アクセッサの行セットを取得するたびに、エンティティ・オブジェクトは新しいRowSet
オブジェクトを作成し、ユーザーが行を操作できるようにします。これは、そのたびに問合せを再実行して結果を生成するという意味ではなく、RowSet
オブジェクトの新しいインスタンスを作成してデフォルトのイテレータを先頭行の前のスロットにリセットするだけです。行セットをデータベースの行で強制的に更新するには、executeQuery()
メソッドを呼び出します。
行セットの作成には若干のオーバーヘッドが伴うため、同じアソシエーション・アクセッサ属性に対して大量の呼出しを行うコードの場合は、アソシエーションのリンク元エンティティ・オブジェクトに対してアソシエーション・アクセッサ行セットの保持を有効にすることを検討する余地があります。アソシエーション・アクセッサのソースであるエンティティ・オブジェクトについて、概要エディタを使用してアソシエーション・アクセッサ行セットの保存を有効にできます。エンティティ・オブジェクトに対して、概要エディタの「一般」ページの「チューニング」セクションで、「アソシエーション・アクセッサ行セットの保存」を選択します。
エンティティ・オブジェクトに対してカスタムJavaエンティティ・コレクション・クラスを有効にすることもできます。他のカスタム・エンティティJavaクラスと同様に、この設定は、エンティティ・オブジェクトの概要エディタの「Java」ページで開く「Javaオプションの選択」ダイアログを選択して実行します。ダイアログでは、「エンティティ・コレクション・クラスの生成」を選択します。次に、JDeveloperによって自動的に作成されるYourEntityColl
Impl
クラスで、init()
メソッドをオーバーライドし、setAssociationAccessorRetained()
メソッドを呼び出してパラメータとしてtrue
を渡す行を、super.init()
の後に追加します。これは、そのエンティティ・オブジェクトのすべてのアソシエーション・アクセッサ属性に適用されます。
エンティティ・オブジェクトに対してこの機能を有効にすると、アソシエーション・アクセッサの行セットが毎回再作成されないため、デフォルトの行セット・イテレータの現在行も維持されるという副作用があります。つまり、デフォルト行セット・イテレータの現在行をリセットして先頭行の前のスロットに戻すには、アソシエーション・アクセッサから取得した行セットに対して、reset()
メソッドを明示的に呼び出す必要があります。
ただし、アクセッサの保持を有効にすると、アクセッサ行セットの行を反復処理する前にいつでもreset()
を呼び出すようにしないと、微妙で検出が困難なエラーがアプリケーションで発生する可能性があることに注意してください。たとえば、アソシエーション・アクセッサ行セットの行を例38-6に示すように反復処理し、合計を計算するものとします。
例38-6 行セットの誤った反復
// In your custom Java entity classRowSet rs = (RowSet)getProducts(); while (rs.hasNext()) { ProductImpl r = (ProductImpl)rs.next(); // Do something important with attributes in each row }
アクセッサ行セットを最初に処理するときには、このコードは正しく動作します。しかし、行セット(およびそのデフォルトの行セット・イテレータ)が保持されるため、2回目以降の行セットへのアクセスでは、現在行はすでに行セットの末尾にあり、rs.hasNext()
がfalse
になるためwhileループはスキップされます。この機能を有効にする場合は、アクセッサの反復コードは例38-7に示すように記述する必要があります。
基礎となる表に対する挿入、更新、および削除権限をカプセル化するPL/SQLパッケージがある場合は、その表を表すエンティティ・オブジェクトに対するデフォルトのDML処理イベントをオーバーライドし、かわりにPL/SQL APIの中でプロシージャを呼び出すことができます。通常、このようなPL/SQLパッケージは、付随するデータベース・ビューと組み合せて使用されます。クライアント・プログラムは、データベース・ビューを使用して基になる表からデータを読み取り、PL/SQLパッケージのプロシージャを使用してデータを表に書き戻します。
たとえば、このようなビューとパッケージの組合せに基づいてProduct
エンティティ・オブジェクトを作成するとします。
Fusion Order DemoスキーマのPRODUCTS
表を使用し、次のDDL文を使用して作成されるPRODUCTS_V
という名前のデータベース・ビューについて考えます。
create or replace view products_v as select product_id,name,image,description from products;
さらに、基になるPRODUCTS
表に対する挿入、更新、削除権限をカプセル化する、例38-8で示されるような簡単なPRODUCTS_API
パッケージを使用します。
例38-8 PRODUCTS表に対する簡単なPL/SQLパッケージAPI
create or replace package products_api is procedure insert_product(p_prod_id number, p_name varchar2, p_supplier_id number, p_list_price number, p_min_price number, p_ship_code varchar2); procedure update_product(p_prod_id number, p_name varchar2, p_supplier_id number, p_list_price number, p_min_price number, p_ship_code varchar2); procedure delete_product(p_prod_id number); end products_api;
この組合せのデータベース・ビューとPL/SQLパッケージに基づいてエンティティ・オブジェクトを作成するには:
38.5.1項「ビューを基にしてエンティティ・オブジェクトを作成する方法」の説明に従ってビューを基にしたエンティティ・オブジェクトを作成します。
38.5.3項「PL/SQLベースのエンティティに関する詳細のベース・クラスへの集中化」の説明に従ってエンティティのベース・クラスを作成します。
38.5.4項「DML操作のストアド・プロシージャ呼出しの実装」の説明に従って適切なストアド・プロシージャ呼出しを実装します。
必要に応じて、38.5.5項「選択処理およびロック処理の追加」の説明に従って選択機能とロック機能を処理します。
注意: この項の例では、Fusion Order Demoアプリケーションの |
ビューを基にしてエンティティ・オブジェクトを作成するには、4.2.2項「エンティティの作成ウィザードで単一のエンティティ・オブジェクトを作成する方法」の説明に従ってエンティティ・オブジェクト作成ウィザードを使用します。ただし、次の例外があります。
「名前」ページで、Product
のようなエンティティ名を設定し、「データベース・オブジェクト」セクションの下部にある「ビュー」チェック・ボックスを選択します。
現在のスキーマで使用できるデータベース・ビューが、「スキーマ・オブジェクト」リストに表示されます。
「スキーマ・オブジェクト」リストで、目的のデータベース・ビューを選択します。
「属性の設定」ページで、「属性の選択」ドロップダウン・リストを使用して主キーとして機能する属性を選択し、そのプロパティの「主キー」の設定を有効にします。
注意: ビューを基にしてエンティティを定義するときは、データ・ディクショナリにデータベース・ビューに関連する制約がないため、主キー属性を自動的に決定することはできません。 |
ビューに基づくエンティティ・オブジェクトは、デフォルトで、基礎となるデータベース・ビューに対して次のすべての文を直接実行します。
SELECT
文(findByPrimaryKey()
の場合)
SELECT FOR UPDATE
文(lock()
の場合)
INSERT
、UPDATE
、DELETE
文(doDML()
の場合)
ストアド・プロシージャ呼出しを使用するには、(38.5.3項「PL/SQLベースのエンティティに関する詳細のベース・クラスへの集中化」の説明に従って)doDML()
操作をオーバーライドし、必要に応じて(38.5.4項「DML操作のストアド・プロシージャ呼出しの実装」の説明に従って)lock()
およびfindByPrimaryKey()
の処理をオーバーライドする必要があります。
PL/SQL APIに基づくエンティティ・オブジェクトが複数ある場合は、一般的な詳細をベース・フレームワーク拡張クラスに抽象化するのがよい方法です。その際、第37章「ビジネス・コンポーネントの高度な手法」に説明されているいくつかの概念を使用します。まず、ベースEntityImpl
クラスを拡張するPLSQLEntityImpl
クラスを作成します。このクラスは、PL/SQLベースの各エンティティがベース・クラスとして使用できます。例38-9に示すように、ベース・クラスのdoDML()
メソッドをオーバーライドし、操作に基づいて異なるヘルパー・メソッドを呼び出します。
注意: エンティティにすでに拡張したエンティティ実装クラスを使用している場合、 |
例38-9 操作に基づいて異なるプロシージャを呼び出すためのdoDML()のオーバーライド
// In PLSQLEntityImpl.java protected void doDML(int operation, TransactionEvent e) { // super.doDML(operation, e); if (operation == DML_INSERT) callInsertProcedure(e); else if (operation == DML_UPDATE) callUpdateProcedure(e); else if (operation == DML_DELETE) callDeleteProcedure(e); }
ベース・クラスPLSQLEntityImpl.java
では、次に示すように、デフォルトの処理を実行するようにヘルパー・メソッドを記述できます。
// In PLSQLEntityImpl.java /* Override in a subclass to perform non-default processing */ protected void callInsertProcedure(TransactionEvent e) { super.doDML(DML_INSERT, e); } /* Override in a subclass to perform non-default processing */ protected void callUpdateProcedure(TransactionEvent e) { super.doDML(DML_UPDATE, e); } /* Override in a subclass to perform non-default processing */ protected void callDeleteProcedure(TransactionEvent e) { super.doDML(DML_DELETE, e); }
このインフラストラクチャを配置した後、PLSQLEntityImpl
クラスに基づくエンティティ・オブジェクトでは、「ソース」→「メソッドのオーバーライド」メニュー項目を使用して、callInsertProcedure()
、callUpdateProcedure()
およびcallDeleteProcedure()
の各ヘルパー・メソッドをオーバーライドして、その特定のエンティティに適したストアド・プロシージャ呼出しを実行できます。
注意: これらのヘルパー・メソッドをサブクラスでオーバーライドしない場合、スーパークラスで定義されているデフォルトの処理が実行されます。代替処理を提供する |
これらの呼出しを実装する作業を簡単にするには、(第37章「ストアド・プロシージャとストアド・ファンクションの呼出し」で説明した)callStoredProcedure()
ヘルパー・メソッドをPLSQLEntityImpl
クラスに追加することもできます。このようにすると、このクラスを拡張するPL/SQLベースのエンティティ・オブジェクトはすべて、ヘルパー・メソッドを利用できます。
DML操作に対するストアド・プロシージャ呼出しを実装するには、エンティティ・オブジェクトのカスタムJavaクラスを作成して、その中の操作をオーバーライドする必要があります。
メソッドのオーバーライドでカスタムJavaクラスを作成するには:
アプリケーション・ナビゲータで、Product
エンティティ・オブジェクトをダブルクリックし、概要エディタで開きます。
概要エディタの「Java」ページで、「Javaオプションの編集」アイコンをクリックします。
「Javaオプションの選択」ダイアログで、「クラスの拡張」をクリックします。
「ベース・クラスのオーバーライド」ダイアログで、「行」フィールドにPLSQLEntityImpl
クラスのパッケージとクラスを入力するか、「参照」をクリックして検索して選択します。
次に、「エンティティ・オブジェクト・クラスの生成」を選択して「OK」をクリックします。
アプリケーション・ナビゲータで、ProductsImpl.java
をダブルクリックして概要エディタで開きます。
「ソース」メニューで「メソッドのオーバーライド」を選択します。
「メソッドのオーバーライド」ダイアログで、callInsertProcedure()
、callUpdateProcedure()
およびcallDeleteProcedure()
の各メソッドを選択し、次に「OK」をクリックします。
次に、必要なコードを入力してこられのプロシージャをオーバーライドします。
例38-10は、これらのオーバーライドされるヘルパー・メソッドに記述するコードを示しています。
例38-10 ヘルパー・メソッドを利用した挿入、更新、削除プロシージャの呼出し
// In ProductsImpl.java protected void callInsertProcedure(TransactionEvent e) { callStoredProcedure("products_api.insert_product(?,?,?,?,?,?)", new Object[] { getProductId(), getProductName(), getSupplierId(), getListPrice(), getMinPrice(), getShippingClassCode() }); } protected void callUpdateProcedure(TransactionEvent e) { callStoredProcedure("products_api.update_product(?,?,?,?,?,?)", new Object[] { getProductId(), getProductName(), getSupplierId(), getListPrice(), getMinPrice(), getShippingClassCode() }); } protected void callDeleteProcedure(TransactionEvent e) { callStoredProcedure("products_api.delete_product(?)", new Object[] { getProductId() }); }
この段階で、Products
エンティティ・オブジェクトに対するProducts
という名前のデフォルトのエンティティ・ベースのビュー・オブジェクトを作成し、そのインスタンスをProductsModule
アプリケーション・モジュールに追加すると、ビジネス・コンポーネント・ブラウザで、Products
ビュー・オブジェクト・インスタンスの行の挿入、更新、削除を簡単にテストできます。
通常は、挿入、更新、および削除の操作をオーバーライドするのみで十分です。データベース・ビューに対してfindByPrimaryKey()
のSELECT
文およびlock()
のSELECT FOR UPDATE
文を実行するデフォルトの動作は、ほとんどの基本的な種類のビューで動作します。
ただし、ビューが複雑でSELECT FOR UPDATE
をサポートしない場合、または他のストアド・プロシージャAPIを使用してfindByPrimaryKey()
機能およびlock()
機能を実行する必要がある場合は、38.5.5項「選択処理およびロック処理の追加」に説明されている方法を実行してもかまいません。
必要に応じて、ストアド・プロシージャを呼び出して、エンティティ・オブジェクトのlock()
およびfindByPrimaryKey()
機能を処理できます。PRODUCTS_API
パッケージを更新し、例38-11で示されている2つのプロシージャを追加したものとします。lock_product
プロシージャとselect_product
プロシージャはどちらも、IN
パラメータとして主キー属性を受け取り、OUT
パラメータを使用して残りの属性の値を返します。
例38-11 PRODUCTS表用に追加するロックおよび選択プロシージャ
/* Added to PRODUCTS_API package */ procedure lock_product(p_prod_id number, p_name OUT varchar2, p_supplier_id OUT number, p_list_price OUT number, p_min_price OUT number, p_ship_code OUT varchar2); procedure select_product(p_prod_id number, p_name OUT varchar2, p_supplier_id OUT number, p_list_price OUT number, p_min_price OUT number, p_ship_code OUT varchar2);
挿入、更新、削除用に追加したものと似たヘルパー・メソッドを使用して、lock()
およびfindByPrimaryKey()
のオーバーライドを処理するように、PLSQLEntityImpl
ベース・クラスを拡張できます。実行時には、lock()
操作とfindByPrimaryKey()
操作はどちらも最終的に、doSelect(boolean lock)
という名前の下位レベルのエンティティ・オブジェクト・メソッドを呼び出します。lock()
操作はパラメータをtrue
値にしてdoSelect()
を呼び出しますが、findByPrimaryKey()
操作はfalse
を渡して呼び出します。
例38-12は、必要に応じてサブクラスがオーバーライドできる2つのヘルパー・メソッドに委譲するようオーバーライドされたPLSQLEntityImpl
のdoSelect()
メソッドを示します。
例38-12 ロック・パラメータに基づいて異なるプロシージャを呼び出すためのdoSelect()のオーバーライド
// In PLSQLEntityImpl.java protected void doSelect(boolean lock) { if (lock) { callLockProcedureAndCheckForRowInconsistency(); } else { callSelectProcedure(); } }
2つのヘルパー・メソッドは、ベース・クラスPLSQLEntityImpl
のデフォルト機能を実行するためだけに記述されています。
// In PLSQLEntityImpl.java /* Override in a subclass to perform non-default processing */ protected void callLockProcedureAndCheckForRowInconsistency() { super.doSelect(true); } /* Override in a subclass to perform non-default processing */ protected void callSelectProcedure() { super.doSelect(false); }
ロックを実行するヘルパー・メソッドの名前がcallLockProcedureAndCheckForRowInconsistency()
であることに注意してください。これは、行をロックするときに、新しく選択される行の値が、エンティティ・キャッシュ内のエンティティ・オブジェクトが現在のデータベース値であると認識している値と同じかどうかを検出する検査を実行する必要があることを、開発者に伝えています。
この古い値と新しい値の属性の比較をサブクラスが実行するのを助けるため、次のようなfinalのヘルパー・メソッドをPLSQLEntityImpl
クラスに追加できます。
// In PLSQLEntityImpl protected void compareOldAttrTo(int attrIndex, Object newVal) { if ((getPostedAttribute(attrIndex) == null && newVal != null) || (getPostedAttribute(attrIndex) != null && newVal == null) || (getPostedAttribute(attrIndex) != null && newVal != null && !getPostedAttribute(attrIndex).equals(newVal))) { throw new RowInconsistentException(getKey()); } }
追加のインフラストラクチャをベース・クラスPLSQLEntityImpl
に置くと、Product
エンティティ・オブジェクトのProductImpl
クラスのcallSelectProcedure()
およびcallLockProcedureAndCheckForRowInconsistency()
ヘルパー・メソッドをオーバーライドできます。select_product
プロシージャとlock_product
プロシージャはOUT
引数を持っているため、37.4.4項「他の種類のストアド・プロシージャの呼出し方法」で説明したように、JDBCのCallableStatement
オブジェクトを使用してこれらの呼出しを実行する必要があります。
例38-13は、select_product
プロシージャを呼び出すために必要なコードを示しています。このコードが実行する基本的な手順は次のとおりです。
PL/SQLブロックが呼び出すCallableStatement
を作成します。
1から始まるバインド変数位置で、OUT
パラメータと型を登録します。
IN
パラメータの値を設定します。
文を実行します。
更新された列の値を取得します。
更新された属性値を行に移入します。
文を閉じます。
例38-13 主キーで行を選択するためのストアド・プロシージャの呼出し
// In ProductsImpl.java protected void callSelectProcedure() { String stmt = "begin products_api.select_product(?,?,?,?,?,?);end;"; // 1. Create a CallableStatement for the PLSQL block to invoke CallableStatement st = getDBTransaction().createCallableStatement(stmt, 0); try { // 2. Register the OUT parameters and types st.registerOutParameter(2, VARCHAR2); st.registerOutParameter(3, NUMBER); st.registerOutParameter(4, NUMBER); st.registerOutParameter(5, NUMBER); st.registerOutParameter(6, VARCHAR2); // 3. Set the IN parameter value st.setObject(1, getProductId()); // 4. Execute the statement st.executeUpdate(); // 5. Retrieve the possibly updated column values String possiblyUpdatedName = st.getString(2); String possiblyUpdatedSupplierId = st.getString(3); String possiblyUpdatedListPrice = st.getString(4); String possiblyUpdatedMinPrice = st.getString(5); String possiblyUpdatedShipCode = st.getString(6); // 6. Populate the possibly updated attribute values in the row populateAttribute(PRODUCTNAME, possiblyUpdatedName, true, false, false); populateAttribute(SUPPLIERID, possiblyUpdatedSupplierId, true, false, false); populateAttribute(LISTPRICE, possiblyUpdatedListPrice, true, false, false); populateAttribute(MINPRICE, possiblyUpdatedMinPrice, true, false, false); populateAttribute(SHIPPINGCLASSCODE, possiblyUpdatedShipCode, true, false, false); } catch (SQLException e) { throw new JboException(e); } finally { if (st != null) { try { // 7. Closing the statement st.close(); } catch (SQLException e) { } } } }
例38-14は、lock_product
プロシージャを呼び出すためのコードを示しています。基本的には例38-13と同じ手順を行いますが、次の2点のみ異なります。
OUT
パラメータから更新された可能性のある列の値を取得した後、PLSQLEntityImpl
から継承されたcompareOldAttrTo()
ヘルパー・メソッドを使用して、行ロック試行の結果としてRowInconsistentException
をスローする必要があるかどうかを判定します。
catch (SQLException e)
ブロックでは、データベースがエラーをスローしたかどうかを検査しています。
ORA-00054: resource busy and acquire with NOWAIT specified
例外がスローされている場合は、lock()
機能のデフォルトのエンティティ・オブジェクトの実装がこのような状況で行うのと同じように、再びADFビジネス・コンポーネントのAlreadyLockedException
をスローします。
例38-14 主キーで行をロックするためのストアド・プロシージャの呼出し
// In ProductsImpl.java protected void callLockProcedureAndCheckForRowInconsistency() { String stmt = "begin products_api.lock_product(?,?,?,?,?,?);end;"; CallableStatement st = getDBTransaction().createCallableStatement(stmt, 0); try { st.registerOutParameter(2, VARCHAR2); st.registerOutParameter(3, NUMBER); st.registerOutParameter(4, NUMBER); st.registerOutParameter(5, NUMBER); st.registerOutParameter(6, VARCHAR2); st.setObject(1, getProductId()); st.executeUpdate(); String possiblyUpdatedName = st.getString(2); String possiblyUpdatedSupplierId = st.getString(3); String possiblyUpdatedListPrice = st.getString(4); String possiblyUpdatedMinPrice = st.getString(5); String possiblyUpdatedShipCode = st.getString(6); compareOldAttrTo(PRODUCTNAME, possiblyUpdatedName); compareOldAttrTo(SUPPLIERID, possiblyUpdatedSupplierId); compareOldAttrTo(LISTPRICE, possiblyUpdatedListPrice); compareOldAttrTo(MINPRICE, possiblyUpdatedMinPrice); compareOldAttrTo(SHIPPINGCLASSCODE, possiblyUpdatedShipCode); } catch (SQLException e) { if (Math.abs(e.getErrorCode()) == 54) { throw new AlreadyLockedException(e); } else { throw new JboException(e); } } finally { if (st != null) { try { st.close(); } catch (SQLException e) { } } } }
これらのメソッドを配置すると、すべてのデータベース操作についてPRODUCTS_API
パッケージをラップするProducts
エンティティ・オブジェクトができます。ビュー・オブジェクトのデータ問合せ機能とエンティティ・オブジェクトのデータ検証および保存機能を明確に分離するため、通常のエンティティ・オブジェクトを使用するのと同じように、このProducts
エンティティ・オブジェクトを利用できます。エンティティ・オブジェクトの慣用名としてProducts
を使用する異なるビュー・オブジェクトを、必要な数だけ作成できます。
RowInconsistentException
が発生した後で、lock()
メソッドをオーバーライドして、エンティティ・オブジェクトをリフレッシュすることができます。例38-15に、エンティティ・オブジェクト実装クラスに追加すると、RowInconsistentExceptionを捕捉し、エンティティ・オブジェクトをリフレッシュするコードを示します。
次のいずれかに基づくエンティティ・オブジェクトを作成する必要がある場合があります。
DBLINK
を介したリモート表に解決されるシノニム
INSTEAD OF
トリガーを持つビュー
このような場合、いずれかの属性がRefresh on InsertまたはRefresh on Updateとしてマークされていると、次のようなエラーが発生します。
JBO-26041: Failed to post data to database during "Update" ## Detail 0 ## ORA-22816: unsupported feature with RETURNING clause
これらの種類のスキーマ・オブジェクトはRETURNING
句をサポートしません。この句は、デフォルトでは、INSERT
操作またはUPDATE
操作が実行されたものと同じデータベース・ラウンドトリップでリフレッシュされた値をより効率よく返すために、エンティティ・オブジェクトが使用します。
この種のエンティティ・オブジェクトでRETURNING句の使用を無効にするには:
エンティティ・オブジェクトのカスタム・エンティティ定義クラスを有効にします。
カスタム・エンティティ定義クラスで、createDef()
をオーバーライドして次のメソッドを呼び出します。
setUseReturningClause(false)
Refresh on Insert属性がエンティティ・オブジェクトの主キーである場合は、「一意キー」プロパティを設定して、エンティティの他の属性を代替一意キーとして指定する必要があります。
このようにしてRETURNING
句の使用を無効にすると、実行時には、エンティティ・オブジェクトが別のSELECT
文を使用してRefresh on Insert動作およびRefresh on Update動作を実装し、挿入または更新の後でリフレッシュするための値を取得します。
継承はオブジェクト指向開発の強力な機能であり、適切に使用すれば開発とメンテナンスを簡単にできます。37.8項「継承を使用する拡張コンポーネントの作成」で説明されているように、ADFビジネス・コンポーネントでは、継承を使用して既存のコンポーネントを拡張する新しいコンポーネントを作成し、新しいプロパティまたは動作を追加したり、親コンポーネントの動作を変更したりできます。継承は、再利用可能なビジネス・ドメイン・レイヤーで異なる種類のエンティティをモデリングする際に便利です。
注意: この項の例では、Fusion Order Demoアプリケーションの |
アプリケーションのデータベース・スキーマでは、同じ表の行に論理的に異なる種類のビジネス情報が格納される場合があります。このような表には、通常、各行に格納される情報の種類を決定する値を含む列があります。たとえば、Fusion Order DemoのPERSONS
表では、顧客、サプライヤ、スタッフに関する情報が、同一の表に格納されます。また、PERSON_TYPE_CODE
列の値(STAFF
、CUSTまたはSUPP)によって、その行が表すPERSON
の種類が決まります。
Fusion Order Demoの実装のこのリリースにはまだこの区別は含まれていませんが、将来のリリースでは次のことが必要になることが十分考えられます。
サプライヤまたはスタッフに固有の、データベースで保持される追加属性の管理
サプライヤまたはスタッフとは異なる、全ユーザーに共通の動作の実装
サプライヤまたはスタッフのみに固有の新機能の実装
図38-1は、Persons
、Staff
、Supplier
の各エンティティ・オブジェクトを個別に作成し、異なる種類のビジネス情報をアプリケーション内でより形式的に区別できるようにした場合の、ビジネス・ドメイン・レイヤーの様子を示しています。サプライヤとスタッフは個人(person)の特別な種類であるため、対応するエンティティ・オブジェクトは、ベースのPersons
エンティティ・オブジェクトを拡張しています。このベースのPersons
エンティティ・オブジェクトには、すべての種類のユーザーに共通する属性とメソッドがすべて含まれています。図のperformPersonFunction()
メソッドは、共通メソッドの1つを表しています。
Supplier
およびStaff
エンティティ・オブジェクトに対しては、その種類のユーザーに固有の属性やメソッドを追加できます。たとえば、サプライヤの現在の契約が期限切れになる時期を追跡するDate
型のContractExpires
属性が、Supplier
に追加されています。また、サプライヤに固有のperformSupplierFunction()
メソッドもあります。同様に、Staff
エンティティ・オブジェクトには、この個人に社員割引の資格があるかどうかを追跡するDiscountEligible
属性が追加されています。performStaffFunction()
は、スタッフに固有のメソッドです。
異なる種類の個人を、ビジネス・ドメイン・レイヤーの継承階層において異なるエンティティ・オブジェクトとしてモデル化することで、共通するデータや動作の共有が簡単になり、ユーザーを区別するアプリケーションの部分を実装できます。
継承階層にエンティティ・オブジェクトを作成するには、エンティティ・オブジェクト作成ウィザードを使用して各エンティティを作成します。ここで説明する例では、次のDDL文を実行してFODアプリケーションのPERSONS
表を変更し、2つの新しい列を追加してあるものとします。
alter table persons add ( discount_eligible varchar2(1), contract_expires date );
異なる種類の情報を含む表に基づいて継承階層内にエンティティ・オブジェクトを作成する前に、まず、行の種類を区別するために使用されている表の列を識別する必要があります。FODアプリケーションのPERSONS
表では、これはPERSON_TYPE_CODE
列です。このような列は表の行を異なるグループに分離つまり識別するのに役立つため、識別子列と呼ばれます。
次に、表の識別子列で有効な値を決定します。値がわからない場合は、JDeveloperの「SQLワークシート」で簡単なSQL文を実行して答えを得ることもできます。ワークシートを使用するには次のようにします。
「表示」メニューから「データベース・ナビゲータ」を選択します。
AdvancedEntityExamplesフォルダを展開し、FOD
接続を選択します。
FODを右クリックし、ポップアップ・メニューから「SQLワークシートを開く」を選択します。
図38-2は、「SQLワークシート」で、PERSONS
表のPERSON_TYPE_CODE
列に対してSELECT DISTINCT
問合せを実行した結果を示しています。これは、PERSON_TYPE_CODE
識別子の値SUPP
、STAFF
、およびCUST
に基づいて行が3つのグループに分類されることを示しています。
表に格納されている異なるビジネス・エンティティの種類の数がわかると、個別の項目をモデル化するために作成する必要のあるエンティティ・オブジェクトの数もわかります。通常は、項目の種類ごとに1つのエンティティ・オブジェクトを作成します。次に、階層のベースとして機能するエンティティを識別するため、各項目の種類に関係する属性のサブセットを特定する必要があります。
たとえば、ContractExpires
とDiscountEligible
を除くすべての属性はすべてのユーザーに関連し、ContractExpires
はサプライヤに固有、DiscountEligible
はスタッフに固有であると判断するものとします。この情報から、Persons
エンティティ・オブジェクトを階層のベースにし、Supplier
およびStaff
エンティティ・オブジェクトはPersons
を拡張してそれぞれの固有属性を追加することにします。
継承階層にベース・エンティティ・オブジェクトを作成するには、エンティティ・オブジェクト作成ウィザードを使用します。
ベース・エンティティ・オブジェクトを作成する手順
アプリケーション・ナビゲータで、エンティティ・オブジェクトを追加するプロジェクトを右クリックし、「新規」を選択します。
「新規ギャラリ」で、「ビジネス層」を展開し、「ADFビジネス・コンポーネント」をクリックして「エンティティ・オブジェクト」を選択し、「OK」をクリックします。
エンティティ・オブジェクト作成ウィザードの「名前」ページで、エンティティの名前とパッケージを指定し、エンティティのベースとなるスキーマ・オブジェクトを選択します。
たとえば、エンティティ・オブジェクトにPersonsという名前を設定し、PERSONS
表をベースにします。
「属性」ページで、ベース・エンティティ・オブジェクトに関係のない属性(ある場合)を「エンティティ属性」リストで選択し、「削除」をクリックして削除します。
たとえば、DiscountEligible
属性とContractExpires
属性をリストから削除します。
「属性の設定」ページで、「属性の選択」ドロップダウン・リストを使用して、継承されるエンティティ・オブジェクトのファミリの識別子として機能する属性を選択し、「多相化識別子」チェック・ボックスを選択してそのことを示します。重要なこととして、このベース・エンティティ・タイプの行を識別するため、この識別子属性に対する「デフォルト値」を指定する必要もあります。
たとえば、PersonTypeCode
属性を選択して識別子属性としてマークし、「デフォルト値」に値cust
を設定します。
注意: 識別子属性の「デフォルト値」を空白のままにしてもかまいません。空白のデフォルト値は、識別子列の値が |
次に、「終了」をクリックしてエンティティ・オブジェクトを作成します。
継承階層にサブタイプ・エンティティ・オブジェクトを作成するには、エンティティ・オブジェクト作成ウィザードを使用します。
作業を始める前に、次のようにします。
新しいエンティティ・オブジェクトが拡張する親エンティティ・オブジェクトであるエンティティ・オブジェクトを決定します。
たとえば、新しいManager
エンティティ・オブジェクトの親エンティティは、User
エンティティです。
親エンティティで識別子属性がすでに識別されていることを確認します。
識別されていない場合は、継承された子を作成する前に、概要エディタを使用して親エンティティの識別子属性で「多相化識別子」プロパティを設定します。
階層にサブタイプ・エンティティ・オブジェクトを作成するには:
アプリケーション・ナビゲータで、エンティティ・オブジェクトを追加するプロジェクトを右クリックし、「新規」を選択します。
「新規ギャラリ」で、「ビジネス層」を展開し、「ADFビジネス・コンポーネント」をクリックして「エンティティ・オブジェクト」を選択し、「OK」をクリックします。
エンティティ・オブジェクト作成ウィザードの「名前」ページで、エンティティの名前とパッケージを指定し、「拡張」フィールドの隣の「参照」ボタンをクリックして、作成しているエンティティが拡張される親エンティティを選択します。
たとえば、新しいエンティティの名前をStaff
にし、拡張フィールドでPersons
エンティティ・オブジェクトを選択します。
「属性」ページの「エンティティ属性」リストに、基礎となる表からベース・エンティティ・オブジェクトに含まれない属性が表示されます。このエンティティ・オブジェクトに含めない属性を選択し、「削除」をクリックします。
たとえば、ここではStaff
エンティティを作成するので、ContractExpires
属性を削除して、DiscountEligible
属性を残します。
「オーバーライド」をクリックして識別子属性を選択し、属性メタデータをカスタマイズしてStaff
サブタイプに個別の「デフォルト値」を提供できるようにします。
たとえば、PersonTypeCode
属性をオーバーライドします。
「属性の設定」ページで、「属性の選択」ドロップダウン・リストを使用して識別子属性を選択します。「デフォルト値」フィールドを変更し、作成しているエンティティ・サブタイプを定義する識別子属性に個別のデフォルト値を設定します。
たとえば、PersonTypeCode
属性を選択し、「デフォルト値」を値staff
に変更します。
「終了」をクリックしてサブタイプ・エンティティ・オブジェクトを作成します。
注意: 同じ手順を繰り返して、 |
継承階層内のエンティティ・オブジェクトにメソッドを追加するには、エンティティ・オブジェクトでカスタムJavaクラスを有効にし、ソース・エディタを使用してメソッドを追加します。
階層内のすべてのエンティティ・オブジェクトに共通するメソッドを追加するには、階層のベース・エンティティ・オブジェクトでカスタムJavaクラスを有効にし、ソース・エディタでメソッドを追加します。たとえば、User
ベース・エンティティ・オブジェクトのPersonsImpl
クラスに次のメソッドを追加すると、階層のすべてのエンティティ・オブジェクトがそれを継承します。
// In PersonsImpl.java public void performPersonFunction() { System.out.println("## performPersonFunction as Customer"); }
階層内のすべてのエンティティ・オブジェクトに共通のサブタイプ・エンティティでメソッドをオーバーライドするには、サブタイプ・エンティティでカスタムJavaクラスを有効にし、「ソース」メニューから「メソッドのオーバーライド」を選択して「メソッドのオーバーライド」ダイアログを開きます。オーバーライドするメソッドを選択し、「OK」をクリックします。次に、オーバーライドするメソッドの実装をソース・エディタでカスタマイズします。たとえば、Staff
サブタイプ・エンティティ・オブジェクトのStaffImpl
クラスでperformPersonFunction()
メソッドをオーバーライドし、実装を次のように変更するものとします。
// In StaffImpl.java public void performPersonFunction() { System.out.println("## performPersonFunction as Staff"); }
サブタイプ階層でエンティティ・オブジェクトのインスタンスについての作業を行う場合、複数の異なるサブタイプのインスタンスを処理することがあります。Staff
エンティティとSupplier
エンティティはPersons
の特別な種類であるため、これらすべてに共通の汎用性の高いPersonsImpl
型を使用して、すべてのエンティティで動作するコードを作成できます。階層内のサブタイプ・ファミリであるクラスのこのような汎用処理を行うとき、Javaは、使用可能なメソッドで最も固有性の高いオーバーライドを常に呼び出します。
つまり、PersonsImpl
のインスタンスでperformPersonFunction()
メソッドを呼び出し、実際にはさらに固有のStaffImpl
サブタイプがある場合、結果の出力は次のようになります。
## performPersonFunction as Staff
これに対し、標準のPersonsImpl
インスタンスによるデフォルトの出力結果は次のようになります。
## performPersonFunction as Customer
階層内のサブタイプ・エンティティ・オブジェクトに固有のメソッドを追加するには、そのエンティティでカスタムJavaクラスを有効にし、ソース・エディタでメソッドを追加します。たとえば、Supplier
サブタイプ・エンティティ・オブジェクトのSupplierImpl
クラスに、次のようなメソッドを追加できます。
// In SupplierImpl.java public void performSupplierFunction() { System.out.println("## performSupplierFunction called"); }
継承を使用すると、新しいベース・エンティティの導入、主キーによるサブタイプ・エンティティの検索、および多相エンティティ・オブジェクトの慣用名を持つビュー・オブジェクトの作成も行うことができます。
InheritanceAndPolymorphicQueries
サンプル・プロジェクトで、Persons
エンティティ・オブジェクトは、PERSONS
表の実際の行に対応するとともに、階層でベース・エンティティの役割も果たしていました。つまり、そのすべての属性は、階層内の全エンティティ・オブジェクトに共通でした。ここで、顧客には固有であっても、スタッフまたはサプライヤには共通しないプロパティがPersons
エンティティに必要な場合は、どうなるでしょう。顧客は顧客満足度調査に参加可能だが、スタッフやサプライヤは参加しない場合を考えてみてください。この要件に対処するためのLastSurveyDate
属性は、Persons
エンティティには必要ですが、それを継承するStaff
およびSupplier
エンティティ・オブジェクトには無意味です。
このような場合は、階層内でベース・エンティティとして機能するBasePersons
といった名前の新しいエンティティ・オブジェクトを導入できます。それは、Persons
、Staff
、およびSupplier
の全エンティティ・オブジェクトに共通するすべての属性を保持します。そして、この表に出現する具体的な行に対応する3つの各エンティティは、BasePersonsから継承する属性と、サブタイプに固有の属性を保持します。BasePersons
型では、PersonTypeCode
属性を識別子属性としてマークするかぎり、「デフォルト値」を空白にしておいてかまいません(または、表のPERSON_TYPE_CODE
列に出現しない他の値にします)。実行時にはBasePersons
エンティティのインスタンスは使用されないため、実際には識別子のデフォルト値は問題ではありません。
エンティティ定義でfindByPrimaryKey()
メソッドを使用するときは、それを呼び出すエンティティ・オブジェクト型のエンティティ・キャッシュのみが検索されます。InheritanceAndPolymorphicQueries
サンプル・プロジェクトが意味するのは、PersonsImpl.getDefinitionObject()
をコールしてPersons
エンティティ・オブジェクトのエンティティ定義にアクセスする場合、そこでfindByPrimaryKey()
をコールすると、たまたま顧客であるキャッシュ内のエントリのみが検索結果となります。これが求める動作である場合もあります。しかし、主キーで継承階層内のサブタイプも含めてエンティティ・オブジェクトを検索する場合は、かわりにEntityDefImpl
クラスのfindByPKExtended()
メソッドを使用できます。この項で説明したPersons
の例では、この代替検索メソッドを使用すると、エンティティ・オブジェクトが顧客、サプライヤまたはスタッフのいずれであっても、主キーで検索できます。その後は、Javaのinstanceof
演算子を使用して発見された型を検査し、PersonsImpl
オブジェクトをさらに固有のエンティティ・オブジェクト型にキャストして、そのサブタイプに固有の機能で作業できます。
継承階層のベース・エンティティ・オブジェクトに対応するエンティティ・オブジェクトの慣用名を持つエンティティ・ベースのビュー・オブジェクトを作成するときは、ベース・エンティティのサブタイプ階層の複数の異なるサブタイプに対応する行を問い合せるように、ビュー・オブジェクトを構成できます。ビュー・オブジェクトの各行は、識別子属性の値の対応に基づいて、適切なサブタイプ・エンティティ・オブジェクトをエンティティ行の部分として使用します。このようなビュー・オブジェクトを設定して使用する具体的な方法については、39.6.2項「多相エンティティ・オブジェクトの慣用名によるビュー・オブジェクトの作成方法」を参照してください。
データベース制約のため、同じトランザクション内でDML操作を実行して複数の関連するエンティティ・オブジェクトに変更を保存するときは、操作の実行順序が重要になる場合があります。外部キー参照を含む新しい行を、参照されている行を挿入する前に挿入しようとすると、データベースで制約違反が発生する可能性があります。コミット時のエンティティ・オブジェクトのデフォルトの処理順序、および必要に応じてその順序をプログラムで変更する方法を理解している必要があります。
注意: この項の例では、Fusion Order Demoアプリケーションの |
デフォルトでは、トランザクションをコミットすると、保留中の変更リスト内のエンティティ・オブジェクトは、時間順、つまりエンティティがリストに追加された順序で処理されます。たとえば、新しいProduct
を作成し、次にその製品に関連する新しいSupplier
を作成すると、新しいProduct
が先に挿入され、その後で新しいSupplier
が挿入されます。
2つのエンティティ・オブジェクトがコンポジットで関連付けられていると、厳密な時間順序が自動的に変更されて、コンポジット関係にある親と子のエンティティ行が制約に違反しない順序で保存されることが保証されます。たとえば、コンポジットされる新しい子のエンティティ行より前に、新しい親のエンティティ行が挿入されます。
関係のあるエンティティが関連付けられてはいてもコンポジットではない場合は、コードを少し作成して、関連するエンティティが正しい順序で保存されるようにする必要があります。
例38-16のPostModule
アプリケーション・モジュールのnewProductForNewSupplier()
カスタム・メソッドについて考えます。このメソッドは、一連のパラメータを受け取って、次の処理を行います。
新しいProduct
を作成します。
新しいSupplier
を作成します。
サービス・リクエストが関係する製品IDを設定します。
トランザクションをコミットします。
Result
Java Beanを作成し、新しい製品IDとサプライヤIDを保持します。
結果を返します。
注意: このコードでは、 |
例38-16 新しいProductとSupplierの作成および新しいIDの戻り
// In PostModuleImpl.java public Result newProductForNewSupplier(String supplierName, String supplierStatus, String productName, String productStatus, Number listPrice, Number minPrice, String shipCode) { oracle.jbo.domain.Date today = new Date(Date.getCurrentDate()); Number objectId = new Number(0); // 1. Create a new product ProductsBaseImpl newProduct = createNewProduct(); // 2. Create a new supplier SuppliersImpl newSupplier = createNewSupplier(); newSupplier.setSupplierName(supplierName); newSupplier.setSupplierStatus(supplierStatus); newSupplier.setCreatedBy("PostingModule"); newSupplier.setCreationDate(today); newSupplier.setLastUpdatedBy("PostingModule"); newSupplier.setLastUpdateDate(today); newSupplier.setObjectVersionId(objectId); // 3. Set the supplier id to which the product pertains newProduct.setSupplierId(newSupplier.getSupplierId().getSequenceNumber()); newProduct.setProductName(productName); newProduct.setProductStatus(productStatus); newProduct.setListPrice(listPrice); newProduct.setMinPrice(minPrice); newProduct.setShippingClassCode(shipCode); newProduct.setCreatedBy("PostingModule"); newProduct.setCreationDate(today); newProduct.setLastUpdatedBy("PostingModule"); newProduct.setLastUpdateDate(today); newProduct.setObjectVersionId(objectId); // 4. Commit the transaction getDBTransaction().commit(); // 5. Construct a bean to hold new supplier id and product id Result result = new Result(); result.setProductId(newProduct.getProductId().getSequenceNumber()); result.setSupplierId(newSupplier.getSupplierId().getSequenceNumber()); // 6. Return the result return result; } private ProductsBaseImpl createNewProduct(){ EntityDefImpl productDef = ProductsBaseImpl.getDefinitionObject(); return (ProductsBaseImpl) productDef.createInstance2(getDBTransaction(), null); }private SuppliersImpl createNewSupplier(){ EntityDefImpl supplierDef = SuppliersImpl.getDefinitionObject(); return (SuppliersImpl) supplierDef.createInstance2(getDBTransaction(), null); }
このメソッドをアプリケーション・モジュールのクライアント・インタフェースに追加し、テスト用のクライアント・プログラムからテストすると、エラーが発生します:
oracle.jbo.DMLConstraintException: JBO-26048: Constraint "PRODUCT_SUPPLIER_FK" violated during post operation: "Insert" using SQL Statement "BEGIN INSERT INTO PRODUCTS( SUPPLIER_NAME,SUPPLIER_STATUS,PRODUCT_NAME, PRODUCT_STATUS,LIST_PRICE,MIN_PRICE, SHIPPING_CLASS_CODE) VALUES (?,?,?,?,?,?,?) RETURNING PRODUCT_ID INTO ?; END;". ## Detail 0 ## java.sql.SQLException: ORA-02291: integrity constraint (FOD.PRODUCT_SUPPILER_FK) violated - parent key not found
PRODUCTS
行が挿入されるとき、その外部キーSUPPLIER_ID
の値がSUPPLIERS
表のどの行にも対応しないというエラーをデータベースが出力します。これは次の理由で発生します:
コードがSupplier
より前にProduct
を作成した。
エンティティ・オブジェクトProducts
とSuppliers
が、関連付けられてはいるがコンポジットではない。
新しいエンティティ行を保存するDML操作が時間順に行われるため、新しいSupplier
より前に新しいProduct
が挿入される。
まだ有効でないサプライヤIDを持つ製品を追加しようとする問題は、例のコード行の順序を変更し、Supplier
を先に作成してからProduct
を作成すると解決できます。目先の問題にはこれで対処できますが、別のアプリケーション開発者が間違った順序でエンティティを作成する可能性が残っています。
さらによい解決策は、エンティティ・オブジェクト自体がポスト順序を処理するようにして、作成順序に関係なく正しく動作するようにすることです。そのためには、関連するエンティティ・オブジェクトを参照する外部キー属性を含むエンティティのpostChanges()
メソッドをオーバーライドし、例38-17に示すようにコードを記述します。この例では、Supplier
エンティティに対する外部キーが含まれているのはProduct
であるため、Product
を更新し、条件によっては、サービス・リクエストをポストする前に関連する新しいSupplier
を強制的にポストするようにします。
このコードでは、ポストしているエンティティがSTATUS_NEW
状態またはSTATUS_MODIFIED
状態のいずれかであるかを検査します。その場合は、getSupplier()
アソシエーション・アクセッサを使用して、関連する製品を取得します。関連するSupplier
のポスト状態もSTATUS_NEW
である場合は、super.postChanges()
を呼び出して自分のDMLを実行する前に、まず関連する親行のpostChanges()
を呼び出します。
例38-17 Supplierを先にポストするためのProductsBaseImplのpostChanges()のオーバーライド
// In ProductsBaseImpl.java public void postChanges(TransactionEvent e) { /* If current entity is new or modified */ if (getPostState() == STATUS_NEW || getPostState() == STATUS_MODIFIED) { /* Get the associated supplier for the product */ SuppliersImpl supplier = getSupplier(); /* If there is an associated supplier */ if (supplier != null) { /* And if it's post-status is NEW */ if (supplier.getPostState() == STATUS_NEW) { /* * Post the supplier first, before posting this * entity by calling super below */ supplier.postChanges(e); } } } super.postChanges(e); }
この状態で例を再び実行すると、newProductForNewSupplier()
メソッドのコードの作成順序を変更しなくても、エンティティは、新しいSupplier
が先で新しいProduct
が後という正しい順序でポストされます。しかし、まだ問題があります。まだ制約違反が発生しますが、今度は理由が異なります。
Suppliers
エンティティ・オブジェクトの主キーがユーザーによって割り当てられている場合は、例38-17のコードのみで、ポスト順序を修正して制約違反に対処できます。
注意: このプログラム的な手法(Java EEアプリケーション・レイヤーで問題を解決する)のかわりに、データベース・レイヤーで遅延可能制約を使用する方法もあります。データベース・スキーマを制御できる場合は、外部キー制約を |
しかし、この例では、Suppliers.SupplierId
はデータベース順序から割り当てられ、ユーザー割当てではありません。そのため、新しいSuppliers
エンティティ行がポストされる時点で、データベースが割り当てる順序値を反映するように、SupplierId
属性が更新されます。新しいサプライヤを参照するProducts.SupplierId
属性の外部キー値は、サプライヤID値のこの更新により親を失います。製品行が保存されるときには、SUPPLIER_ID
の値と一致する行がSUPPLIERS
表にまだ存在しないため、制約違反が再び発生します。次の2つの項では、このような親を失うという問題に対処するための解決策について説明します。
4.10.10項「トリガーによってデータベース順序から割り当てられた主キー値の取得方法」で説明されているように、エンティティ・オブジェクトの主キー属性がDBSequence
型の場合は、そのオブジェクトが作成されるトランザクションの間、キーの数値は一時的に一意の負の値になります。同じトランザクション内で関連するエンティティの値を作成すると、これらの間の関連は、この一時的な負のキー値に基づくことになります。DBSequence
の値が主キーであるエンティティ・オブジェクトをポストすると、その主キーはデータベースが割り当てる正しい順序番号を反映して更新されますが、それに関連付けられたエンティティは一時的な負の外部キー値を保持したままで親を失います。
コンポジットに基づくエンティティ・オブジェクトの場合は、親エンティティ・オブジェクトのDBSequence
値が設定されている主キーが更新されると、コンポジットされる子エンティティ行の一時的な負の外部キー値は、親の更新されたデータベース割当ての主キーを反映して、自動的に更新されます。つまり、コンポジット・エンティティの場合、親を失うという問題は発生しません。
ただし、エンティティ・オブジェクトがコンポジットではないアソシエーションによって関連付けられている場合は、コードを作成して、一時的に負の値を参照する関連エンティティ行を、更新されたデータベース割当ての主キー値で更新する必要があります。次の項では、必要なコードについて説明します。
この例のSuppliers
のようなエンティティがDBSequence
値の主キーを持ち、それと関連付けられた(ただしコンポジットではない)他のエンティティによって外部キーとして参照される場合は、例38-18
に示すように、この新しいSuppliers行を参照する可能性のあるエンティティ行の行セットに対する参照を保存するように、postChanges()
メソッドをオーバーライドする必要があります。現在のSupplier
行のステータスがNewの場合は、getProduct()
アソシエーション・アクセッサから返るRowSet
の値を、super.postChanges()
を呼び出す前に、newProductsBeforePost
メンバー・フィールドに代入します。
例38-18 この新しいSupplierを参照するエンティティ行に対する参照の保存
// In SuppliersImpl.java RowSet newProductsBeforePost = null; public void postChanges(TransactionEvent TransactionEvent) { /* Only bother to update references if Product is a NEW one */ if (getPostState() == STATUS_NEW) { /* * Get a rowset of products related * to this new supplier before calling super */ newProductsBeforePost = (RowSet)getProductsBase(); } super.postChanges(TransactionEvent); }
この保存したRowSet
オブジェクトは、後で例38-19で示されているオーバーライドされたrefreshFKInNewContainees()
メソッドで使用します。新しいエンティティ行は、このメソッドを呼び出すことで、postChanges()
を呼び出す前にそれを参照していた他のエンティティ行を更新された主キー値でカスケード更新できます。newProductsBaseBeforePost
行セット(NULLでない場合)のProductsBaseImpl
行を反復処理し、それぞれのサプライヤID値に、新しくポストされたSupplier
エンティティの、順序から割り当てられた新しいサプライヤ値を設定します。
例38-19 新しいSupplierId値のエンティティ行のカスケード更新
// In SuppliersImpl.java protected void refreshFKInNewContainees() { if (newProductsBeforePost != null) { Number newSupplierId = getSupplierId().getSequenceNumber(); /* * Process the rowset of products that referenced * the new supplier prior to posting, and update their * SupplierId attribute to reflect the refreshed SupplierId value * that was assigned by a database sequence during posting. */ while (newProductsBeforePost.hasNext()) { ProductsBaseImpl svrReq = (ProductsBaseImpl)newProductsBeforePost.next(); product.setSupplierId(newSupplierId); } closeNewProductRowSet(); } }
この変更を実装すると、例38-16のコードは、データベース制約違反を起こさないで動作するようになります。
ADFビジネス・コンポーネントには、開発者が使用できる組込みの宣言的検証規則の基本セットが付属しています。ただし、エンティティ・オブジェクト用のバリデータ・アーキテクチャの強力な機能は、独自のカスタム検証規則を作成できることです。同じ種類の検証コードを繰り返し作成している場合は、カスタム検証規則クラスを作成し、このような共通の検証パターンをパラメータ化された方法で取得できます。
定義したカスタム検証規則クラスは、JDeveloperに登録し、組込み規則と同じように簡単に使用できます。実際、カスタム検証規則をカスタムUIパネルにバンドルすることもできます。JDeveloperはこれを利用して、検証規則で必要とするパラメータを開発者が簡単に使用および構成できるようにします。
エンティティ・オブジェクト用のカスタム検証規則を作成するには、oracle.jbo.rules
パッケージのJboValidatorInterface
を実装するJavaクラスが必要です。「新規ギャラリ」からスケルトン・クラスを作成できます。
カスタム・バリデータを作成するには:
アプリケーション・ナビゲータで、バリデータを作成するプロジェクトを右クリックし、ポップアップ・メニューから「新規」を選択します。
「新規ギャラリ」で「ビジネス層」を展開し、「ADFビジネス・コンポーネント」をクリックして「検証規則」を選択し、「OK」をクリックします。
例38-20に示すように、JBOValidatorInterface
には、1つのメインvalidate()
メソッドと、Description
プロパティ用のgetterおよびsetterメソッドが含まれます。
例38-20 すべての検証規則が実装する必要のあるJboValidatorInterface
package oracle.jbo.rules; public interface JboValidatorInterface { void validate(JboValidatorContext valCtx) { } java.lang.String getDescription() { } void setDescription(String description) { } }
検証規則の動作がパラメータ化されて柔軟性が増してから、検証クラスの各パラメータにBeanプロパティを追加します。たとえば、例38-21のコードにはDateMustComeAfterRule
という名前のカスタム検証規則が含まれており、ある日付属性が別の日付属性より後でなければならないことを検証します。規則を使用する開発者が検証対象の2つの日付として使用する日付属性の名前を構成できるように、このクラスではinitialDateAttrName
とlaterDateAttrName
という2つのプロパティが定義されています。
例38-21は、カスタム検証規則を実装するコードを示しています。これは、AbstractValidator
を拡張し、エンティティ・オブジェクトのカスタム・メッセージ・バンドルの使用を継承しています。開発者がエンティティ・オブジェクトの規則を使用すると、JDeveloperは検証エラー・メッセージをここに保存します。
検証規則のvalidate()
メソッドは、実行時に規則クラスが機能を実行する必要がある場合に常に呼び出されます。このコードが実行する基本的な手順は次のとおりです。
エンティティ・レベルでバリデータが正しくアタッチされていることを確認します。
検証するエンティティ行を取得します。
先の日と後の日の属性の値を取得します。
先の日が後の日より前であることを検証します。
検証が失敗した場合は例外をスローします。
例38-21 カスタムのDateMustComeAfterRule
// NOTE: package and imports omitted public class DateMustComeAfterRule extends AbstractValidator implements JboValidatorInterface { /** * This method is invoked by the framework when the validator should do its job */ public void validate(JboValidatorContext valCtx) { // 1. If validator is correctly attached at the entity level... if (validatorAttachedAtEntityLevel(valCtx)) { // 2. Get the entity row being validated EntityImpl eo = (EntityImpl)valCtx.getSource(); // 3. Get the values of the initial and later date attributes Date initialDate = (Date) eo.getAttribute(getInitialDateAttrName()); Date laterDate = (Date) eo.getAttribute(getLaterDateAttrName()); // 4. Validate that initial date is before later date if (!validateValue(initialDate,laterDate)) { // 5. Throw the validation exception RulesBeanUtils.raiseException(getErrorMessageClass(), getErrorMsgId(), valCtx.getSource(), valCtx.getSourceType(), valCtx.getSourceFullName(), valCtx.getAttributeDef(), valCtx.getNewValue(), null, null); } } else { throw new RuntimeException("Rule must be at entity level"); } } /** * Validate that the initialDate comes before the laterDate. */ private boolean validateValue(Date initialDate, Date laterDate) { return (initialDate == null) || (laterDate == null) || (initialDate.compareTo(laterDate) < 0); } /** * Return true if validator is attached to entity object * level at runtime. */ private boolean validatorAttachedAtEntityLevel(JboValidatorContext ctx) { return ctx.getOldValue() instanceof EntityImpl; } // NOTE: Getter/Setter Methods omitted private String description; private String initialDateAttrName; private String laterDateAttrName; }
カスタム検証規則を簡単に再利用できるように、通常は、規則を利用するアプリケーションでの参照用として規則をJARファイルにパッケージします。
検証規則クラスはBeanであるため、標準のJavaBeanカスタマイザ・クラスを実装して、設計時にBeanのプロパティを設定しやすくすることができます。例38-21のDateMustComeAfter
規則の例では、開発者はinitialDateAttrName
とlaterDateAttrName
の2つのプロパティを構成する必要があります。
図38-3は、Swing用のJDeveloperビジュアル・デザイナを使用して作成されたDateMustComeAfterRuleCustomizer
を示しており、タイトル付きの境界を持つJPanel
に、ドロップダウン用の2つのJLabelプロンプトと2つのJComboBox
コントロールが含まれています。クラスのコードによって、IDEで現在編集されているエンティティ・オブジェクトのDate値属性の名前がドロップダウン・リストに移入されます。これにより、DateMustComeAfterRule
検証をエンティティ・オブジェクトに追加する開発者は、検証の開始日と終了日に使用する日付属性を簡単に選択できます。
カスタマイザとDateMustComeAfterRule
Java Beanを関連付けるには、BeanInfo
クラスを作成するときの標準的な手順に従います。例38-22に示すようにDateMustComeAfterRuleBeanInfo
は、カスタマイザ・クラスをDateMustComeAfter
Beanクラスと関連付けるBeanDescriptorを返します。
通常は、カスタマイザ・クラスとこのBean情報を、設計時専用の独立したJARファイルにパッケージします。
例38-22 カスタマイザとカスタム検証規則を関連付けるためのBeanInfo
package oracle.fodemo...frameworkExt.rules; import java.beans.BeanDescriptor; import java.beans.SimpleBeanInfo; public class DateMustComeAfterRuleBeanInfo extends SimpleBeanInfo { public BeanDescriptor getBeanDescriptor() { return new BeanDescriptor(DateMustComeAfterRule.class, DateMustComeAfterRuleCustomizer.class); } }
カスタム検証規則は作成後、JDeveloper IDEのプロジェクトまたはアプリケーションレベルに追加できるため、他の開発者がその規則を宣言的に使用できます。
エンティティ・オブジェクトを含むプロジェクトでカスタム検証規則を登録するには:
アプリケーション・ナビゲータで、目的のプロジェクトを右クリックして、ポップアップ・メニューから「プロジェクト・プロパティ」を選択します。
「プロジェクト・プロパティ」ダイアログで、「ビジネス・コンポーネント」を展開し、「登録済の規則」を選択します。
「登録済の規則」ページで、「追加」をクリックします。
「検証規則の登録」ダイアログで、作成した検証規則を参照して見つけ(38.9.1項「カスタム検証規則の作成方法」で作成したような検証規則)、「OK」をクリックします。
IDEレベルのカスタム・バリデータを登録するには:
「ツール」メニューで「設定」を選択します。
「ビジネス・コンポーネント」→「規則の登録」ページから、1つまたは複数の検証規則を追加できます。
検証規則を追加するときは、検証規則クラスの完全修飾名を指定し、JDeveloperの使用可能なバリデータのリストで表示される検証規則の名前を設定します。
履歴タイプは、ある時点に固有のデータの追跡に使用されます。JDeveloperにはいくつかの履歴タイプが付属していますが、独自のものも作成できます。標準の履歴タイプとその使用方法の詳細は、4.10.12項「履歴列を使用して作成および変更した日付を追跡する方法」を参照してください。
付属の履歴タイプに限定されることなく、「設定」ダイアログの「履歴タイプ」ページを使用してカスタム履歴タイプを追加または削除してから、カスタムJavaコードを記述して目的の動作を実装できます。カスタム履歴タイプを処理するコードは、再利用できるようアプリケーション全体にわたるエンティティ・ベース・クラスで記述します。
図38-5は、タイプIDが11
のlast update login
カスタム・タイプです。last_update_login
がFND_LOGINS
表の外部キーであると仮定します。
カスタム履歴列タイプを作成するには:
「ツール」メニューで「設定」を選択します。
「設定」ダイアログで、「ビジネス・コンポーネント」を展開し、「履歴タイプ」をクリックします。
「履歴タイプ」ページで「新規」をクリックします。
「履歴タイプ」ダイアログで、名前(スペースの使用可)の文字列値および数値IDを入力します。
「タイプID」は11から126までの整数である必要があります。0 - 10の数値は内部用に予約されています。表示文字列は、次に「属性の編集」ダイアログを使用する際、「履歴列」ドロップダウン・リストに表示されます。
EntityImpl.java
ファイルを開いて、例38-23のような定義を追加します。
例38-24のようなコードで、EntityImpl
ベース・クラスのgetHistoryContextForAttribute(AttributeDefImpl attr)
メソッドをオーバーライドします。
履歴タイプは通常、アプリケーションの存続期間中は値の監査に使用されるため、削除する必要はありません。ただし、削除する必要がある場合は、次の作業を実行します。
「設定」ダイアログで、JDeveloper履歴タイプ・リストから目的の履歴タイプを削除します。
ベースのEntityImpl.getHistoryContextForAttribute
メソッドの履歴タイプをサポートするため実装したカスタム・コードがあれば削除します。
エンティティ属性メタデータの履歴タイプのすべての慣用名を削除します。この履歴タイプを使用するように定義した属性があれば、編集する必要があります。
JDeveloper履歴タイプ・リストから目的の履歴タイプを削除するには:
「ツール」メニューで「設定」を選択します。
「設定」ダイアログで、「ビジネス・コンポーネント」を展開し、「履歴タイプ」をクリックします。
「履歴タイプ」ページで、削除する履歴タイプを選択して「削除」をクリックします。