この章では、エンティティ・オブジェクトを使用して、J2EEアプリケーションで使用する再使用可能なビジネス・ドメイン・オブジェクトのレイヤーを作成する方法について説明します。
この章の内容は次のとおりです。
エンティティ・オブジェクトは、データベース表内の行を表し、そのデータ変更を簡略化するためのADF Business Componentsのコンポーネントです。重要なことは、エンティティ・オブジェクトを使用すると、これらの行のドメイン・ビジネス・ロジックをカプセル化できるため、ビジネス・ポリシーおよびビジネス・ルールを一貫性のある方法で検証できるということです。この章の終わりには、図6-1に示す概念を理解できるようになります。
エンティティ・オブジェクトを定義するには、エンティティ・オブジェクトが示す行が含まれるデータベース表を指定します。
エンティティ・オブジェクトを他のエンティティ・オブジェクトに関連付けることにより、基礎となるデータベース表間の関連を表すことができます。
実行時には、エンティティ行は関連するエンティティ定義オブジェクトによって管理されます。
各エンティティ行は、関連する行キーによって識別されます。
エンティティ行は、データベース・トランザクションを提供するアプリケーション・モジュールのコンテキストで取得および変更します。
アプリケーション・モジュールがエンティティ・オブジェクトを作成、変更または削除し、トランザクションをコミットすると、変更は自動的に保存されます。ServiceRequestおよび作成者のUser、または論理的に含まれるServiceHistoryエントリを組み合せて使用する必要がある場合、エンティティ間のアソシエーションによりタスクが簡略化されます。エンティティ・オブジェクトは、データの有効性を強化するために様々な宣言的ビジネス・ロジック機能をサポートしています。後半の章で詳しく説明しますが、通常、別のカスタム・アプリケーション・ロジックおよびビジネス・ルールを宣言的な検証に補完することにより、最大量のドメイン・ビジネス・ロジックを各エンティティ・オブジェクトに効率的にカプセル化します。関連付けられた一連のエンティティ・オブジェクトによって再使用可能なビジネス・ドメイン・レイヤーが形成されます。このビジネス・ドメイン・レイヤーは、複数のアプリケーションで利用できます。
|
注意: この章の例の動作バージョンを体験するには、サンプルのダウンロード・ページ(http://otn.oracle.com/documentation/jdev/b25947_01/)からDevGuideExamplesワークスペースをダウンロードし、BusinessLayerWithEntityObjectsプロジェクトを参照してください。 |
エンティティ・オブジェクトおよびアソシエーションを作成する最も簡単な方法は、これらを既存の表からリバース・エンジニアリングする方法です。多くの場合、作業対象のデータベース・スキーマがすでに用意されているため、この最も簡単な方法は、実際に使用する最も一般的な方法でもあります。必要な場合には、エンティティ・オブジェクトを最初から作成し、後でこのエンティティ・オブジェクト用の表を生成することもできます。
エンティティ・オブジェクトを作成するには、表からのビジネス・コンポーネント作成ウィザードを使用します。このウィザードは、「新規ギャラリ」の「Business Tier」→「ADF Business Components」カテゴリから起動できます。これがプロジェクトで作成する最初のコンポーネントである場合、「ビジネス・コンポーネント・プロジェクトの初期化」ダイアログが表示され、ウィザードが表示される前にデータベース接続を選択できます。これらの例では、SRDEMOスキーマについてSRDemoという名前の接続を使用していることが前提です。
「エンティティ・オブジェクト」ページのステップ1で、「使用可能」リストからエンティティ・オブジェクトを作成する表のリストを選択します。上部の「パッケージ」フィールドを使用して、すべてのエンティティ・オブジェクトを作成するパッケージを表示できます。「自動問合せ」チェック・ボックスを選択すると、使用可能な表のリストが即座に表示されます。選択しない場合は、必要に応じて名前フィルタを指定した後に「問合せ」ボタンをクリックしてリストを取得する必要があります。「使用可能」リストから表を選択した後、「選択済」リストに、この表について提案されるエンティティ・オブジェクト名とともに、関連する表名がカッコで囲まれて表示されます。「選択済」リストでエンティティ・オブジェクト名をクリックすると、その下にある「エンティティ名」フィールドを使用してデフォルトのエンティティ・オブジェクト名を変更できます。各エンティティ・オブジェクト・インスタンスは特定の表内の単一行を示しているため、エンティティ・オブジェクトの名前には、複数形の名詞ではなく、User、ProductおよびServiceHistoryのような単数形の名詞の使用をお薦めします。図6-2に、SRDEMOスキーマで5つのすべての表を選択し、パッケージ名としてdevguide.model.entitiesを設定し、提案された各エンティティ・オブジェクトの名前を単数形に変更した後のウィザード・ページを示します。
|
注意: エンティティ・オブジェクトはデータベース行を表しているため、エンティティ行と呼ぶのが自然です。また、実行時には、エンティティ行は、そのデータベース行のビジネス・ロジックをカプセル化するJavaオブジェクトのインスタンスであるため、よりオブジェクト指向的なエンティティ・インスタンスという呼び方も適しています。このため、これらの2つの用語は同義です。 |
「終了」をクリックし、エンティティ・オブジェクトを作成します。コンポーネントの作成中、進捗状況ダイアログが表示され、図6-3のようにアプリケーション・ナビゲータで処理結果のコンポーネントを参照できます。「フラット・レベル」コントロールと「タイプでソート」ボタンを使用して、これらの表示効果を試すことができます。
既存の表からエンティティ・オブジェクトを作成する場合、最初に、次の情報を推測するためにデータ・ディクショナリからデータが取得されます。
表の列名に基づくJavaに適したエンティティ属性名(USER_ID -> UserIdなど)
基礎となる列のデータ型に基づく各属性のSQLおよびJavaデータ型
各属性の長さと精度
主キーと一意キーの属性
NOT NULL制約に基づく属性の必須フラグ
外部キーの制約に基づく新規エンティティ・オブジェクトとその他のエンティティ間の関連
これにより、宣言的設定を表すXMLコンポーネント定義ファイルが作成され、パッケージ名と対応するディレクトリに保存されます。前述の説明で作成したdevguide.model.entitiesパッケージ内のエンティティの1つの名前はUserであったため、このXMLファイルは、プロジェクトのソース・パスの下で./devguide/model/entities/User.xmlとして作成されます。このXMLファイルには、表の名前、各エンティティ属性の名前とデータ型、および各属性の列名が含まれます。この内容を確認するには、アプリケーション・ナビゲータでエンティティ・オブジェクトを選択し、構造ウィンドウで対応するソース・フォルダ内を参照して、エンティティ・オブジェクトのXMLファイルを確認します。User.xmlノードをダブルクリックすると、このXMLがエディタで開かれ、その内容を確認できます。
|
注意: IDEレベルのビジネス・コンポーネントのJava生成設定で指定されている場合、オプションのカスタム・エンティティ・オブジェクト・クラスUserImpl.javaもウィザードで生成される場合があります。 |
名前を指定したエンティティ・オブジェクト以外にも、エンティティ・オブジェクト間の関連に関する情報を取得する名前付きアソシエーション・コンポーネントも生成されます。図6-4のデータベース・ダイアグラムを見ると、外部キー制約名をJavaに適した名前に変換してAssoc接尾辞を付けることにより、SvrAssignedToUsrFkAssocやSvrCreatedByUsrFkAssocなどのデフォルトのアソシエーション名が導出されることを確認できます。作成されたアソシエーションごとに、適切なXMLコンポーネント定義ファイルが作成され、パッケージ名と対応するディレクトリに保存されます。デフォルトでは、外部キーからリバース・エンジニアリングされたアソシエーションは、エンティティと同じパッケージ内に作成されます。このため、たとえば、アソシエーションXMLファイルの名前の1つは./devguide/model/entities/SvrAssignedToUsrFkAssoc.xmlになります。
表に主キー制約がない場合、JDeveloperは、エンティティ・オブジェクトの主キーを推測できません。すべてのエンティティ・オブジェクトには少なくとも1つの属性が主キーとしてマークされている必要があるため、ウィザードでは、RowIDという名前の属性が作成され、データベースのROWID値がこのエンティティの主キーとして使用されます。必要な場合、後でエンティティ・オブジェクトを編集し、別の属性を主キーとしてマークし、RowID属性を削除できます。エンティティ・オブジェクト作成ウィザードを使用する場合、その他の属性を主キーとして設定していないと、RowIDを主キーとして使用するよう求められます。
単一のエンティティ・オブジェクトを作成するには、エンティティ・オブジェクト作成ウィザードを使用します。このウィザードは、「新規ギャラリ」の「Business Tier」→「ADF Business Components」カテゴリから起動できます。ステップ1の「名前」ページで既存の表の名前を選択すると、新しいエンティティについても表からのビジネス・コンポーネント作成ウィザードを使用した場合と同じ情報がすべて推測されます。存在しない表の名前を入力する場合、ウィザードの「属性」ページで各属性を1つずつ定義する必要があります。6.2.6項「エンティティ・オブジェクトからのデータベース表の作成」で説明するように、この表は後で手動で作成または生成できます。
表からのビジネス・コンポーネント作成ウィザードまたはエンティティ・オブジェクト作成ウィザードを使用してエンティティ・オブジェクトを作成する場合、エンティティ・オブジェクトを使用して、基礎となる表、シノニムまたはビューを表すことができます。6.2.2.1項「表に主キーがないときに行われる処理」で説明したように、フレームワークは、データ・ディクショナリ内のデータベースの主キーおよび外部キー制約を調べることにより、主キーおよび関連するアソシエーションを推測できます。ただし、選択するスキーマ・オブジェクトがデータベース・ビューである場合、データベース・ビューにはデータベース制約がないため、主キーもアソシエーションも推測できません。この場合、表からのビジネス・コンポーネント作成ウィザードを使用すると、主キーはRowIDにデフォルト設定されます。エンティティ・オブジェクト作成ウィザードを使用する場合、少なくとも1つの属性を主キーとしてマークすることにより、主キーを手動で指定する必要があります。
選択するスキーマ・オブジェクトがシノニムである場合、結果には2種類あります。シノニムが表のシノニムである場合、ウィザードおよびエディタは、表を指定した場合と同じように動作します。かわりに、シノニムがデータベース・ビューを参照している場合、ウィザードおよびエディタは、ビューを指定した場合と同じように動作します。
新しいエンティティ・オブジェクトを作成した後、エンティティ・オブジェクト・エディタを使用してその設定を編集できます。アプリケーション・ナビゲータのポップアップ・メニューで「編集」メニュー・オプションを選択するか、エンティティ・オブジェクトをダブルクリックし、エディタを起動します。エディタの別のパネルを開くことにより、エンティティを定義する設定や、その実行時の動作を制御する設定を調整できます。この章の後半の項では、これらの多くの設定について説明します。
エンティティ・オブジェクトに基づいてデータベース表を作成するには、エンティティ・オブジェクトが含まれるアプリケーション・ナビゲータでパッケージを選択し、ポップアップ・メニューから「データベース・オブジェクトの作成」を選択します。ダイアログが表示され、表の作成元のエンティティを選択できます。このツールを使用して、作成したエンティティ・オブジェクトの表を最初から生成することも、既存の表をドロップして再作成することもできます。
|
注意: この機能を使用する場合、後で実行するDDLスクリプトは生成されません。この機能では、データベースに対して操作が直接実行され、既存の表がドロップされます。作業に進む前に、この処理を実行するかどうかを確認するダイアログが表示されます。既存の表に基づくエンティティの場合、慎重に作業してください。 |
すでにエンティティ・オブジェクトを作成した表も、自分で(またはDBAが)変更する必要がある場合があります。基礎となる表に属性が追加されても既存のエンティティに混乱が生じることはありませんが、J2EEアプリケーションで表内の新しい列にアクセスするには、エンティティ・オブジェクトをデータベース表と同期化する必要があります。この同期化を自動的に実行するには、該当するエンティティ・オブジェクトを選択し、ポップアップ・メニューから「データベースとの同期化」を選択します。たとえば、SQL*Plusのコマンド・プロンプトで次を実行し、新しいSECURITY_QUESTION列をUSERS表に追加したとします。
ALTER TABLE USERS ADD (security_question VARCHAR2(60));
既存のUserエンティティに対して同期化機能を使用した後、図6-5のように「データベースとの同期化」ダイアログが表示されます。
このダイアログでは、自動的に実行可能な変更が提案されます。目的の同期化ボタンをクリックすると、同期化を実行できます。
表からのビジネス・コンポーネント作成ウィザードを使用すると、同時に多くのビジネス・コンポーネントを簡単に生成できます。とはいえ、単にこの処理が可能だという理由でこのウィザードを使用して、データベース・スキーマ内のすべての表についてエンティティ・オブジェクトを即座に作成する必要はありません。アプリケーションでこれらすべての表が使用される場合、このウィザードの使用は適しています。しかし、このウィザードは必要であればいつでも使用できるため、アプリケーションに必要なことがわかっている表についてエンティティ・オブジェクトを作成することをお薦めします。8.9項「アプリケーション・モジュールの粒度の決定」には、ビジネス・サービスのユースケース駆動型設計について説明されています。これらの説明は、アプリケーションのビジネス・ロジック上のニーズをサポートするために必要となるエンティティ・オブジェクトを理解する上で役に立ちます。エンティティ・オブジェクトは、後で必要に応じていつでも追加できます。
データベース表に外部キー制約が定義されていない場合、作成されたエンティティ・オブジェクト間のアソシエーションは自動的に推測されません。ここで説明するいくつかの実行時機能はエンティティ・アソシエーションが存在しているかどうかに依存するため、エンティティ・アソシエーションを手動で作成することをお薦めします。
アソシエーションを作成するには、アソシエーション作成ウィザードを使用します。
たとえば、ServiceRequestおよびServiceHistoryエンティティが存在していない場合、次の手順を使用してこれらを手動で作成できます。
「新規ギャラリ」の「Business Tier」→「ADF Business Components」カテゴリからアソシエーション作成ウィザードを開きます。
ステップ1の「名前」ページで、アソシエーション・コンポーネントの名前とこのコンポーネントを格納するパッケージを指定します。
ステップ2の「エンティティ・オブジェクト」ページで、アソシエーションに含まれ、マスターとして機能するエンティティ・オブジェクトの1つからソース属性を選択します。図6-6に、ServiceRequestエンティティ・オブジェクトからソース・エンティティ・オブジェクトとして選択したSvrId属性を示します。
次に、アソシエーションに含まれるその他のエンティティ・オブジェクトから、対応する関連先属性を選択します。ServiceHistoryの各行には、これらの行を特定のServiceRequest行に関連付けるサービス・リクエストIDが含まれるため、ServiceHistoryエンティティ・オブジェクトでこのSvrId外部キー属性を関連先属性として選択します。
次に、「追加」をクリックし、対応する属性ペアを下にあるソース属性および関連先属性ペアの表に追加します。アソシエーションを定義するために複数の属性ペアが必要な場合、これらのステップを繰り返し、別のソース/ターゲット属性ペアを追加できます。ここでは、必要な属性ペアは1つ(SvrId、SvrId)のみです。
最後に、「カーディナリティ」ドロップダウンに表示されているアソシエーションのカーディナリティが正しいことを確認します。ServiceRequest行と対応するServiceHistory行間の関連は1対多であるため、デフォルト設定のままでかまいません。
ステップ3の「アソシエーションSQL」ページで、ServiceRequestエンティティ・オブジェクトの特定のインスタンスに関連するServiceHistoryエンティティ・オブジェクトにアクセスするために実行時に使用するアソシエーションSQL述語をプレビューできます。
ステップ4の「アソシエーション・プロパティ」ページで、このアソシエーションが一方向関連と双方向関連のどちらであるかを指定することや、アソシエーションの実行時の動作を定義するその他のプロパティを設定することが可能です。図6-7では、「関連元」グループ・ボックスと「関連先」グループ・ボックスの両方で「アクセッサの公開」チェック・ボックスが選択されています。デフォルトでは、アソシエーションは双方向関連であるため、どちらのエンティティ・オブジェクトも必要に応じて反対側の関連エンティティ行にアクセスできます。つまり、この例では、ServiceRequestエンティティ・オブジェクトのインスタンスを操作している場合、関連するServiceHistory行のコレクションに簡単にアクセスできます。また、このインスタンスが属するServiceRequestにも、ServiceHistoryエンティティ・オブジェクトのインスタンスを使用して簡単にアクセスできます。ビジネス検証ロジックを作成する場合、双方向関連ナビゲーションの方が便利なため、通常これらのデフォルトのチェック・ボックス設定のままにしておきます。
「アソシエーション・プロパティ」ページのアクセッサ名のデフォルト設定について検討し、これらの名前をより直感的なものに変更した方が適切かどうかを決定する必要があります。これらのアクセッサ名により、実行時に関連の反対側のエンティティにプログラム的にアクセスするときに使用するアクセッサ属性の名前が定義されます。デフォルトでは、これらのアクセッサ名は、反対側のエンティティ・オブジェクトの名前になります。エンティティのアクセッサ名はエンティティ・オブジェクト属性とその他のアクセッサ間で一意である必要があるため、1つのエンティティを別のエンティティに関連付ける方法が複数存在する場合、デフォルトのアクセッサ名は、この名前を一意にするために数値接尾辞を使用して変更されます。たとえば、ServiceRequestエンティティが、リクエストを作成したユーザーを示すためにUserエンティティに関連付けられた後、2回目は、サービス・リクエストを解決するために割り当てられた技術者を示すために関連付けられたとします。この場合、ServiceRequestエンティティのデフォルトのアクセッサ名は、UserとUser1になります。対応するアソシエーションごとに「アソシエーション・プロパティ」ページを開くことにより、これらのアクセッサ名をCreatedByUserやTechnicianAssignedなどのより直感的なものに変更できます。
デフォルトのアソシエーション名はわかりにくいため、表からエンティティ・オブジェクトを作成した後の最初のタスクとして、これらの名前をわかりやすいものに変更できます。また、アソシエーションは、通常はプロジェクトの最初に構成し、それ以降はそれほど頻繁にアクセスしないコンポーネントであるため、これらを別のパッケージに移動し、エンティティ・オブジェクトを見やすくできます。コンポーネントの名前変更と別のパッケージへの移動は両方とも、JDeveloperのリファクタ機能を使用して直接実行します。一連のビジネス・コンポーネントを別のパッケージに移動するには、アプリケーション・ナビゲータで1つ以上のコンポーネントを選択し、ポップアップ・メニューから「リファクタ」→「移動」を選択します。コンポーネントの名前を変更するには、ナビゲータでこのコンポーネントを選択し、ポップアップ・メニューから「リファクタ」→「名前の変更」を選択します。
ADF Business Componentsをリファクタする場合、コンポーネントに関連するXMLまたはJavaファイルあるいはその両方が自動的に移動されるとともに、これらを参照するその他のコンポーネントが更新されます。図6-8に、すべてのアソシエーションの名前を変更し、これらをdevguide.model.entities.associationsパッケージに移動した後のアプリケーション・ナビゲータの様子を示します。任意のパッケージ名を選択してアソシエーションをリファクタすることにより、このようなサブパッケージを使用してこれらのエンティティ間の論理的な関連を維持しながら、アソシエーションを表示する必要がないときはパッケージを閉じてアソシエーションを非表示にしておくことができます。
アソシエーションを作成すると、適切なXMLコンポーネント定義ファイルが作成され、パッケージ名と対応するディレクトリに保存されます。devguide.model.entities.associationsパッケージ内にServiceHistoriesForServiceRequestという名前のアソシエーションを作成した場合、アソシエーションXMLファイルがServiceHistoriesForServiceRequest.xmlという名前で./devguide/model/entities/associationsディレクトリに作成されます。実行時には、エンティティ・オブジェクトはこのアソシエーション情報を使用して、一連の関連エンティティの操作を自動化します。
コンポジット・アソシエーションを作成する場合、表現可能な関連の種類や様々なオプションについて理解していると役に立ちます。
エンティティ・オブジェクト間の関連を使用して、次のようなソース・エンティティの状態に応じて2つのスタイルの関連を表現できます。
関連先エンティティを参照している。
ネストされた論理的部分として関連先エンティティが含まれる。
図6-9のように、SRDemoアプリケーション・ビジネス・レイヤーには、Product、リクエスト側のUserおよび割り当てられたUser(技術者)を参照するServiceRequestがあります。これらの関連は、第一種のアソシエーションを示します。このアソシエーションでは、UserまたはProductエンティティ・オブジェクトはServiceRequestとは関係なく存在できます。また、ServiceRequestを削除しても、このエンティティが参照していたProductや関連するUserがカスケード削除されることはありません。
一方、ServiceRequestと、関連するServiceHistoryディテールのコレクション間の関連は、単純な参照よりも強力です。ServiceHistoryエントリは、ServiceRequest全体の論理部分を構成しています。つまり、ServiceRequestは、ServiceHistoryエントリで構成されています。ServiceHistoryエンティティ行にとってServiceRequestから独立して存在することには意味がありません。また、ServiceRequestを削除する場合(可能な場合)、そのコンポーネント部分もすべて削除する必要があります。
このタイプの論理関係は、コンポジットと呼ばれる第二種のアソシエーションを示します。図6-9のUMLダイアグラムでは、アソシエーションで他方を構成する側に黒い菱形を使用して、より強力なコンポジット関連を示しています。
表からのビジネス・コンポーネント作成ウィザードでは、ON DELETE CASCADEオプションを持つ外部キーに対してデフォルトでコンポジット・アソシエーションが作成されます。アソシエーションをコンポジット・アソシエーションとして指定するには、アソシエーション作成ウィザードまたはアソシエーション・エディタを使用して、「アソシエーション・プロパティ」ページの「コンポジット・アソシエーション」チェック・ボックスを選択します。コンポジット・アソシエーションがある場合、エンティティ・オブジェクトには実行時に別の動作が追加されます。この制御の詳細と設定は、6.6.3.12項「コンポジット動作の概要および構成」を参照してください。
ビジネス・ドメイン・オブジェクトのレイヤーはチームにとって再使用可能な主要資産となるため、多くの場合、UMLモデルを使用して視覚化すると使いやすくなります。JDeveloperでは、自分やチームのメンバーが参照用として使用できるビジネス・ドメイン・レイヤーのダイアグラムを簡単に作成できるようサポートされています。
エンティティ・オブジェクトのダイアグラムを作成するには、ビジネス・コンポーネント・ダイアグラムの作成ダイアログを使用します。このダイアログは、「新規ギャラリ」の「Business Tier」→「ADF Business Components」カテゴリからアクセスします。このダイアログでは、ダイアグラム名と、ダイアグラムの作成先のパッケージ名を入力するよう求められます。Business Domain Objectsのようなダイアグラム名と、devguide.model.designのようなパッケージ名を入力し、「OK」をクリックして、空のダイアグラムを作成します。
既存のエンティティ・オブジェクトをダイアグラムに追加するには、アプリケーション・ナビゲータでこれらのオブジェクトをすべて選択し、ダイアグラムにドロップします。プロパティ・インスペクタを使用して、パッケージ名を非表示にし、フォントを変更し、グリッドおよび改ページをオフにして、表示しないと曖昧になる2つのアソシエーションの名前を表示します。これで、ダイアグラムは図6-10のようになります。
ビジネス・コンポーネント・ダイアグラムを作成すると、ダイアグラムが格納されているパッケージ名と一致するプロジェクトのモデル・パスのサブディレクトリに、ダイアグラムを示すXMLファイルが作成されます。図6-10のBusiness Domain Objectsダイアグラムの場合、一致する*.oxd_bc4jファイルがモデル・パスの./devguide/model/designサブディレクトリに作成されます。デフォルトでは、アプリケーション・ナビゲータによってプロジェクト・コンテンツ・パスの表示が統一され、ソース・パスのADFコンポーネントおよびJavaファイルがプロジェクト・モデル・パスのUMLモデル・アーティファクトと同じパッケージ・ツリーに表示されます。ただし、図6-11のように、ナビゲータの「ディレクトリの切替え」ツールバー・ボタンを使用すると、必要に応じて、プロジェクト・コンテンツ・パスのルート・ディレクトリを個別に表示できます。
ビジネス・コンポーネントのUMLダイアグラムは、エンティティ・オブジェクトをダイアグラムにドロップしたときのみを示す静的な図ではありません。むしろ、UMLダイアグラムは、現在のコンポーネント定義をUMLベースでレンダリングした図であるため、常に現在の状況を示しています。さらに、UMLダイアグラムは、視覚的なサポートとナビゲーションを提供するツールであるとともに、編集用のツールでもあります。ダイアグラムで任意のエンティティ・オブジェクトについてポップアップ・メニューから「プロパティ」を選択(またはダブルクリック)することにより、エンティティ・オブジェクト・エディタを起動できます。また、エンティティおよびエンティティ属性の名前変更や属性の追加または削除など、エンティティ・オブジェクトの編集タスクをダイアグラム上で直接実行することもできます。
エンティティ・オブジェクトなどのビジネス・コンポーネントをUMLダイアグラムに含めると、JDeveloperで例6-1のように、コンポーネントのXMLコンポーネント・ディスクリプタのDataセクションにメタデータが追加されます。この追加情報が使用されるのは設計時のみです。
例6-1 エンティティ・オブジェクトXMLディスクリプタに追加されたUMLメタデータ
<Entity Name="ServiceRequest" ... >
<Data>
<Property Name ="COMPLETE_LIBRARY" Value ="FALSE" />
<Property Name ="ID"
Value ="ff16fca0-0109-1000-80f2-8d9081ce706f::::EntityObject" />
<Property Name ="IS_ABSTRACT" Value ="FALSE" />
<Property Name ="IS_ACTIVE" Value ="FALSE" />
<Property Name ="IS_LEAF" Value ="FALSE" />
<Property Name ="IS_ROOT" Value ="FALSE" />
<Property Name ="VISIBILITY" Value ="PUBLIC" />
</Data>
:
</Entity>
基本的なエンティティ・オブジェクトのビジネス・ドメイン・レイヤーが準備されている場合、UIコントロール・ヒントを定義して値を即座に追加できます。これにより、エンド・ユーザーに対してドメイン・データをロケールに依存した一貫性のある方法で表示できるようになります。ヒントの格納は、多言語アプリケーションをローカライズしやすい方法で管理されます。この項では、エンティティ・オブジェクト属性のラベル・テキスト、ツールチップおよびフォーマット・マスクのヒントを定義する方法について説明します。第7章「エンティティ・ベースのビュー・オブジェクトを使用した更新可能なデータ・モデルの構築」で説明するように、ビジネス・ドメイン・レイヤーで定義するUIヒントは、エンティティ・ベースのビュー・オブジェクトによって自動的に継承されます。
属性のコントロール・ヒントをエンティティ・オブジェクトに追加するには、エンティティ・オブジェクト・エディタを開き、左側のパネルで「属性」ノードを開き、エンティティの属性のリストを表示します。図6-12に、ServiceRequestエンティティ・オブジェクトを例としてこのエディタの様子を示します。RequestDateなどの特定の名前を選択し、「コントロール・ヒント」タブを選択すると、次を設定できます。
「ラベル・テキスト」ヒント: Requested On
「ツールチップ・テキスト」ヒント: The date on which the service request was created
フォーマットの種類: Simple Date
その他の属性を選択し、これらの属性に適したコントロール・ヒントを定義することもできます。
|
注意: Javaで定義される数値および日付のフォーマット・マスクの標準セットは、OracleデータベースのSQLおよびPL/SQL言語によって使用されるものとは異なります。詳細は、java.text.DecimalFormatおよびjava.text.SimpleDateFormatクラスのJavadocを参照してください。 |
エンティティ・オブジェクトの属性のコントロール・ヒントを定義すると、これらを格納する標準のJavaメッセージ・バンドル・ファイルが作成されます。このファイルは、関連付けられているエンティティ・オブジェクト・コンポーネントに固有のもので、このコンポーネントに基づいて名前が付けられます。devguide.model.entitiesパッケージ内のServiceRequestエンティティの場合、作成されるメッセージ・バンドル・ファイルの名前はServiceRequestImplMsgBundle.javaになり、devguide.model.entities.commonサブパッケージに作成されます。アプリケーション・ナビゲータでServiceRequestを選択すると、各コンポーネントの実装ファイルのグループを示す構造ウィンドウのソース・フォルダにこの新しいファイルが追加されていることを確認できます。例6-2に、メッセージ・バンドル・ファイルにコントロール・ヒント情報がどのように表示されるかを示します。各String配列の最初のエントリはメッセージ・キーです。2番目のエントリは、このキーに対応するロケール固有のString値です。
例6-2 エンティティ・オブジェクト・コンポーネントのメッセージ・バンドル・クラスに格納されるロケールに依存したコントロール・ヒント
package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
// ---------------------------------------------------------------------
// --- File generated by Oracle ADF Business Components Design Time.
// ---------------------------------------------------------------------
public class ServiceRequestImplMsgBundle extends JboResourceBundle {
static final Object[][] sMessageStrings = {
{ "AssignedDate_FMT_FORMAT", "MM/dd/yyyy HH:mm" },
{ "AssignedDate_FMT_FORMATTER", "oracle.jbo.format.DefaultDateFormatter" },
{ "AssignedDate_LABEL", "Assigned On" },
{ "AssignedTo_LABEL", "Assigned To" },
{ "CreatedBy_LABEL", "Requested By" },
{ "ProblemDescription_DISPLAYWIDTH", "60" },
{ "ProblemDescription_LABEL", "Problem" },
{ "RequestDate_FMT_FORMAT", "MM/dd/yyyy HH:mm" },
{ "RequestDate_FMT_FORMATTER", "oracle.jbo.format.DefaultDateFormatter" },
{ "RequestDate_LABEL", "Requested On" },
{ "RequestDate_TOOLTIP",
"The date on which the service request was created" },
{ "Status_LABEL", "Status" },
{ "SvrId_LABEL", "Request" }
};
// etc.
ADF Business Componentsを使用して作成されたアプリケーションのモデル・レイヤーを国際化するには、各コンポーネントのメッセージ・バンドル・ファイルの翻訳バージョンを作成する必要があります。たとえば、ServiceRequestImplMsgBundleメッセージ・バンドルのイタリア語バージョンはServiceRequestImplMsgBundle_itという名前のクラスになり、さらに限定されたスイス・イタリア語バージョンはServiceRequestImplMsgBundle_it_chになります。通常、これらのクラスにより、ベース・メッセージ・バンドル・クラスが拡張されます。また、これらのクラスには、ローカライズが必要なメッセージ・キーのエントリとともに、ローカライズされたこれらの翻訳が含まれます。
たとえば、ServiceRequestエンティティ・オブジェクトのメッセージ・バンドルのイタリア語バージョンは、例6-3のようになります。このイタリア語バージョンでは、RequestDateおよびAssignedDateのフォーマット・マスクがdd/MM/yyyy HH:mmに変更されています。これにより、イタリア語のユーザーには、2006年5月3日の日付値は、デフォルトのメッセージ・バンドルのフォーマット・マスクで生成される05/03/2006 15:55ではなく03/05/2006 15:55として表示されます。オーバーライドされたgetContents()メソッドに注意してください。このメソッドは、スーパークラス・バンドルからオーバーライドされていない文字列にマージされた、より限定された翻訳文字列によるメッセージ配列を戻します。実行時には、現在のユーザーのロケール設定に基づいて適切なメッセージ・バンドルが自動的に使用されます。
例6-3 イタリア語にローカライズされたエンティティ・オブジェクト・コンポーネントのメッセージ・バンドル
package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
public class ServiceRequestImplMsgBundle_it
extends ServiceRequestImplMsgBundle {
static final Object[][] sMessageStrings = {
{ "AssignedDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" },
{ "AssignedDate_LABEL", "Assegnato il" },
{ "AssignedTo_LABEL", "Assegnato a" },
{ "CreatedBy_LABEL", "Aperto da" },
{ "ProblemDescription_LABEL", "Problema" },
{ "RequestDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" },
{ "RequestDate_LABEL", "Aperto il" },
{ "RequestDate_TOOLTIP", "La data in cui il ticket è stato aperto" },
{ "Status_LABEL", "Stato" },
{ "SvrId_LABEL", "Ticket" }
};
public Object[][] getContents() { return super.getMergedArray(sMessageStrings, super.getContents()); }
}
エンティティ・オブジェクトには、通常のエンタープライズ・ビジネス・アプリケーションの実装を簡略化するための様々な宣言的機能が用意されています。タスクによっては、宣言的機能のみでニーズが満たされる場合があります。しかし、より複雑なビジネス・ロジックや検証規則をビジネス・ドメイン・レイヤーに実装するために宣言的動作を超える機能が必要な場合は、これらの機能も使用できます。この章では、宣言的機能について詳しく説明します。カスタム・コードを使用した最も一般的なエンティティ・オブジェクトの拡張方法は、第9章「エンティティ・オブジェクト内のプログラム的なビジネス・ルールの実装」を参照してください。
エンティティ・オブジェクトの宣言的な実行時動作を構成するには、エンティティ・オブジェクト・エディタを使用します。このエディタにアクセスするには、アプリケーション・ナビゲータでエンティティを選択し、ポップアップ・メニューから「編集」を選択します。図6-13に、ServiceRequestエンティティ・オブジェクトを例としてこのエディタの様子を示します。
「名前」ページで、エンティティ・オブジェクトの名前を確認し、エンティティ・オブジェクトが関連付けられているデータベース表を構成できます。「属性」ページで、エンティティ・オブジェクトに関連するデータを示す属性を作成または削除できます。このノードを開くと、エンティティ・オブジェクトの各属性のプロパティにアクセスできます。
「チューニング」ページで、単一のトランザクションで同じタイプの複数のエンティティを作成、変更または削除するときのデータベース操作をより効率的にするためのオプションを設定できます。「イベント発行」ページで、エンティティ・オブジェクトがその状態の変化を他のエンティティ・オブジェクトに通知するために使用可能なイベントを定義できます。必要な場合、配信されるイベントにエンティティ・オブジェクトの属性のすべてまたは一部を含めることができます。「サブスクライブ」ページで、選択した他のエンティティ・オブジェクトのイベントが発行されたときに通知を受けるエンティティ・オブジェクトを登録します。「認可」ページで、任意またはすべての属性についてロール・ベースの更新可能性権限を定義します。最後に、「カスタム・プロパティ」ページで、実行時にエンティティでアクセス可能なカスタム・メタデータを定義できます。
|
注意: エンティティの属性名のリストが長い場合、目的の属性を簡単に見つける方法があります。ツリー内の「属性」ノードが開かれた状態で属性名を入力すると、入力した文字に応じて属性がインクリメンタル検索され、ツリー内のその名前が表示されます。 |
エンティティ・オブジェクトの実行時動作を記述および制御するすべての宣言的設定は、XMLコンポーネント定義ファイルに格納されます。エディタを使用してエンティティの設定を変更する場合、「OK」を押すと、コンポーネントのXML定義ファイルおよびオプションのカスタムJavaファイルが更新されます。変更を即座に適用し、エディタでの作業を続ける必要がある場合は、「適用」ボタンを使用します。一般的に、編集中の変更適用が役に立つのは、コンポーネントのカスタムJavaファイルを初めて生成するときに、エディタで別のページを開く前にこれらのファイルを生成する場合のみです。
エンティティ・オブジェクトの宣言的機能の多くはその属性の設定に関連付けられているため、この項では、図6-13に示されている重要なオプションについて詳しく説明します。
「永続的」プロパティにより、属性値が基礎となる表内の列に対応するか、単なる一時的な値であるかを制御します。属性が永続的である場合、「データベース列」領域を使用して、属性に対応する基礎となる列の名前を変更し、精度およびスケール情報を使用してその列型を指定できます(VARCHAR2(40)やNUMBER(4,2)など)。エンティティ・オブジェクトは、実行時にこの情報に基づいて、属性値の最大長および精度/スケールを設定し、値が要件を満たさない場合は例外をスローします。
表からのビジネス・コンポーネント作成ウィザードとエンティティ・オブジェクト作成ウィザードでは、各エンティティ・オブジェクト属性のJava型は、関連付けられている列のデータベース列型のSQL型から自動的に推測されます。「属性」の「型」フィールドを使用すると、エンティティ属性のJava型を目的の型に変更できます。「データベース列」の「型」フィールドには、属性がマップされている基礎となるデータベース列のSQL型が表示されます。「データベース列」の「名前」フィールドにより、属性がマップされる列が制御されます。
エンティティ・オブジェクトは、表6-1に示す列型の表を処理できます。java.lang.Stringクラスを除いて、デフォルトのJava属性タイプはすべてoracle.jbo.domainおよびoracle.ord.imパッケージに格納されており、対応する型のOracleデータベースのデータを効率的に処理できるようサポートされています。Javaの「型」フィールドには、その他の多くの共通型が含まれており、これらもサポートされています。
表6-1 デフォルトのエンティティ・オブジェクトの属性タイプのマッピング
| Oracleの列型 | エンティティの列型 | エンティティのJava型 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
注意: ここで示した型以外にも、java.io.Serializableインタフェースが実装されていることを前提に、任意のJavaオブジェクト型をエンティティ・オブジェクトの属性の型として使用できます。 |
VARCHAR2(n)などの最大長の定義をサポートする型を使用する場合、「データベース列」の「型」フィールドには、値の一部として属性の最大長が含まれます。このため、たとえば、データベースのVARCHAR2(10)列に基づく属性の場合、「データベース列」の「型」としてVARCHAR2(10)が表示され、最初に最大長として10文字が示されます。なんらかの理由により、String値の属性の最大長を、基礎となる列で使用可能な文字数より少ない文字数に制限する必要がある場合は、「データベース列」の「型」値の最大長を変更します。たとえば、USERS表のEMAIL列はVARCHAR2(50)であるため、デフォルトでは、Usersエンティティ・オブジェクトのEmail属性も同じ型に設定されます。実際の電子メール・アドレスが常に8文字以下である場合、Email属性のデータベース列の型をVARCHAR2(8)に更新し、エンティティ・オブジェクト・レベルでの最大長を8文字に設定できます。
これは、NUMBER(p[,s])のように精度やスケールの定義をサポートするデータベース列の型に関連する属性についても同様です。このため、たとえば、データベースのNUMBER(7,2)列に基づく属性の精度を5、スケールを1に制限するには、「データベース列」の「型」をNUMBER(5,1)に更新します。
「更新可能」設定により、特定の属性をいつ更新できるかを制御します。設定内容は、次のとおりです。
常に: 属性は常に更新可能です。
なし: 属性は読取り専用です。
新規の間: エンティティ行を初めて作成するトランザクション中は属性を設定できますが、データベースへのコミットが成功した後、属性は読取り専用になります。
「主キー」プロパティにより、属性がエンティティを一意に識別するキーの一部であるかどうかを指定します。通常、主キーには単一属性を使用しますが、複数属性の主キーも完全にサポートされています。
実行時には、getKey()メソッドを使用して任意のエンティティ行に関連するKeyオブジェクトにアクセスすると、このKeyオブジェクトには、このエンティティ・オブジェクトの主キー属性の値が含まれます。このエンティティ・オブジェクトに複数の主キー属性がある場合、Keyオブジェクトにはこれらの各値が含まれます。重要なのは、これらの値はエンティティ・オブジェクト定義で対応する主キー属性と同じ相対順序で表示されるということです。
たとえば、ServiceHistoryエンティティ・オブジェクトには、SvrIdおよびLineNoという複数の主キー属性があります。エンティティ・オブジェクト・エディタの「属性」ページでは、SvrIdが先頭で、LineNoが2番目です。ServiceHistory型のエンティティ行のKeyオブジェクトによってカプセル化された値の配列では、これらの2つの属性値がこれとまったく同じ順序になります。このことを理解するのが重要な理由は、findByPrimaryKey()を使用して複数属性の主キーを持つエンティティを検索する場合、作成したKeyオブジェクトでこれら複数の主キー属性の順序が間違っていると、エンティティ行が期待されたとおりに見つからないためです。
「デフォルト」フィールドにより、属性の静的デフォルト値を指定します。たとえば、ServiceRequestエンティティ・オブジェクトのStatus属性のデフォルト値をOpenに設定したり、Userエンティティ・オブジェクトのUserRole属性のデフォルト値をuserに設定できます。
基礎となる列値が挿入または更新操作時にデータベース・トリガーによって更新される場合、「リフレッシュ」の「挿入後」または「リフレッシュ」の「更新後」チェック・ボックスをそれぞれ選択することにより、変更された値をフレームワークが自動的に取得し、エンティティ・オブジェクトとデータベース行を同期化できます。エンティティ・オブジェクトは、INSERTまたはUPDATEを実行しながらOracle SQLのRETURNING INTO機能を使用して、変更された列を1回のデータベースのラウンドトリップでアプリケーションに戻します。
|
注意: DBLINKを介してリモート表に解決されるシノニムのエンティティ・オブジェクトを作成する場合、この機能を使用すると、実行時に次のようなエラーが発生します。JBO-26041: Failed to post data to database during "Update" ## Detail 0 ## ORA-22816: unsupported feature with RETURNING clause このようなデータベース制限を回避する技術は、26.5項「結合ビューまたはリモートDBLinkに基づくエンティティ・オブジェクト」を参照してください。 |
「リフレッシュ」の「挿入後」が使用される一般的なケースは、主キー属性の値がBEFORE INSERT FOR EACH ROWトリガーによって割り当てられる場合です。多くの場合、このトリガーにより、次のようなPL/SQLロジックを使用してデータベース順序から主キーが割り当てられます。
CREATE OR REPLACE TRIGGER ASSIGN_SVR_ID
BEFORE INSERT ON SERVICE_REQUESTS FOR EACH ROW
BEGIN
IF :NEW.SVR_ID IS NULL OR :NEW.SVR_ID < 0 THEN
SELECT SERVICE_REQUESTS_SEQ.NEXTVAL
INTO :NEW.SVR_ID
FROM DUAL;
END IF;
END;
図6-14のように、「属性」の「型」をDBSequenceという名前の組込みデータ型に設定すると、データベース順序によって主キーが自動的に割り当てられます。このデータ型を設定すると、「リフレッシュ」の「挿入後」プロパティが自動的に有効化されます。
主キーがDBSequenceである新しいエンティティ行を作成すると、一意の負の数値が一時値として割り当てられます。この値は、このエンティティ行を作成するトランザクション中、主キーとして機能します。同じトランザクション内で一連の相関エンティティを作成する場合、その他の新しい関連エンティティ行にこの一時値を外部キー値として割り当てることができます。トランザクションのコミット時に、エンティティ・オブジェクトは、RETURNING INTO句を使用してINSERT操作を発行し、実際にデータベースのトリガーによって割り当てられた主キー値を取得します。先にこの一時的な負の値を外部キーとして使用していた新しい関連エンティティでは、マスターの実際の新しい主キーを反映してこの値が更新されます。
|
注意: 図6-14のように、通常、DBSequence値を持つ主キーの「更新可能」プロパティも「なし」に設定します。一時IDはエンティティ・オブジェクトによって割り当てられ、INSERTオプションの後に実際のID値を使用してリフレッシュされます。エンド・ユーザーがこの値を更新する必要はありません。 |
|
注意: 「順序」タブに表示されている順序名が設計時に使用されるのは、6.2.6項「エンティティ・オブジェクトからのデータベース表の作成」で説明されている「データベース表の作成...」機能を使用する場合のみです。ここで示す順序は、エンティティ・オブジェクトの基礎となる表とともに作成されます。 |
実行時には、フレームワークによりエンティティ・オブジェクトの更新の上書きが自動的に検出され、作業中に別のユーザーによって更新およびコミットされたデータであることに気付かずにユーザーがこのデータを変更できないようにします。通常、このチェックは、基礎となる行がロックされているときにデータベース内で対応する現在の列値と各永続エンティティ属性の元の値を比較することによって実行されます。データベースの現在の状態と一致しない行の更新が検出されると、RowInconsistentExceptionが発行されます。
更新の上書きの検出をより効率的にするには、エンティティが変更されるたびに値が更新されるエンティティの属性を特定します。通常、このような値には、行内のバージョン番号列や更新日付列などがあります。作成したデータベース・トリガーによって更新識別子の属性値を割り当て、「リフレッシュ」の「挿入後」および「リフレッシュ」の「更新後」プロパティを使用してエンティティ・オブジェクトでこれらの値をリフレッシュできます。また、次の項で説明する履歴属性の機能を使用してエンティティ・オブジェクトによって更新識別子の属性値の更新が管理されるように指定できます。ユーザーによる問合せ以降に行が変更されたかどうかを最も効率的な方法で検出するには、「更新識別子」を選択し、更新識別子の属性値のみを比較します。
エンティティ・オブジェクトでは、次のような履歴情報を頻繁に追跡する必要があります。
誰がこのエンティティを作成したか
いつ作成したか
このエンティティを最後に変更したのは誰か
いつ変更したか
この行は何回変更されたか
図6-15のように、この情報は「履歴列」属性に格納されます。
属性のデータ型がNumber、StringまたはDateであり、主キーの一部ではない場合、このプロパティを有効化して履歴監査用の属性値を自動的に管理できます。属性の処理方法は、指定した履歴属性タイプによって異なります。「履歴列」で「バージョン番号」型を選択すると、オブジェクトが更新されるたびに数値属性の値が自動的に増分されます。「作成者」、「作成日付」、「変更者」または「変更日付」を選択すると、オブジェクトが作成または変更されたときに、それぞれ現在のユーザーのユーザー名または現在の日付を使用して値が更新されます。
場合によっては、論理的に関連付けられた様々なオブジェクトに関する情報が1つのデータベース表に格納されることがあります。たとえば、給与計算アプリケーションで処理するアルバイト(Hourly)、正社員(Salaried)、契約社員(Contract)がすべて、EMPLOYEE_TYPE列を持つ1つのEMPLOYEES表に格納される場合があります。EMPLOYEE_TYPE列では、H、SまたはCなどの値を使用して、特定の行がそれぞれアルバイト、正社員、契約社員のいずれを表すのかを示します。従業員の属性や動作の多くはすべての従業員について同じですが、特定のプロパティやビジネス・ロジックは従業員のタイプによって異なります。この場合、このような異なるタイプの従業員を表すために継承階層を使用すると役に立ちます。すべての従業員に共通の属性およびメソッドは、ベースとなるEmployeeエンティティ・オブジェクトの一部とします。一方、HourlyEmployee、SalariedEmployeeおよびContractEmployeeなどのサブタイプのエンティティ・オブジェクトにより、ベースとなるEmployeeオブジェクトを拡張し、別のプロパティや動作を追加します。「多相化識別子」属性プロパティを使用して、行のタイプを識別する属性の値を示します。継承の設定および使用方法は、26.6項「ビジネス・ドメイン・レイヤーでの継承の使用」を参照してください。
エンティティ・オブジェクトがその他のエンティティを構成している場合、ネストされた他のエンティティ・オブジェクト部分の論理コンテナとしての役割を正しく果すために、別の実行時動作が示されます。エンティティ・オブジェクトの構成については、次の機能が常に有効です。
構成されるエンティティ・オブジェクトが作成されると、この値が既存のエンティティを所有する親のエンティティとして識別しているか確認するため、外部キー属性の値の存在がチェックされます。作成時に外部キーの値を指定できなかった場合や、既存のエンティティ・オブジェクトを識別していない値を指定した場合、親エンティティが識別されていない状態で親のない子行は許可されず、InvalidOwnerExceptionがスローされます。
|
注意: 外部キー属性の値の存在がチェックされたことにより、現在のトランザクションで新しい保留エンティティが検索される他、必要に応じてデータベース内の既存のエンティティが検索されます。 |
この機能により、構成する側のエンティティ・オブジェクトと構成される側のエンティティ・オブジェクトの両方が含まれるトランザクションで実行されるDML操作が正しい順序で実行されたかどうかを確認します。たとえば、構成する側の新しい親エンティティ・オブジェクトに対するINSERT文は、構成される側の子エンティティ・オブジェクトに関連するDML操作の前に実行されます。
Refresh-On-Insert主キーを持つ新しいエンティティ行を保存すると、トリガーによって割り当てられた主キー値の取得後に、構成される側のエンティティの外部キー属性値が自動的に更新されて新しい主キー値が反映されます。
これ以外にも、アソシエーション作成ウィザードの「アソシエーション・プロパティ」ページの設定またはアソシエーション・エディタを介して制御可能なコンポジット関連機能が多数用意されています。図6-16に、ServiceRequestエンティティ・オブジェクトとServiceHistoryエンティティ・オブジェクト間のServiceHistoriesForServiceRequestアソシエーションが表示されたこのページの様子を示します。これらの設定は、データベースのON DELETE CASCADE外部キー制約からコンポジションをリバース・エンジニアリングした結果のデフォルト設定です。
別の機能およびこれらの動作に影響するプロパティは、次のとおりです。
構成される側の子が存在していても、構成する側の親の削除を有効化または禁止できます。「カスケード削除の実装」が選択されていない場合に、構成する側の親に構成される側の子が含まれる場合、構成する側のエンティティ・オブジェクトの削除は禁止されます。このオプションを選択すると、構成する側のエンティティ・オブジェクトを無条件に削除でき、構成される側のエンティティ・オブジェクトも削除されます。関連する「データベースのカスケード削除用に最適化」オプションが選択されていない場合、構成される側のエンティティ・オブジェクトでは、トランザクションのコミット時に通常のDELETE文が実行され、変更が永続的になります。このオプションを選択すると、データベースのON DELETE CASCADE制約によって対応する行の削除が処理されることを前提に、構成される側のエンティティではDELETE文は実行されません。
「カスケード更新キー属性」オプションを選択すると、構成する側のエンティティの主キー値が変更される場合、構成される側のエンティティの外部キー属性値が自動で更新されるよう設定できます。
エンティティ・オブジェクト・エディタで特に注意する必要があるページの1つに「検証」ページがあります。このページでは、エンティティまたはその属性の宣言的な検証規則を参照および管理できます。フレームワークは、ユーザーが保留中の変更をコミットしようとした場合のみならず、行を移動しようとした場合にも、エンティティ・レベルの検証規則を適用します。また、ユーザーが関連属性の値を変更する場合、属性レベルの検証規則が適用されます。検証規則を追加する場合、適切なエラー・メッセージを指定します。このエラー・メッセージは、後で必要に応じて他の言語に簡単に翻訳できます。Oracle ADFには、この項で説明する多くの組込みの宣言的な検証規則が付属されています。Method Validatorを使用したカスタム検証コードの起動方法は9.3項「Method Validatorの使用」、独自のカスタム規則を使用した宣言的規則の基本セットの拡張方法は26.9項「カスタム検証規則の実装」を参照してください。
エンティティ・オブジェクトに検証規則を追加するには、図6-17のように、エンティティ・オブジェクト・エディタの「検証」ページを使用します。属性レベルの検証規則を追加するには、「宣言済の検証規則」ツリーで属性を選択し、「新規」をクリックします。エンティティ・レベルの検証規則の定義の場合も同様ですが、「新規」をクリックする前にルート・エンティティ・オブジェクト・ノードを選択する点が異なります。
新しい検索規則を追加する場合、「検証規則の追加」ダイアログが表示されます。「規則」ドロップダウン・リストを使用して、目的の検証規則を選択し、ページ内のその他のコントロールを使用して宣言設定を構成します。このコントロールの内容は、選択する検証規則の種類によって異なります。図6-18に、ServiceRequestエンティティ・オブジェクトのProdId属性にレンジ検証規則を定義するときの「検証規則の追加」ダイアログの様子を示します。この検証規則は、値のレンジを100〜999に設定するためのものです。検証規則を追加する場合、検証規則に違反したときにユーザーに対して表示されるエラー・メッセージを入力することもできます。
エンティティ・オブジェクトに検証規則を追加すると、XMLコンポーネント定義が更新され、使用した規則と入力した規則プロパティを示すエントリが含まれます。たとえば、前述のレンジ検証規則をProdId属性に追加すると、XMLファイルにRangeValidationBeanエントリが含まれます。
<Entity Name="ServiceRequest"
<!-- : -->
<Attribute Name="ProdId" IsNotNull="true" Precision="8" Scale="0"
ColumnName="PROD_ID" Type="oracle.jbo.domain.Number"
ColumnType="NUMBER" SQLType="NUMERIC" TableName="SERVICE_REQUESTS" >
<RangeValidationBean
xmlns="http://xmlns.oracle.com/adfm/validation"
ResId="ProdId_Rule_0"
OnAttribute="ProdId"
OperandType="LITERAL"
MinValue="100"
MaxValue="999" >
</RangeValidationBean>
</Attribute>
<!-- : -->
</Entity>
実行時に、この規則はこの宣言情報に基づいてエンティティ・オブジェクトによって自動的に適用されます。エラー・メッセージは翻訳可能文字列であり、エンティティ・オブジェクトのメッセージ・バンドル・クラスの翻訳可能UIコントロール・ヒントと同じように管理されます。バリデータに関するXMLコンポーネント定義エントリのResIdプロパティは、メッセージ・バンドルのStringキーと対応しています。例6-4に、関連するServiceRequestエンティティ・オブジェクトのメッセージ・バンドルの一部を示します。ここでは、ProdId_Rule_0キーがデフォルト・ロケールのエラー・メッセージとともに表示されています。検証エラー・メッセージは、前述のUIコントロール・ヒントについて説明したものと同じメカニズムを使用して翻訳されます。
例6-4 検証エラー・メッセージが含まれるエンティティ・オブジェクトのメッセージ・バンドル
package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
// ---------------------------------------------------------------------
// --- File generated by Oracle ADF Business Components Design Time.
// ---------------------------------------------------------------------
public class ServiceRequestImplMsgBundle extends JboResourceBundle {
static final Object[][] sMessageStrings = {
// other strings here
{ "ProdId_Rule_0", "Valid product codes are between 100 and 999" },
// other strings here
};
// etc.
}
バリデータにはエンティティ・レベルで使用できるものと、属性レベルで使用できるものがあることを理解することが重要です。また、List Validatorは比較的小さなセットを対象として設計されていることにも注意してください。
次の組込みバリデータは、エンティティ・オブジェクト・レベルで使用できます。
エンティティの主キーが一意であるかどうかを検証します。
エンティティ・オブジェクトのカスタムJavaクラスのメソッドを起動し、プログラム的な検証を評価します。
次の組込みバリデータは、エンティティ・オブジェクトの属性レベルで使用できます。
属性値を次と比較して検証します。
リテラル値
ビュー・オブジェクトの問合せ結果の先頭行の選択属性
SQL問合せ結果の先頭行の先頭列
次に基づいてメモリー内の値セットに属性が存在するかどうかを検証します。
静的リスト
ビュー・オブジェクトのデフォルトの行セットの行の選択属性
SQL問合せ結果の行の先頭列の値
属性が最小値と最大値の間に収まっているかどうかを検証します。
属性値の文字列長が、文字の定数より小さい、等しい、またはそれより大きいかどうかを検証します。
属性値が正規表現に準拠しているかどうかを検証します。
エンティティ・オブジェクトのカスタムJavaクラスのメソッドを起動し、プログラム的な検証を評価します。
List Validatorは、比較的小さい値セットに対して属性を検証するためのものです。図6-19のように、「問合せ結果」または「ビュー・オブジェクト属性」スタイルのリスト検証を選択する場合、検証対象の属性値がリスト内の属性と一致するかどうかを検証するためにメモリー内スキャンが実行される前に、問合せ結果のすべての行が取得されることに注意してください。バリデータのSQLまたはビュー・オブジェクト問合せによって実行される問合せでは、問合せのWHERE句で検証される値は参照されません。
つまり、ユーザーが入力した製品コードが、膨大な量の製品が含まれる表に存在するかどうかを検証する場合、この機能は適していません。データベースに対して目的の検証問合せを実行するためにビュー・オブジェクトを使用してSQLベースの検証を効率的に実行するための技術は、9.6項「検証でのビュー・オブジェクトの使用」を参照してください。
外部クライアント・プログラムは、アプリケーション・モジュールにアクセスしてデータ・モデル内の任意のビュー・オブジェクトを操作できますが、UIベースのクライアントやプログラム的クライアントは、設計上、エンティティ・オブジェクトを直接操作できません。第7章「エンティティ・ベースのビュー・オブジェクトを使用した更新可能なデータ・モデルの構築」では、ビュー・オブジェクトの柔軟なSQL問合せ機能に、エンティティ・オブジェクトのビジネス・ロジックとデータベースとの自動対話機能を組み合せることによる、非常に強力なアプリケーション構築機能の組合せの簡単な実現方法について説明します。この組合せにより、現在のエンド・ユーザーのタスク上のニーズに対応するよう設計され、再使用可能なドメイン・ビジネス・オブジェクト・レイヤー内で一元管理されたビジネス・ロジックを共有する、完全に更新可能なアプリケーション・モジュールのデータ・モデルを作成できます。
ただし、まず重要なのは、ビュー・オブジェクトとエンティティ・オブジェクトを組み合せた能力の利用方法について学習する前に、これらのオブジェクト自体の使用方法について理解することです。これらのオブジェクトについて詳細に学習することにより、これらを単体で使用する必要があるときと、独自のアプリケーションでこれらを組み合せる必要があるときについて理解が深まります。
クライアントはエンティティ・オブジェクトを直接操作できないため、通常、エンティティ・オブジェクトをプログラム的に操作するために記述するコードは、カスタム・アプリケーション・モジュール・クラス内または別のエンティティ・オブジェクトのカスタム・クラス内のカスタム・コードになります。この項では、この章の前半で作成方法を学習したSRDemoエンティティを操作し、devguide.modelパッケージ内のSRServiceという名前のアプリケーション・モジュールのカスタム・メソッド内からエンティティ・オブジェクトおよびアソシエーションをプログラム的に操作する方法の例を示します。
エンティティ行にアクセスするには、エンティティ定義と呼ばれる関連オブジェクトを使用します。実行時には、エンティティ・オブジェクトごとにエンティティ定義オブジェクトがあります。この定義オブジェクトは、エンティティの構造を示し、定義オブジェクトが示すエンティティ・オブジェクトのインスタンスを管理します。devguide.modelパッケージでSRServiceアプリケーション・モジュールを作成し、このアプリケーション・モジュールのカスタムJavaクラスを有効化した後、特定のサービス・リクエストの現在の状態を戻すメソッドを作成する必要があるとします。このメソッドは、例6-5に示すSRServiceImpl.javaファイルのメソッドのようにretrieveServiceRequestStatus()のようなメソッドになります。
この例では、次のような基本手順に分けられます。
エンティティ定義を検索します。
devguide.model.entities.ServiceRequestエンティティのエンティティ定義オブジェクトを取得するには、その完全修飾名をEntityDefImplクラスの静的findDefObject()メソッドに渡します。oracle.jbo.serverパッケージ内のEntityDefImplクラスにより、各エンティティ・オブジェクトのエンティティ定義が実装されます。
キーを作成します。
検索する主キー属性が含まれるKeyオブジェクトを作成します。この場合、メソッドに引数として渡される単一のrequestId値が含まれるキーを作成します。
キーを使用してエンティティ・オブジェクトを検索します。
エンティティ定義のfindByPrimaryKey()メソッドを使用して、キーによってエンティティ・オブジェクトを検索します。この場合、getDBTransaction()メソッドを使用してアプリケーション・モジュールから取得できる現在のトランザクション・オブジェクトを渡します。エンティティ・オブジェクト行を示す具体的なクラス名は、oracle.jbo.server.EntityImplクラスです。
データの一部をコール元に戻します。
EntityImplのgetAttribute()メソッドを使用して、Status属性の値をコール元に戻します。
例6-5 キーによるServiceRequestエンティティ・オブジェクトの検索
// Custom method in SRServiceImpl.java
public String findServiceRequestStatus(long requestId) {
String entityName = "devguide.model.entities.ServiceRequest";
// 1. Find the entity definition for the ServiceRequest entity
EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
// 2. Create the key
Key svcReqKey = new Key(new Object[]{requestId});
// 3. Find the entity object instance using the key
EntityImpl svcReq = svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
if (svcReq != null) {
// 4. Return the Status attribute of the ServiceRequest
return (String)svcReq.getAttribute("Status");
}
else {
return null;
}
}
|
注意: oracle.jbo.Keyオブジェクト・コンストラクタは、より一般的な単一の属性値のキー以外にも、複数の属性キーの作成をサポートするためにオブジェクト配列を使用します。 |
6.3項「アソシエーションの作成および構成」では、アソシエーションを使用すると、1つのエンティティ・オブジェクトから別のエンティティ・オブジェクトに簡単にアクセスできることを説明しました。ここでは、その実際の仕組みについて簡単なメソッドを使用して説明します。この場合、サービス・リクエストを検索するfindServiceRequestTechnician()メソッドを追加してから、リクエストに割り当てられた技術者を示すUserエンティティ・オブジェクトにアクセスできます。
ただし、これはIDによってServiceRequestエンティティ・オブジェクトを検索するアプリケーション・モジュールの第2メソッドであるため、まずは次のretrieveServiceRequestById()ヘルパー・メソッドにこの機能をリファクタする必要がある場合が考えられます。このヘルパー・メソッドは、IDによってサービス・リクエストを検索する必要があるアプリケーション・モジュールのどの部分においても再使用できます。
// Helper method in SRServiceImpl.java
private EntityImpl retrieveServiceRequestById(long requestId) {
String entityName = "devguide.model.entities.ServiceRequest";
EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
Key svcReqKey = new Key(new Object[]{requestId});
return svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
}
例6-6に、findServiceRequestTechnician()のコードを示します。この例は、次の3つの基本手順に従います。
IDによってServiceRequestを検索します。
retrieveServiceRequestById()ヘルパー・メソッドを使用して、IDによってServiceRequestエンティティ・オブジェクトを取得します。
アクセッサ属性を使用して関連付けられたエンティティにアクセスします。
前述の6.3.1.1項「エンティティ・アソシエーションのアクセッサ名の変更」では、ServiceRequestsAssignedToUserアソシエーションのアクセッサ名を変更し、ServiceRequestエンティティがTechnicianAssignedというアクセッサ名を使用して、関連する2つのUserエンティティ・オブジェクトの1つにアクセスできるようにしました。エンティティ・オブジェクト値を取得するために使用したものと同じgetAttribute()メソッドを使用して、アソシエーションのアクセッサ名を渡し、関連の反対側にあるエンティティ・オブジェクトを取得できます。
データの一部をコール元に戻します。
戻されたUserエンティティに対してgetAttribute()メソッドを使用して、名前と姓を連結することにより、割り当てられた技術者の名前が戻されます。
関連するUserエンティティにアクセスするためにSQLを記述する必要はありません。ServiceRequestエンティティ・オブジェクトとUserエンティティ・オブジェクト間のADFアソシエーションで取得された関連情報のみで、データ・ナビゲーションの一般的なタスクを自動化できます。
例6-6 アクセッサ属性を使用した関連付けられたエンティティへのアクセス
// Custom method in SRServiceImpl.java
public String findServiceRequestTechnician(long requestId) {
// 1. Find the service request entity
EntityImpl svcReq = retrieveServiceRequestById(requestId);
if (svcReq != null) {
// 2. Access the User entity object using the association accessor attribute
EntityImpl tech = (EntityImpl)svcReq.getAttribute("TechnicianAssigned");
if (tech != null) {
// 3. Return some of the User entity object's attributes to the caller
return tech.getAttribute("FirstName")+" "+tech.getAttribute("LastName");
}
else {
return "Unassigned";
}
}
else {
return null;
}
}
エンティティ行を取得した後、エンティティ行は簡単に更新または削除できます。例6-7に示すupdateRequestStatus()のようなメソッドを追加することにより、このジョブを処理できます。この例は、次の3つの簡単な手順に従います。
IDによってServiceRequestを検索します。
retrieveServiceRequestById()ヘルパー・メソッドを使用して、IDによってServiceRequestエンティティ・オブジェクトを取得します。
1つ以上の属性を新しい値に設定します。
EntityImplクラスのsetAttribute()メソッドを使用して、Status属性の値を渡された新しい値に更新します。
トランザクションをコミットします。
アプリケーション・モジュールのgetDBTransaction()メソッドを使用して、現在のトランザクション・オブジェクトにアクセスし、commit()メソッドをコールしてトランザクションをコミットします。
例6-7 既存のエンティティ行の更新
// Custom method in SRServiceImpl.java
public void updateRequestStatus(long requestId, String newStatus) {
// 1. Find the service request entity
EntityImpl svcReq = retrieveServiceRequestById(requestId);
if (svcReq != null) {
// 2. Set its Status attribute to a new value
svcReq.setAttribute("Status",newStatus);
try {
// 3. Commit the transaction
getDBTransaction().commit();
}
catch (JboException ex) {
getDBTransaction().rollback();
throw ex;
}
}
}
エンティティ行の削除の例もこれと同じです。ただし、既存のエンティティを検索した後、トランザクションをコミットする前に、かわりに次の行を使用してエンティティを削除する点が異なります。
// Remove the entity instead! svcReq.remove();
エンティティ定義を使用して既存のエンティティ行を検索する以外にも、エンティティ定義を使用して新しいエンティティ行を作成することもできます。ここでは、対象をサービス・リクエストから製品に変更して、例6-8のようなcreateProduct()メソッドを記述することもできます。これにより、新しい製品の名前や説明が取得され、この製品に割り当てられた新しい製品IDが戻されます。この場合、Productエンティティ・オブジェクトのProdId属性が更新され、6.6.3.8項「トリガーによってデータベース順序から割り当てられた主キー値」で説明したDBSequence型になっていると仮定します。これにより、この値は自動的にリフレッシュされ、PRODUCTS表のASSIGN_PRODUCT_IDトリガーによってSRDemoアプリケーション・スキーマのPRODUCTS_SEQ順序から割り当てられる値が反映されます。
この例では、次の手順に従います。
エンティティ定義を検索します。
EntityDefImpl.findDefObject()を使用して、Productエンティティのエンティティ定義を検索します。
新しいインスタンスを作成します。
エンティティ定義に対してcreateInstance2()メソッドを使用して、エンティティ・オブジェクトの新しいインスタンスを作成します。
|
注意: 実際には、このメソッド名の末尾には2があります。付録D「ADF Business Componentsのよく使用されるメソッド」のD.2.5項「EntityDefImplクラス」で説明するように、正規のcreateInstance()メソッドにはprotectedアクセスがあり、開発者によってカスタマイズされるよう設計されています。AttributeList型の2番目の引数を使用して、作成時に指定する必要がある属性値を指定します。これは、リストで検出されるすべての属性の値を初期化するために使用するものではありません。たとえば、このAPIを使用して構成される側の子エンティティ行の新しいインスタンスを作成する場合、構成する側の親エンティティの外部キー属性を2番目の引数としてAttributeListオブジェクトに指定します。これを指定しない場合、InvalidOwnerExceptionが発生します。 |
属性値を設定します。
エンティティ・オブジェクトに対してsetAttribute()メソッドを使用して、新しいエンティティ行のNameおよびDescription属性に値を割り当てます。
トランザクションをコミットします。
現在のトランザクション・オブジェクトに対してcommit()をコールし、トランザクションをコミットします。
トリガーによって割り当てられた製品IDをコール元に戻します。
getAttribute()を使用してProdId属性をDBSequenceとして取得し、getSequenceNumber().longValue()をコールして順序番号をlong値としてコール元に戻します。
例6-8 エンティティ行の新規作成
// Custom method in SRServiceImpl.java
public long createProduct(String name, String description) {
String entityName = "devguide.model.entities.Product";
// 1. Find the entity definition for the Product entity
EntityDefImpl productDef = EntityDefImpl.findDefObject(entityName);
// 2. Create a new instance of a Product entity
EntityImpl newProduct = productDef.createInstance2(getDBTransaction(),null);
// 3. Set attribute values
newProduct.setAttribute("Name",name);
newProduct.setAttribute("Description",description);
try {
// 4. Commit the transaction
getDBTransaction().commit();
}
catch (JboException ex) {
getDBTransaction().rollback();
throw ex;
}
// 5. Access the database trigger assigned ProdId value and return it
DBSequence newIdAssigned = (DBSequence)newProduct.getAttribute("ProdId");
return newIdAssigned.getSequenceNumber().longValue();
}
この時点で、カスタム・アプリケーション・モジュールのメソッドのテストが可能です。テスト用のコードをオブジェクトに組み込む一般的な技術の1つとして、コードをstatic main()メソッドに組み込む方法があります。例6-9に、先に記述したサンプル・メソッドをテストするために、SRServiceImpl.javaカスタム・アプリケーション・モジュール・クラスに追加できるサンプルのmain()メソッドを示します。5.7項「コマンドラインJavaテスト・クライアントの作成方法」で使用したものと同じConfigurationオブジェクトを使用して、テスト用のアプリケーション・モジュールをインスタンス化および操作します。
|
注意: このConfigurationオブジェクトがoracle.jbo.clientパッケージに格納されているという事実から、アプリケーション・モジュールにアクセスするためのアプリケーション・クライアントとしての使用が提案されます。main()メソッドは一種のプログラム的なコマンドライン・クライアントであるため、このような使用方法に問題はありません。また、createRootApplicationModule()の戻り値をアプリケーション・モジュールの実装クラスに直接キャストするのは最適ではありませんが、このオブジェクトはアプリケーション・モジュールのクライアントであるとしても、main()メソッドのコードがアプリケーション・モジュールの実装クラス自体の中に格納されているため、この状況でこの操作を行うことは適切です。 |
コードを見ると、これまでに作成した4つのメソッドを使用して次が実行されていることがわかります。
サービス・リクエスト101のステータスの取得
サービス・リクエスト101に割り当てられた技術者の名前の取得
サービス・リクエスト101のステータスの不正な値「Reopened」への設定
nullの製品名を持つ新しい製品の作成
新しい製品の作成、および新しく割り当てられた製品IDの表示
例6-9 SRServiceアプリケーション・モジュールを内部からテストするmainメソッドのサンプル
// Main method in SRServiceImpl.java
public static void main(String[] args) {
String amDef = "devguide.model.SRService";
String config = "SRServiceLocal";
ApplicationModule am =
Configuration.createRootApplicationModule(amDef,config);
/*
* NOTE: This cast to use the SRServiceImpl class is OK since this
* ---- code is inside a business tier *Impl.java file and not in a
* client class that is accessing the business tier from "outside".
*/
SRServiceImpl service = (SRServiceImpl)am;
// 1. Retrieve the status of service request 101
String status = service.findServiceRequestStatus(101);
System.out.println("Status of SR# 101 = " + status);
// 2. Retrieve the name of the technician assigned to service request 101
String techName = service.findServiceRequestTechnician(101);
System.out.println("Technician for SR# 101 = " + techName);
try {
// 3. Set the status of service request 101 to illegal value "Reopened"
service.updateRequestStatus(101,"Reopened");
}
catch (JboException ex) {
System.out.println("ERROR: "+ex.getMessage());
}
long id = 0;
try {
// 4. Create a new product supplying a null product name
id = service.createProduct(null,"Makes Blended Fruit Drinks");
}
catch (JboException ex) {
System.out.println("ERROR: "+ex.getMessage());
}
// 5. Create a new product and display its newly assigned product id
id = service.createProduct("Smoothie Maker","Makes Blended Fruit Drinks");
System.out.println("New product created successfully with id = "+id);
Configuration.releaseRootApplicationModule(am,true);
}
SRServiceImpl.javaクラスを実行すると、例6-9のmain()メソッドがコールされ、次の出力が表示されます。
Status of SR# 101 = Closed Technician for SR# 101 = Bruce Ernst ERROR: The status must be Open, Pending, or Closed ERROR: JBO-27014: Attribute Name in Product is required New product created successfully with id = 209
図6-17のように、ServiceRequestエンティティ・オブジェクトのStatus属性でList Validatorに障害が発生しているため、サービス・リクエストのステータスを「Reopened」に設定する試みは失敗しています。このバリデータは、未処理、保留中または処理済である静的リストの値のみを許可するよう構成されています。また、製品名をnullとしてcreateProduct()をコールしようとした初回の試みでは、Productエンティティ・オブジェクトのName属性に対する組込み必須検証が原因で例外が発生しています。
|
注意: SRServiceアプリケーション・モジュールで作成したカスタム・サービス・メソッドを、同じクラス内のmain()メソッドがコールするのではなく、クライアント・アプリケーションが起動するにはどうすればよいのでしょうか。このための簡単な手順は、8.4項「クライアントへのカスタム・サービス・メソッドの公開」を参照してください。これは、アプリケーション・モジュール・エディタの「クライアント・インタフェース」ページを使用する直接的な構成オプションです。 |
この章でこれまで説明してきたように、エンティティ・オブジェクトのデータベース対話機能および多くの宣言的実行時機能はすべて、カスタムJavaコードを使用せずに実現できます。これらの宣言的機能を超えてエンティティにカスタム・ビジネス・ロジックを実装する必要がある場合、カスタム・コードを必要とするエンティティに対してJava生成を有効化する必要があります。通常、カスタム・エンティティ・オブジェクトおよびエンティティ定義クラスで記述、使用およびオーバーライドする一般的なコードの詳細は、付録D「ADF Business Componentsのよく使用されるメソッド」を参照してください。後半の各章では、SRDemoアプリケーションがエンティティ・クラスで同様にカスタム・コードを使用する方法について個々の例を使用して説明します。
エンティティ・オブジェクトのカスタムJavaクラスの生成を有効にするには、エンティティ・オブジェクト・エディタの「Java」ページを使用します。図6-20のように、エンティティ・オブジェクトには3つのオプションのJavaクラスを関連付けることができます。「エンティティ・コレクション・クラス」を実際にカスタマイズすることはまれですが、「エンティティ・オブジェクト・クラス」は最も頻繁にカスタマイズします。また、「エンティティ定義クラス」をカスタマイズする機会は少なくなっています。
エンティティ・コレクション・クラス: カスタマイズすることはまれです。
エンティティ・オブジェクト・クラス: 最も頻繁にカスタマイズします。このクラスは、基礎となるデータベース表の各行を示します。
エンティティ定義クラス: カスタマイズする頻度は低くなります。このクラスは、エンティティ行を管理し、その構造を定義する関連クラスを示します。
カスタム・エンティティ・オブジェクト・クラスの生成を有効にする場合、「アクセッサ」チェック・ボックスも選択すると、エンティティ・オブジェクトの属性ごとにgetterメソッドおよびsetterメソッドが生成されます。ServiceRequestエンティティ・オブジェクトの場合、対応するカスタムServiceRequestImpl.javaクラスには次のようなメソッドが生成されます。
public Number getSvrId() {...}
public void setSvrId(Number value) {...}
public String getStatus() {...}
public void setStatus(String value) {...}
public Date getRequestDate() {...}
public void setRequestDate(Date value) {...}
public String getProblemDescription() {...}
public void setProblemDescription(String value) {...}
public Number getProdId() {...}
public void setProdId(Number value) {...}
public Number getCreatedBy() {...}
public void setCreatedBy(Number value) {...}
public Number getAssignedTo() {...}
public void setAssignedTo(Number value) {...}
public Date getAssignedDate() {...}
public void setAssignedDate(Date value) {...}
public ProductImpl getProduct() {...}
public void setProduct(ProductImpl value) {...}
public RowIterator getServiceHistories() {...}
public UserImpl getTechnicianAssigned() {...}
public void setTechnicianAssigned(UserImpl value) {...}
public UserImpl getCreatedByUser() {...}
public void setCreatedByUser(UserImpl value) {...}
これらのメソッドを使用して行データを操作すると、コンパイル時にデータ型の使用方法が正しいかどうかがチェックされます。つまり、ProdId属性の値を取得するために次のようなコードを記述するかわりに、
Number prodId = (Number)svcReq.getAttribute("ProdId");
次のようなコードを記述できます。
Number prodId = svcReq.getProdId();
後者の方法では、ProdIdではなく誤ってProductCodeと入力したときにJavaコンパイラによって入力ミスが捕捉されます。
// spelling name wrong gives compile error Number prodId = svcReq.getProductCode();
エンティティ・オブジェクトのアクセッサ・メソッドが生成されていない場合、次のような不適切なコード行をコンパイラで捕捉できません。
// Both attribute name and type cast are wrong, but compiler cannot catch it
String prodId = (String)svcReq.getAttribute("ProductCode");
これには、スペルが正しくない属性名とキャストの型が誤っている戻り値getAttribute()が両方とも含まれています。EntityImplベース・クラスが実装するRowインタフェース上で汎用APIを使用する場合、このようなエラーはコンパイル時に捕捉されないため、実行時に例外が発生します。
生成するカスタムJavaクラスを1つ以上選択すると、指定したJavaファイルが作成されます。devguide.model.entities.ServiceRequestという名前のエンティティ・オブジェクトの場合、カスタムJavaファイルのデフォルト名は、エンティティ・オブジェクト・クラスについてはServiceRequestImpl.java、エンティティ定義クラスについてはServiceRequestDefImpl.javaになります。これらのファイルは両方とも、コンポーネントのXMLコンポーネント定義ファイルと同じ./devguide/model/entitiesディレクトリに作成されます。
エンティティ・オブジェクトのJava生成オプションは、ビュー・オブジェクト・エディタの「Java」ページに後でアクセスしてもそのまま反映されています。XML定義ファイルの場合と同様、このエディタでどのような変更を行っても、カスタムJavaクラスで生成されたコードは最新の状態に保たれます。後でなんらかの理由により、カスタムJavaファイルが必要なくなった場合、「Java」ページで関連するオプションの選択を解除すると、カスタムJavaファイルを削除できます。
すべてのADFコンポーネントと同様、アプリケーション・ナビゲータでエンティティ・オブジェクトを選択すると、構造ウィンドウには、関連する実装ファイルがすべて表示されます。唯一必要なファイルは、XMLコンポーネント定義ファイルです。前に説明したように、コンポーネントに翻訳可能なUIコントロール・ヒントが定義されている場合、コンポーネント・メッセージ・バンドル・ファイルも存在します。図6-21のように、カスタムJavaクラスの生成を有効にした場合、これらのクラスは、エンティティ・オブジェクトのソース・フォルダの下にも表示されます。カスタムJavaファイルのソース・コードを表示または操作する必要がある場合、ソース・コード・エディタでこのファイルを開く方法には2通りあります。
図6-21のように、ポップアップ・メニューで関連する「移動先」オプションを選択します。
構造ウィンドウのソース・フォルダでファイルをダブルクリックします。
カスタムJavaクラスの詳細は、次の各項を参照してください。
XML専用エンティティ・オブジェクトを使用する場合、実行時には、その機能はデフォルトのADF Business Components実装クラスによって提供されます。生成される各カスタムJavaクラスによって適切なADF Business Componentsのベース・クラスが自動的に拡張されるため、コードでは、デフォルトの動作を継承し、このクラスを簡単に追加またはカスタマイズできます。エンティティ・オブジェクト・クラスはEntityImplを拡張し、エンティティ定義クラスはEntityDefImplを拡張します(これらは両方ともoracle.jbo.serverパッケージ内にあります)。
開発者によっては、以前に大変な経験をしたために、生成されたJavaソース・ファイルに独自のコードを追加することを躊躇する場合があります。JDeveloperによって作成および管理される各カスタムJavaソース・コード・ファイルには、独自のカスタム・コードをこのファイルに追加しても安全であることを示す、次のようなコメントがファイルの上部に記載されています。
// --------------------------------------------------------------------- // --- File generated by Oracle ADF Business Components Design Time. // --- Custom code may be added to this class. // --- Warning: Do not modify method signatures of generated methods. // ---------------------------------------------------------------------
コンポーネント・エディタで「OK」または「適用」ボタンをクリックしても、ファイルが知らぬ間に再生成されることはありません。かわりに、管理が必要なメソッドはスマートに更新され、独自のカスタム・コードはそのまま残されます。
これまでの説明で、ビュー・オブジェクトの実行時動作をカスタマイズする必要がある場合や、バインド変数またはビュー行属性へ強く型付けされたアクセスのみ行う場合のビュー・オブジェクトのカスタムJavaクラスの生成方法を示しました。
ADF Business ComponentsのカスタムJava生成のデフォルト設定を構成するには、「ツール」→「設定」メニューを選択し、「ビジネス・コンポーネント」ページを開き、後で作成するビジネス・コンポーネントで使用する設定を設定します。ADF Business Componentsを初めて使用する開発者には、デフォルトではカスタムJavaクラスを生成しないよう設定することをお薦めします。この項で説明したようなカスタムJavaコードが必要な状況になった場合、そのコンポーネントに必要なカスタムJavaコードのみを有効にできます。経験を積むにつれ、どのようなデフォルト設定の組合せが最適であるかがわかるようになります。
これまで説明したように、エンティティ・オブジェクトは、XML専用モードで機能するか、XMLコンポーネント定義とカスタムJavaクラスの組合せを使用して機能するように設計されています。この機能のため、属性値はエンティティのクラスのプライベート・メンバー・フィールドには格納されません。これは、XML専用モードの場合、このようなクラスが存在しないためです。かわりに、属性には、名前のみならず、エンティティのXMLコンポーネント定義ファイル内のAttributeおよびアソシエーション関連のAccessorAttributeタグのゼロベースの順序に基づいて、このファイルの数値索引も割り当てられます。実行時には、エンティティ行の属性値は、エンティティの属性リスト内における属性の数値的位置による索引が付けられた状態で、EntityImplベース・クラスによって管理される疎配列構造に格納されます。
多くの場合、このプライベート実装に関する詳細は、エンティティ・オブジェクトを使用する開発者は理解する必要がないため、重要ではありません。ただし、エンティティ・オブジェクトのカスタムJavaクラスを有効にする場合、エンティティ・オブジェクト・クラスで自動的に管理される生成コードの一部にこの実装の詳細が関わってきます。この場合、コードの使用目的を理解している方が賢明です。たとえば、ServiceRequestエンティティ・オブジェクトのカスタムJavaクラスの場合、例6-10のように、属性またはアクセッサごとに対応する生成済の整数定数があります。JDeveloperでは、これらの定数の値がXMLコンポーネント定義内の属性の順序を正しく反映しているかどうかが確認されます。
例6-10 カスタム・エンティティJavaクラスで自動的に管理される属性定数
public class ServiceRequestImpl extends EntityImpl {
public static final int SVRID = 0;
public static final int STATUS = 1;
public static final int REQUESTDATE = 2;
public static final int PROBLEMDESCRIPTION = 3;
public static final int PRODID = 4;
public static final int CREATEDBY = 5;
public static final int ASSIGNEDTO = 6;
public static final int ASSIGNEDDATE = 7;
public static final int TECHNICIANASSIGNED = 8;
public static final int CREATEDBYUSER = 9;
public static final int PRODUCT = 10;
public static final int SERVICEHISTORIES = 11;
// etc.
また、自動的に管理される、エンティティ・オブジェクト・クラスで強く型付けされたgetterメソッドおよびsetterメソッドでは、これらの属性定数は次のように使用されます。
// In devguide.model.entities.ServiceRequestImpl class
public Number getAssignedTo() {
return (Number)getAttributeInternal(ASSIGNEDTO); // <-- Attribute constant
}
public void setAssignedTo(Number value) {
setAttributeInternal(ASSIGNEDTO, value); // <-- Attribute constant
}
エンティティ属性定数に関連する自動管理コードの最後の側面は、getAttrInvokeAccessor()およびsetAttrInvokeAccessor()メソッドです。これらのメソッドにより、数値索引を使用した属性アクセスのパフォーマンスが最適化されます。これは、汎用処理の実行時にEntityImplベース・クラスの汎用コードが属性値にアクセスするときの通常のパフォーマンスです。getAttrInvokeAccessor()メソッドの例は、次のServiceRequestImpl.javaクラスのようになります。もう1つのsetAttrInvokeAccessor()メソッドの場合も同様です。
// In devguide.model.entities.ServiceRequestImpl class
/** getAttrInvokeAccessor: generated method. Do not modify. */
protected Object getAttrInvokeAccessor(int index,AttributeDefImpl attrDef)
throws Exception {
switch (index) {
case SVRID: return getSvrId();
case STATUS: return getStatus();
case REQUESTDATE: return getRequestDate();
case PROBLEMDESCRIPTION: return getProblemDescription();
case PRODID: return getProdId();
case CREATEDBY: return getCreatedBy();
case ASSIGNEDTO: return getAssignedTo();
case ASSIGNEDDATE: return getAssignedDate();
case SERVICEHISTORIES: return getServiceHistories();
case TECHNICIANASSIGNED: return getTechnicianAssigned();
case CREATEDBYUSER: return getCreatedByUser();
case PRODUCT: return getProduct();
default:
return super.getAttrInvokeAccessor(index, attrDef);
}
}
属性と索引に関連して生成されたこのコードに関する経験則は、次のとおりです。
必要に応じて、強く型付けされた属性のgetterメソッドおよびsetterメソッドの内部にカスタム・コードを追加してください。
エンティティ・オブジェクト・エディタを使用して、エンティティ・オブジェクト属性の順序または型を変更してください。
getterメソッドおよびsetterメソッドのJavaシグネチャや関連するXMLコンポーネント定義は、自動的に変更されます。
getAttrInvokeAccessor()メソッドおよびsetAttrInvokeAccessor()メソッドは変更しないでください。
属性索引番号の値は手動で変更しないでください。
|
注意: ソース・コントロールのマージ競合やその他の理由により、生成された属性定数を手動で編集する必要がある場合、対応するエンティティ・オブジェクトのXMLコンポーネント定義の<Attribute>および<AccessorAttribute>タグの順序がゼロベースの順序に反映されていることを確認する必要があります。 |
生成したカスタム・エンティティ・クラスを使用する場合とEntityImpl汎用クラスを操作する場合の違いをわかりやすくするために、例6-11に、先に実装したSRServiceImpl.javaメソッドの2つ目のSRService2Impl.javaアプリケーション・モジュール・クラスにおけるバージョンを示します。注意する必要がある違いは、次のとおりです。
属性アクセスは、強く型付けされた属性アクセッサを使用して実行されます。
アソシエーションのアクセッサ属性により、アソシエーションの反対側にある強く型付けされたエンティティ・クラスが戻されます。
カスタム・エンティティ・クラスのgetDefinitionObject()メソッドを使用して、完全修飾されたエンティティ定義名を文字列として使用しないようにします。
カスタム・エンティティ・クラスのcreatePrimaryKey()メソッドにより、エンティティのKeyオブジェクトの作成を簡略化します。
例6-11 強く型付けされたカスタム・エンティティ・オブジェクト・クラスを使用したプログラム的なエンティティの例
package devguide.model;
import devguide.model.entities.ProductImpl;
import devguide.model.entities.ServiceRequestImpl;
import devguide.model.entities.UserImpl;
import oracle.jbo.ApplicationModule;
import oracle.jbo.JboException;
import oracle.jbo.Key;
import oracle.jbo.client.Configuration;
import oracle.jbo.domain.DBSequence;
import oracle.jbo.domain.Number;
import oracle.jbo.server.ApplicationModuleImpl;
import oracle.jbo.server.EntityDefImpl;
import oracle.jbo.server.EntityImpl;
// ---------------------------------------------------------------------
// --- File generated by Oracle ADF Business Components Design Time.
// --- Custom code may be added to this class.
// --- Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
/**
* This custom application module class illustrates the same
* example methods as SRServiceImpl.java, except that here
* we're using the strongly typed custom Entity Java classes
* ServiceRequestImpl, UserImpl, and ProductImpl instead of working
* with all the entity objects using the base EntityImpl class.
*/
public class SRService2Impl extends ApplicationModuleImpl {
/**This is the default constructor (do not remove)
*/
public SRService2Impl() {
}
/*
* Helper method to return a ServiceRequest by Id
*/
private ServiceRequestImpl retrieveServiceRequestById(long requestId) {
EntityDefImpl svcReqDef = ServiceRequestImpl.getDefinitionObject();
Key svcReqKey =
ServiceRequestImpl.createPrimaryKey(new DBSequence(requestId));
return (ServiceRequestImpl)svcReqDef.findByPrimaryKey(getDBTransaction(),
svcReqKey);
}
/*
* Find a ServiceRequest by Id
*/
public String findServiceRequestStatus(long requestId) {
ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
if (svcReq != null) {
return svcReq.getStatus();
}
return null;
}
/*
* Create a new Product and Return its new id
*/
public long createProduct(String name, String description) {
EntityDefImpl productDef = ProductImpl.getDefinitionObject();
ProductImpl newProduct = (ProductImpl)productDef.createInstance2(
getDBTransaction(),null);
newProduct.setName(name);
newProduct.setDescription(description);
try {
getDBTransaction().commit();
}
catch (JboException ex) {
getDBTransaction().rollback();
throw ex;
}
DBSequence newIdAssigned = newProduct.getProdId();
return newIdAssigned.getSequenceNumber().longValue();
}
/*
* Update the status of an existing service request
*/
public void updateRequestStatus(long requestId, String newStatus) {
ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
if (svcReq != null) {
svcReq.setStatus(newStatus);
try {
getDBTransaction().commit();
}
catch (JboException ex) {
getDBTransaction().rollback();
throw ex;
}
}
}
/*
* Access an associated Used entity from the ServiceRequest entity
*/
public String findServiceRequestTechnician(long requestId) {
ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
if (svcReq != null) {
UserImpl tech = (UserImpl)svcReq.getTechnicianAssigned();
if (tech != null) {
return tech.getFirstName()+" "+tech.getLastName();
}
else {
return "Unassigned";
}
}
else {
return null;
}
}
// Original main() method generated by the application module editor
//
// /**Sample main for debugging Business Components code using the tester.
// */
// public static void main(String[] args) {
// launchTester("devguide.model", /* package name */
// "SRServiceLocal" /* Configuration Name */);
// }
/*
* Testing method
*/
public static void main(String[] args) {
String amDef = "devguide.model.SRService";
String config = "SRServiceLocal";
ApplicationModule am =
Configuration.createRootApplicationModule(amDef,config);
/*
* NOTE: This cast to use the SRServiceImpl class is OK since this
* ---- code is inside a business tier *Impl.java file and not in a
* client class that is accessing the business tier from "outside".
*/
SRServiceImpl service = (SRServiceImpl)am;
String status = service.findServiceRequestStatus(101);
System.out.println("Status of SR# 101 = " + status);
String techName = service.findServiceRequestTechnician(101);
System.out.println("Technician for SR# 101 = " + techName);
try {
service.updateRequestStatus(101,"Reopened");
}
catch (JboException ex) {
System.out.println("ERROR: "+ex.getMessage());
}
long id = 0;
try {
id = service.createProduct(null,"Makes Blended Fruit Drinks");
}
catch (JboException ex) {
System.out.println("ERROR: "+ex.getMessage());
}
id = service.createProduct("Smoothie Maker","Makes Blended Fruit Drinks");
System.out.println("New product created successfully with id = "+id);
Configuration.releaseRootApplicationModule(am,true);
}
}
基礎となる表内の列にマップされる属性のみならず、エンティティ・オブジェクトには、値ホルダーである一時属性やJavaで計算された値を表示する一時属性を組み込むことができます。この項では、FullName一時属性をUsersエンティティ・オブジェクトに追加する簡単な例を示します。この属性は、FirstName属性とLastName属性の値を連結して、その値を計算します。
エンティティ・オブジェクト・エディタで「属性」ページを開き、「新規」ボタンをクリックします。図6-22に例を示します。
FullNameなどの属性名を入力します。
StringなどのJava属性タイプを設定します。
「永続的」チェック・ボックスの選択は解除します。
値を計算する場合、「更新可能」を「なし」に設定します。
次に、「OK」をクリックし、属性を作成します。
一時属性を追加し、エンティティ・オブジェクト・エディタを終了すると、エンティティ・オブジェクトのXMLコンポーネント定義が更新され、新しい属性が反映されます。一方、XMLで永続的エンティティ・アソシエーションは次のようになります。
<Attribute Name="FirstName" IsNotNull="true" Precision="30" ColumnName="FIRST_NAME" Type="java.lang.String" ColumnType="VARCHAR2" SQLType="VARCHAR" TableName="USERS" > </Attribute>
一時属性のAttributeタグは、次のようになります。この場合、TableNameはなく、ColumnNameは$none$になっています。
<Attribute Name="FullName" IsUpdateable="false" IsQueriable="false" IsPersistent="false" ColumnName="$none$" Type="java.lang.String" ColumnType="$none$" SQLType="VARCHAR" > </Attribute>
一時属性は、データ値のプレースホルダです。一時属性の「更新可能」プロパティを「新規の間」または「常に」に変更すると、エンド・ユーザーは属性値を入力できるようになります。一時属性に計算値を表示する場合は通常、「更新可能」プロパティを「なし」に設定し、値を計算するカスタムJavaコードを記述します。
エンティティ・オブジェクトに一時属性を追加した後、この属性を計算属性にするには、次のようにする必要があります。
エンティティ・オブジェクト・エディタの「Java」ページでカスタム・エンティティ・オブジェクト・クラスを有効にし、アクセッサ・メソッドの生成を選択します。
一時属性のアクセッサ・メソッドの内部で、計算済値を戻すJavaコードを記述します。
たとえば、UserImpl.javaビュー行クラスを生成した後、計算済値を戻すJavaコードは、次のようにgetFullName()メソッド内にあります。
// Getter method for FullName calculated attribute in UserImpl.java
public String getFullName() {
// Commented out original line since we'll always calculate the value
// return (String)getAttributeInternal(FULLNAME);
return getFirstName()+" "+getLastName();
}
エンド・ユーザーによってLastNameまたはFirstName属性が変更されるたびに、計算されたFullName属性が再評価されるようにするには、いずれかの値が設定されるたびに内容が保証されないものとしてFullNameをマークする1行をそれぞれのsetterメソッドに追加します。
// Setting method for FirstName attribute in UserImpl.java
public void setFirstName(String value) {
setAttributeInternal(FIRSTNAME, value);
// Notify any clients that the FullName attribute has changed
populateAttribute(FULLNAME,null,true, /* send notification */
false, /* markAsChanged */
false);/* saveCopy */
}
および
public void setLastName(String value) {
setAttributeInternal(LASTNAME, value);
// Notify any clients that the FullName attribute has changed
populateAttribute(FULLNAME,null,true, /* send notification */
false, /* markAsChanged */
false);/* saveCopy */
}