この章では、最も一般的なビジネス・ルールの実装に関する、主要なエンティティ・オブジェクトのイベントと機能について説明します。
この章の内容は次のとおりです。
組込みの宣言的な検証機能を補うエンティティ・オブジェクトには、Javaコードを使用してカプセル化されたビジネス・ロジックの実装を処理する、Method Validatorやいくつかのイベントが含まれています。この章の終わりには、図9-1に示すすべての概念に加えて、他の内容も理解できるようになります。
属性レベルのMethod Validatorは、属性値が変更されると、検証コードをトリガーします。
エンティティ・レベルのMethod Validatorは、エンティティ行が検証されると、検証コードをトリガーします。
あるエンティティに対して、次の主要メソッドをカスタムJavaクラスでオーバーライドできます。
create()
: 行の作成時にデフォルト値を割り当てます。
initDefaults()
: 行の作成時、または新規行のリフレッシュ時にデフォルト値を割り当てます。
isAttributeUpdateable()
: 属性を条件付きで更新可能にします。
remove()
: 条件付きで削除を制限します。
prepareForDML()
: エンティティ行が保存される前に属性値を割り当てます。
beforeCommit()
: 指定された型のすべてのエンティティ行に関連する規則を実行します。
afterCommit()
: エンティティ・オブジェクトの状態の変更に関する通知を送信します。
それぞれのエンティティ行は、データが有効であるかどうかを追跡します。既存のエンティティ行をデータベースから取得すると、エンティティは有効であると判断されます。既存のエンティティ行の最初の持続属性が変更された場合、または、新しいエンティティ行が作成された場合、エンティティは無効であるとマークされます。
また、構成される側の子エンティティ行は、それを構成する親エンティティ・オブジェクトに不可欠であるとみなされるため、構成される側の子エンティティ行になんらかの変更を加えると、親エンティティは無効であるとマークされます。
エンティティが無効な状態にある場合、エンティティを有効にする前に、構成した宣言的な検証と、実装したプログラム的な検証規則が再度評価されます。あるエンティティ行が実行時に有効であるかどうかは、その行でisValid()
メソッドをコールして確認できます。
エンティティ・オブジェクト検証規則は、属性レベルおよびエンティティ・レベルのいずれかの基本カテゴリに分類できます。
特定のエンティティ・オブジェクト属性に対する属性レベルの検証は、エンド・ユーザーまたはプログラム・コードによる属性値の変更が試行されるとトリガーされます。属性を設定する順序は指定できないため、属性レベルの検証規則は、規則の成否がその属性の候補値のみに依存する場合にのみ使用する必要があります。
属性レベルの検証の例を次に示します。
サービス・リクエストのAssignedDate
の値には、過去の日付は設定できません。
サービス・リクエストのProdId
属性は、既存の製品である必要があります。
その他のすべての検証規則は、エンティティ・レベルの検証規則となります。これらの規則には、実装の際、規則の成否の決定に2つ以上のエンティティ属性、または、構成される子エンティティ行を考慮する必要があります。
属性レベルの検証の例を次に示します。
サービス・リクエストのAssignedDate
の値には、RequestDate
以降の日付を設定する必要があります。
サービス・リクエストのProdId
属性は、既存の製品である必要があります。
エンティティ・レベルの検証規則は、Row
に対しvalidate()
メソッドをコールするとトリガーされます。次の場合にトリガーされます。
エンティティ・オブジェクトにおいてメソッドを明示的にコールした場合
無効なエンティティ行を含むビュー行においてメソッドを明示的にコールした場合
ビュー・オブジェクトのイテレータにより、現在の行の変更を許可する前に、ビュー・オブジェクトの現在の行に対してメソッドがコールされた場合
トランザクションのコミット処理により、データベースへ変更を送信する前に、保留中の変更リスト内の無効なエンティティが有効にされた場合
トランザクションのコミット処理は、3つの基本的なフェーズに分かれています。
保留中の変更リスト内のすべての無効エンティティ行が有効であることを確認します。
適切なDML操作を実行し、保留中の変更をデータベースに送信します。
トランザクションをコミットします。
エンティティ・オブジェクト内に、SELECT
文内の送信済の変更の参照に応じて問合せまたはストアド・プロシージャを実行するようなビジネス検証ロジックがある場合、9.6.3項「指定された型のすべてのエンティティに関連する条件の検証」に示すbeforeCommit()
メソッドを使用してコーディングする必要があります。このメソッドは、すべてのDMLが適用された後に実行されるため、そのメソッドが実行した問合せやストアド・プロシージャでは、保存された、コミット前の保留中の変更をすべて参照できます。
現在のエンティティや他のエンティティの属性を更新するコードが検証規則に含まれている場合、エンティティの検証により、対象のエンティティやその他のエンティティが無効になる場合があります。保留中の変更リスト内のすべての無効なエンティティを検証するトランザクションのコミット処理フェーズの一環として、このトランザクションは、保留中のすべてのエンティティ行が有効になるまで、1回につき保留中の変更リストを最大10回までパスします。
10回パスした後に、リスト内に無効なエンティティが残っている場合、次の例外が発生します。
JBO-28200: Validation threshold limit reached. Invalid Entities still in cache
これは、意図しないエンティティの無効化を繰り返すような処理を回避するため、検証規則コードをデバッグする必要があることを示しています。
エンティティ・オブジェクトの検証規則で例外がスローされた場合、その例外はバンドルされ、クライアントに戻されます。トランザクションのpostChanges
処理中、イベントを処理するためオーバーライドしたメソッドによって検証の失敗がスローされる場合、現在のpostChanges
サイクルですでに実行されている可能性のあるデータベースのINSERT
、UPDATE
およびDELETE
文がロールバックされます。
エンティティ行がメモリー内に存在する場合、エンティティの状態には行の論理状態が反映されています。図9-2は、様々なエンティティ行の状態と、エンティティ行の状態がどのように遷移するかを示しています。エンティティ行が最初に作成されると、その状態はNew
になります。setNewRowState()
メソッドを使用して、エンティティがInitialized
であるとマークできます。これにより、ユーザーが属性を1つ以上設定し、状態がNew
に戻るまで、トランザクションの保留中の変更リストから削除されます。Unmodified
状態とは、データベースから取得されてから、まだ変更されていないエンティティの状態を表します。また、New
またはModified
状態のエンティティがトランザクションのコミット完了後に遷移する状態でもあります。トランザクション内で削除のために保留中の場合は、Unmodified
エンティティ行はDeleted
状態へと遷移します。最終的に、New
状態の行がトランザクションのコミット前に削除されたり、Unmodified
状態の行が正常に削除された場合、その行はDead
状態へと遷移します。
getEntityState()
メソッドを使用すると、ビジネス・ロジック・コードのエンティティ行の現在の状態にアクセスできます。
Method Validatorは、独自のJavaコードを使用した宣言的な検証規則を補完するものとして、オラクル社がお薦めする主要な手段です。Method Validatorでは、独自の検証メソッド内に記述したJavaコードが、エンティティ・オブジェクト検証サイクルにおいて適切なタイミングでトリガーされます。コード内でそれぞれ別のメソッド名をトリガーする場合、属性レベルのMethod Validatorとエンティティ・レベルのMethod Validatorはいくつでも追加できます。すべての検証メソッド名は、validate
から始まる必要があります。ただし、その規則に従っているかぎりは、その機能に最適な名前を自由に付けることができます。
エンティティ・オブジェクト・エディタを開きます。
エンティティ・オブジェクトにまだカスタムJavaクラスがない場合、最初に「Java」ページを開き、「エンティティ・オブジェクト・クラス」の生成を有効にし、「適用」をクリックして*.java
ファイルを生成します。
「検証」ページを開き、検証する属性を選択します。
「新規」をクリックし、検証規則を追加します。
図9-3に示すように、「規則」ドロップダウン・リストから「Method Validator」タイプを選択します。
「検証規則の追加」ダイアログには、属性レベル検証メソッドに対応するメソッド・シグネチャが表示されます。次のいずれかを選択できます。
エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。
「メソッドの作成と選択」を選択したままにすると、validate
から始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。
最後に、この検証規則が失敗した場合にエンド・ユーザーに表示される、デフォルト・ロケールのエラー・メッセージのテキストを追加します。
Method Validatorを新たに追加すると、JDeveloperでは新しい検証規則を反映するように、XMLコンポーネント定義が更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例9-1は、サービス・リクエストのAssignedDate
が現在月の日付であることを確認する、簡単な属性レベル検証規則を示しています。メソッドには、対応する属性と同じ型の引数が与えられ、その条件付きのロジックはこの入力パラメータの値に基づいています。属性バリデータの実行時には、属性値はまだ該当する新しい値に設定されていません。そのため、AssignedDate
属性の属性バリデータ内でgetAssignedDate()
メソッドをコールすると、クライアントが設定しようとしている候補値ではなく、現在の値が戻されます。
例9-1 簡単な属性レベルのMethod Validator
// In ServiceRequestImpl.java in SRDemo Sample public boolean validateAssignedDate(Date data) { if (data != null && data.compareTo(getFirstDayOfCurrentMonth()) <= 0) { return false; } return true; }
注意: compareTo() メソッドの戻り値は、2つの日付が等しい場合はゼロ(0 )、最初の日付が次の日付より前の場合はマイナス1(-1 )、最初の日付が次の日付より後の場合はプラス1(1 )となります。 |
エンティティ・レベルのMethod Validatorの作成方法:
エンティティ・オブジェクト・エディタを開きます。
エンティティ・オブジェクトにまだカスタムJavaクラスがない場合、最初に「Java」ページを開き、「エンティティ・オブジェクト・クラス」の生成を有効にし、「適用」をクリックして*.java
ファイルを生成します。
「検証」ページを開き、エンティティ・オブジェクト自体を表すルート・ノードをツリーから選択します。
「新規」をクリックし、検証規則を追加します。
図9-4に示すように、「規則」ドロップダウン・リストから「Method Validator」タイプを選択します。
「検証規則の追加」ダイアログには、エンティティ・レベル検証メソッドに対応するメソッド・シグネチャが表示されます。次のいずれかを選択できます。
エンティティ・オブジェクトのカスタムJavaクラスに、適切なシグネチャのあるメソッドがすでに存在する場合、リストに表示されます。これは「メソッドの作成と選択」チェック・ボックスの選択を解除した後に選択できます。
「メソッドの作成と選択」を選択したままにすると、validate
から始まるどのようなメソッド名でも「メソッド名」ボックスに入力できます。「OK」をクリックすると、適切なシグネチャとともに、メソッドがエンティティ・オブジェクトのカスタムJavaクラスに追加されます。
最後に、この検証規則が失敗した場合にエンド・ユーザーに表示される、デフォルト・ロケールのエラー・メッセージのテキストを追加します。
Method Validatorを新たに追加すると、JDeveloperでは新しい検証規則を反映するように、XMLコンポーネント定義が更新されます。メソッドの作成を指定した場合、そのメソッドはエンティティ・オブジェクトのカスタムJavaクラスに追加されます。例9-2は、サービス・リクエストのAssignedDate
がRequestDate
より後の日付であることを確認する、簡単なエンティティ・レベル検証規則を示しています。
宣言的なデフォルト設定では不十分なとき、次の場合にエンティティ・オブジェクトでプログラムによるデフォルト設定を実行できます。
エンティティ行が最初に作成される場合
エンティティ行が最初に作成される場合、またはリフレッシュされて再度空白に設定される場合
エンティティ行がデータベースに保存される場合
エンティティ属性値が設定される場合
create()
メソッドは、エンティティ行の最初の作成時にデフォルト値の初期化を処理するエンティティ・オブジェクト・イベントを提供します。例9-3は、SRDemoアプリケーションのServiceHistory
エンティティ・オブジェクトのオーバーライドされたcreateメソッドです。このメソッドは、新しいサービス履歴エンティティ行のSvhType
属性、CreatedBy
属性、およびLineNo
属性に値を移入する、属性のsetterメソッドをコールします。
例9-3 新しい行の属性値に対する、プログラムによるデフォルト設定
// In ServiceHistoryImpl.java in SRDemo sample protected void create(AttributeList nameValuePair) { super.create(nameValuePair); setSvhType(getDefaultNoteType()); setCreatedBy(getCurrentUserId()); setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1)); }
エンティティ行がNew
状態であり、そのエンティティ行に対してrefresh()
メソッドをコールした場合、REFRESH_REMOVE_NEW_ROWS
またはREFRESH_FORGET_NEW_ROWS
フラグを指定しないと、エンティティ行はInitialized
状態に戻ります。このプロセスの一環として、エンティティ・オブジェクトのinitDefaults()
メソッドが実行されますが、create()
メソッドは再度実行されません。そのため、行が最初に作成されたとき、また、initialized状態にリフレッシュされたときに実行するプログラム的なデフォルト設定ロジックで、initDefaults()
メソッドをオーバーライドします。
6.6.3.7項「トリガーによって割り当てられた値の同期化」では、コミット時にデータベース順序によって移入する必要のある主キー属性の値のあるDBSequence
型の使用方法について説明しました。エンティティ行の作成時にユーザーが値を参照でき、この値がデータ保存時に変更されないように、順序番号を事前に割り当てる必要が生じることがあります。そのためには、例9-4に示すように、オーバーライドされたcreate()
メソッドでoracle.jbo.server
パッケージのSequenceImpl
ヘルパー・クラスを使用します。この例は、SRDemoアプリケーションのProduct
エンティティ・オブジェクトのカスタムJavaクラスのコードを示しています。super.create()
をコールした後に、順序名と現在のトランザクション・オブジェクトを渡し、SequenceImpl
オブジェクトの新規インスタンスを作成します。続いてSequenceImpl
のgetSequenceNumber()
メソッドからの戻り値を含む、setProdId()
属性のsetterメソッドをコールします。
例9-4 作成時に順序からの属性値のデフォルト設定
// In ProductImpl.java import oracle.jbo.server.SequenceImpl; // Default ProdId value from PRODUCTS_SEQ sequence at entity row create time protected void create(AttributeList nameValuePair) { super.create(nameValuePair); SequenceImpl sequence = new SequenceImpl("PRODUCTS_SEQ",getDBTransaction()); setProdId(sequence.getSequenceNumber()); }
行を保存する前に、プログラムによるデフォルト値をエンティティ・オブジェクトの属性値に割り当てる場合、prepareForDML()
メソッドをオーバーライドし、適切な属性のsetterメソッドをコールし、導出される属性値を移入します。INSERT
、UPDATE
またはDELETE
の処理中にのみ割当てを実行するには、DML_INSERT
、DML_UPDATE
、DML_DELETE
のそれぞれの整数値の定数と、このメソッドに渡されたoperation
パラメータの値を比較できます。
例9-5は、特定の型のサービス履歴メモが作成されたときのサービス・リクエストのステータスを自動変更するため、SRDemoアプリケーション内のServiceHistory
エンティティ・オブジェクトで使用される、オーバーライドされたprepareForDML()
メソッドを示しています。新しいサービス履歴のエントリを挿入すると、このコードにより次のステータスが変更されます。
顧客が新規履歴メモを追加した場合の、保留中または処理済のサービス・リクエストのステータスの未処理への変更
技術者が新規履歴メモを追加した場合の、未処理のサービス・リクエストのステータスの保留中への変更
例9-5 PrepareForDMLを使用した保存前の導出値の割当て
// In ServiceHistoryImpl.java protected void prepareForDML(int operation, TransactionEvent e) { super.prepareForDML(operation, e); // If we are inserting a new service history entry... if (operation == DML_INSERT) { ServiceRequestImpl serviceReq = getServiceRequest(); String historyType = getSvhType(); // If request is pending or closed and customer adds note, status => Open if ((serviceReq.isPending() || serviceReq.isClosed()) && CUSTOMER_TYPE.equals(historyType)) { serviceReq.setOpen(); } // If request is open & technician adds a non-hidden note, status => Pending if (serviceReq.isOpen() && TECHNICIAN_TYPE.equals(historyType)) { serviceReq.setPending(); } } }
他の属性値が設定されているときに、導出された属性値を割り当てるには、最初の属性のsetterメソッドにコードを追加します。例9-6は、SRDemoアプリケーションのServiceRequest
エンティティ・オブジェクトのAssignedTo
属性に対するsetterメソッドを示しています。setAttributeInternal()
をコールしてAssignedTo
属性の値を設定すると、AssignedDate
属性のsetterメソッドにより、現在日時の値が設定されます。
例9-6 AssignedTo属性の変更に応じて割当て日を設定するメソッド
// In ServiceRequestImpl.java public void setAssignedTo(Number value) { setAttributeInternal(ASSIGNEDTO, value); setAssignedDate(getCurrentDateWithTime()); }
注意: ここで説明した生成済の属性のgetterメソッドとsetterメソッドへのカスタム・コードの追加は、安全に行うことができます。JDeveloperでクラスのコードが変更された場合でも、カスタム・コードはそのまま保持されます。 |
refresh(int flag)
メソッドをRow
に対して使用すると、保留中の変更をリフレッシュできます。refresh()
メソッドの動作は、パラメータとして渡すフラグによって決まります。この動作を制御する3つの主要なフラグの値は、Row
インタフェースの次の定数です。
REFRESH_WITH_DB_FORGET_CHANGES
は、現在のトランザクションによる行への変更を破棄し、行データがデータベースからリフレッシュされます。行が変更されたかどうかにかかわらず、現在の行はデータベースの最新データで上書きされます。
REFRESH_WITH_DB_ONLY_IF_UNCHANGED
は、変更されていない行に対してのみ、REFRESH_WITH_DB_FORGET_CHANGES
と同様に動作します。現在のトランザクションで行がすでに変更されている場合は、行はリフレッシュされません。
REFRESH_UNDO_CHANGES
は、変更されていない行に対してのみ、REFRESH_WITH_DB_FORGET_CHANGES
と同様に動作します。変更された行については、このモードは、トランザクションの開始時点での属性値でリフレッシュします。行はmodified状態のままとなります。
デフォルトでは、refresh()
を実行したNew
状態のすべてのエンティティ行がInitialized
状態の空白行に戻ります。宣言的なデフォルト値と、initDefaults()
メソッドに記述されたプログラム的なデフォルトはリセットされますが、エンティティ・オブジェクトのcreate()
メソッドは、この無効化のプロセスでは実行されません。
このデフォルトの動作は、次の2つのフラグのうちの1つと、前述の項のフラグの1つをビット単位のOR
演算子によって組み合せて変更できます。
REFRESH_REMOVE_NEW_ROWS
: リフレッシュ時に新しい行が削除されます。
REFRESH_FORGET_NEW_ROWS
: 新しい行はDead
とマークされます。
ビジネス・ロジックでSQL問合せの実行が必要とされる場合、一般的には、そのタスクの実行にはビュー・オブジェクトを使用します。検証のために実行するSQL文では、エンティティ・ベースのビュー・オブジェクトであれば、エンティティ・キャッシュ内の保留中の変更が参照されます。読取り専用ビュー・オブジェクトでは、データベースに送信されたデータのみを取得します。
エンティティ・オブジェクトは、すべてのアプリケーション・シナリオ内での再使用を考慮して設計されているため、特定のアプリケーション・モジュールのデータ・モデルにおけるビュー・オブジェクト・インスタンスに直接的には依存できません。依存する場合、他のアプリケーション・モジュールで再使用ができないという事態が発生します。
そのかわり、エンティティ・オブジェクトから実行時に検出されるルート・アプリケーション・モジュールへアクセスし、そのアプリケーション・モジュール・インスタンスのcreateViewObject()
メソッドによって、必要な検証ビュー・オブジェクト・インスタンスを作成できます。このAPIでは、設計時にデータ・モデルに追加されたビュー・オブジェクト・インスタンスと同様に、ビュー・オブジェクトにインスタンス名を割り当てることができます。そのため、必要に応じてfindViewObject()
を使用して再検索できます。
SQLベースの検証コードは何度も実行できるため、毎回、使用するたびにビュー・オブジェクト・インスタンスを作成し、使用後に削除するという方法は効率的ではありません。そのかわり、既存のビュー・オブジェクト・インスタンスを使用するか、既存のインスタンスが存在しない場合は、最初に必要なときに作成するよう、例9-7に示すようなヘルパー・メソッドを実装できます。実行時に作成されたビュー・オブジェクト・インスタンスのインスタンス名がデータ・モデル内で設計時に指定したインスタンスの名前と競合しないよう、最初にValidation_
などの文字列を付けるなど、ビュー・オブジェクト定義名に基づく命名規則を採用します。これは1つの方法にすぎません。設計時の名前と競合しないのであれば、どのような命名規則でも採用できます。
例9-7 検証用にビュー・オブジェクトにアクセスするヘルパー・メソッド
/** * Find instance of view object used for validation purposes in the * root application module. By convention, the instance of the view * object will be named Validation_your_pkg_YourViewObject. * * If not found, create it for the first time. * * @return ViewObject to use for validation purposes * @param viewObjectDefName */ protected ViewObject getValidationVO(String viewObjectDefName) { // Create a new name for the VO instance being used for validation String name = "Validation_" + viewObjectDefName.replace('.', '_'); // Try to see if an instance of this name already exists ViewObject vo = getDBTransaction().getRootApplicationModule() .findViewObject(name); // If it doesn't already exist, create it using the definition name if (vo == null) { vo = getDBTransaction().getRootApplicationModule() .createViewObject(name,viewObjectDefName); } return vo; }
このようなヘルパー・メソッドが存在するため、検証コードからgetValidationVO()
をコールし、使用するビュー・オブジェクト定義の完全修飾名を渡すことができます。これにより、例9-8のようなコードを記述できます。
例9-8 Method Validatorでの検証ビュー・オブジェクトの使用
// Sample entity-level validation method public boolean validateSomethingUsingViewObject() { Number numVal = getSomeEntityAttr(); String stringVal = getAnotherEntityAttr(); // Get the view object instance for validation ViewObject vo = getValidationVO("devguide.example.SomeViewObjectName"); // Set it's bind variables (which it will typically have!) vo.setNamedBindWhereClauseParam("BindVarOne",numVal); vo.setNamedBindWhereClauseParam("BindVarTwo",stringVal); vo.executeQuery(); if ( /* some condition */) { /* * code here returns true if the validation succeeds */ } return false; }
サンプル・コードで示すように、検証に使用されるビュー・オブジェクトには通常、1つ以上の名前付きのバインド変数が含まれます。ビュー・オブジェクトが取得するデータの種類によって、前述の/* some condition */
式は異なります。たとえば、ビュー・オブジェクトのSQL問合せがCOUNT()
などの集約関数を選択した場合、条件では一般的にvo.first()
メソッドを使用して最初の行にアクセスし、続いてgetAttribute()
メソッドを使用して属性値にアクセスし、そのカウントに対してデータベースが戻す結果を参照します。
問合せから0行または1行のどちらが戻されたかによって、検証が成功または失敗した場合、この条件はvo.first()
がnull
を戻したかどうかのみを検証します。vo.first()
がnull
を戻した場合、最初の行は存在しません。つまり、問合せで行は検出されていません。
また、ビュー・オブジェクトから取得された1つ以上の問合せ結果に対し、検証の成否を繰り返し確認することもあります。
一般的なSQLベースの検証の1つに、関連表内に候補となる外部キーが存在するかどうかを確認する簡単なテストがあります。このタイプの検証は、エンティティ定義でfindByPrimaryKey()
メソッドを使用すると実装できますが、エンティティが存在する場合、エンティティのすべての属性を取得します。他により負荷の低い存在チェックを行う方法として、検証にビュー・オブジェクトを使用する方法があります。
例9-9は、SRDemoアプリケーションのProduct
エンティティ・オブジェクトのカスタム・エンティティ定義クラスに含まれるexists()
メソッドを示しています。最初に、このメソッドでは、パラメータとして、DBTransaction
を受け入れる前述のヘルパー・メソッドのバリアントを使用し、該当する検証ビュー・オブジェクトのインスタンスを戻します。これは、同じクラスのgetProductExistsVO()
メソッド内にカプセル化されます。
検証に使用される読取り専用ビュー・オブジェクトには、oracle.srdemo.model.entities.validationqueries
パッケージ内でProductExistsById
という名前が付いています。このビュー・オブジェクトにはカスタムJavaクラス(ProductExistsByIdImpl
)があるため、exists()
メソッドのコードでは、強く型付けされたsetTheProductId()
メソッドを使用し、ビュー・オブジェクトが定義する名前付きのバインド変数を設定できます。続いて、コードが問合せを実行し、行が検出されたかどうかに基づき、ブール変数foundIt
を設定します。
例9-9 ビュー・オブジェクトとエンティティ・キャッシュを使用した、効率的な存在チェック
// In ProductDefImpl.java in SRDemo sample public boolean exists(DBTransaction t, Number productId) { boolean foundIt = false; ProductExistsByIdImpl vo = getProductExistsVO(t); vo.setTheProductId(productId); vo.setForwardOnly(true); vo.executeQuery(); foundIt = (vo.first() != null); /* * If we didn't find it in the database, * try checking against any new employees in cache */ if (!foundIt) { Iterator iter = getAllEntityInstancesIterator(t); while (iter.hasNext()) { ProductImpl product = (ProductImpl)iter.next(); /* * NOTE: If you allow your primary key attribute to be modifiable * then you should also check for entities with entity state * of Entity.STATUS_MODIFIED here as well. */ if (product.getEntityState() == Entity.STATUS_NEW && product.getProdId().equals(productId)) { foundIt = true; break; } } } return foundIt; }
現在、SRDemoアプリケーションではエンド・ユーザーは新製品を作成できませんが、今後、作成が可能になった場合に備え、検証を実装しておくことをお薦めします。executeQuery()
に続くコードでは、候補となる製品IDが、キャッシュに存在する新しいProduct
エンティティのものであるかどうかをテストします。
検証ビュー・オブジェクトにはエンティティ・オブジェクトの慣用名がないため、その問合せはデータベース内の行を参照するのみです。そのため、データベースへの確認後にfoundIt
変数がfalseになる場合、残りのコードが現在キャッシュ内にあるProductImpl
エンティティ行のイテレータを取得します。そして、行内をループし、新しいProduct
エンティティ行に候補となる製品IDが存在するかどうかを確認します。存在した場合、exists()
メソッドはtrue
を戻します。
beforeCommit()
メソッドは、保留中の変更リスト内の各エンティティ行の変更がデータベースに送信された後で、かつコミットされる前に実行されます。これは、指定した型のすべてのエンティティ行に対して規則を適用する必要のある、ビュー・オブジェクト・ベースの検証を実行するために最適なメソッドです。
注意: beforeCommit() ロジックで例外ValidationException がスローされた場合、構成中にjbo.txn.handleafterpostexc プロパティをtrue に設定する必要があります。これにより、フレームワークは現在のコミット・サイクルにおいて、データベースに送信済(かつコミット前)の他のエンティティ・オブジェクトのメモリー内の状態のロールバックを自動的に処理します。 |
作成した検証規則や、導出された値のプログラムによるデフォルト設定には、関連するエンティティ行の確認が必要となる場合があります。これは、エンティティ・オブジェクトのカスタムJavaクラスのアソシエーション・アクセッサ・メソッドによって非常に簡単に処理できます。アクセッサ・メソッドをコールすると、アソシエーションのカーディナリティに応じて、すべての関連するエンティティ行、またはエンティティ行のRowSet
に簡単にアクセスできます。
例9-10は、SRDemoアプリケーションのServiceHistory
エンティティ・オブジェクトで使用される、プログラムによるデフォルト設定のロジックを示しています。新規サービス履歴の行の行番号は、ServiceHistoryImpl
型の親エンティティ行にアクセスし、そこで値を1つ増やす前にgetMaxHistoryLineNumber()
というヘルパー・メソッドを実行して算出します。親エンティティ行がすでにキャッシュに存在する場合、アソシエーション・アクセッサはそこからキャッシュ内の行にアクセスします。キャッシュ内に存在しない場合は、主キーを使用してキャッシュにアクセスします。
例9-10 createメソッドにおける構成する側の親エンティティ行へのアクセス
// In ServiceHistoryImpl.java in SRDemo sample protected void create(AttributeList nameValuePair) { super.create(nameValuePair); setSvhType(getDefaultNoteType()); setCreatedBy(getCurrentUserId()); setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1)); }
例9-11は、ServiceRequest
エンティティ・オブジェクトのカスタムJavaクラスにおける、getMaxHistoryLineNumber()
のコードを示しています。アソシエーション・アクセッサの別の方法として、また、既存のサービス履歴行のLineNo
属性の最大値を算出するため、子のServiceHistory
行(ServiceHistoryImpl
型)のRowSet
を取得する方法も示しています。
例9-11 アソシエーション・アクセッサを使用した、値の算出における構成される側の子エンティティ行へのアクセス
// In ServiceRequestImpl.java in SRDemo Sample public long getMaxHistoryLineNumber() { long max = 0; RowSet histories = (RowSet)getServiceHistories(); if (histories != null) { while (histories.hasNext()) { long curLine = ((ServiceHistoryImpl)histories.next()).getLineNo() .longValue(); if (curLine > max) { max = curLine; } } } histories.closeRowSet(); return max; }
jbo.security.enforce
実行時の構成プロパティをMust
またはAuth
のいずれかの値に設定している場合、oracle.jbo.server.SessionImpl
オブジェクトは、認証されたユーザーの名前と、そのユーザーの属するロールの情報を取得するメソッドを提供します。これは、クライアントがアクセス可能なoracle.jbo.Session
インタフェースの実装クラスです。
oracle.jbo.Session
インタフェースでは、次の2つのメソッドを提供します。
String[] getUserRoles()
は、ユーザーが属するロール名の配列を戻します。
boolean isUserInRole(String roleName)
は、ユーザーが指定されたロールに属する場合にtrue
を戻します。
エンティティ・オブジェクト・コードは、次をコールしてSession
にアクセスできます。
Session session = getDBTransaction().getSession();
例9-12に、この方法を使用しているヘルパー・メソッドを示します。このメソッドは、isUserInRole()
メソッドによって現在のユーザーがtechnician
ロールに属しているかをテストして、ユーザーが技術者かどうかを判断します。
例9-12 認証されたユーザーが指定されたロールに属するかどうかをテストするヘルパー・メソッド
protected boolean currentUserIsTechnician() { return getDBTransaction().getSession().isUserInRole("technician"); }
定数を別々のSRConstants
クラスにリファクタすると、SRDemoアプリケーションでは、SREntityImpl
ベース・クラスに次のようなヘルパー・メソッドが含まれます。サンプルのすべてのエンティティ・オブジェクトは、この一般的な機能を継承するように拡張されています。
protected boolean currentUserIsTechnician() { return getDBTransaction().getSession() .isUserInRole(SRConstants.TECHNICIAN_ROLE); } protected boolean currentUserIsManager() { return getDBTransaction().getSession() .isUserInRole(SRConstants.MANAGER_ROLE); } protected boolean currentUserIsCustomer() { return getDBTransaction().getSession() .isUserInRole(SRConstants.USER_ROLE); } protected boolean currentUserIsStaffMember() { return currentUserIsManager() || currentUserIsTechnician(); }
これらのメソッドは、現在のユーザーのロールに基づく、サービス・リクエスト・タイプの条件付きのデフォルトの設定のため、create()
メソッドで使用されます。getDefaultNoteType()
ヘルパー・メソッドは次のようになります。
// In ServiceHistoryImpl.java in SRDemo sample private String getDefaultNoteType() { return currentUserIsStaffMember() ? TECHNICIAN_TYPE : CUSTOMER_TYPE; }
これはServiceHistory
エンティティ・オブジェクトのオーバーライドされたcreate()
メソッドにより、現在のユーザーのロールに基づくサービス履歴タイプのデフォルトの設定のために使用されます。
// In ServiceHistoryImpl.java in SRDemo sample protected void create(AttributeList nameValuePair) { super.create(nameValuePair); setSvhType(getDefaultNoteType()); setCreatedBy(getCurrentUserId()); setLineNo(new Number(getServiceRequest().getMaxHistoryLineNumber()+1)); }
認証されたユーザーの名前にアクセスするには、Session
インタフェースをそのSessionImpl
実装クラスにキャストする必要があります。その後、getUserPrincipalName()
メソッドを使用できます。例9-13は、現在のユーザー名を取得するためにエンティティ・オブジェクトで使用可能なヘルパー・メソッドを示しています。
エンティティ属性値が現在のトランザクション内で変更されている場合、属性getterメソッドをその属性値に対してコールすると、この保留中の変更値が戻されます。getPostedAttribute()
メソッドを使用すると、エンティティ・オブジェクトのビジネス・ロジックで、エンティティ行が変更される前にデータベースから読み取った、属性の元の値を確認できます。このメソッドは属性索引を引数としているため、JDeveloperが保持している、生成された属性の適切な索引定数を渡します。
現在のユーザー・セッションに関連した情報を、エンティティ・オブジェクトのビジネス・ロジックにより参照可能な方法で格納する必要がある場合、Session
オブジェクトで提供されるユーザー・データのハッシュテーブルを使用できます。SRDemoアプリケーションによるハッシュテーブルの使用方法を検討してみます。新規ユーザーがアプリケーション・モジュールに初めてアクセスする場合、prepareSession()
メソッドがコールされます。例9-14に示すように、SRService
アプリケーション・モジュールはprepareSession()
をオーバーライドし、LoggedInUser
ビュー・オブジェクト・インスタンスのretrieveUserInfoForAuthenticatedUser()
メソッドをコールして、認証されたユーザーに関する情報を自動的に取得します。次に、ユーザーの数値IDをユーザー・データ・ハッシュテーブルに保存するため、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドをコールします。
例9-14 ユーザー情報の自動問合せのためのprepareSession()のオーバーライド
// In SRServiceImpl.java in SRDemo Sample protected void prepareSession(Session session) { super.prepareSession(session); /* * Automatically query up the correct row in the LoggedInUser VO * based on the currently logged-in user, using a custom method * on the LoggedInUser view object component. */ getLoggedInUser().retrieveUserInfoForAuthenticatedUser(); setUserIdIntoUserDataHashtable(); }
例9-15に、LoggedInUser
ビュー・オブジェクトのretrieveUserInfoForAuthenticatedUser()
メソッドのコードを示します。これは、それ自体のEmailAddress
バインド変数を、セッションの認証されたユーザー名に設定し、executeQuery()
をコールしてUSERS
表から追加のユーザー情報を取得します。
例9-15 ユーザー詳細の追加取得のための、認証されたユーザー名へのアクセス
// In LoggedInUserImpl.java public void retrieveUserInfoForAuthenticatedUser() { SessionImpl session = (SessionImpl)getDBTransaction().getSession(); setEmailAddress(session.getUserPrincipalName()); executeQuery(); first(); }
LoggedInUser
ビュー・オブジェクトが取得する、認証されたユーザーの情報の1つには、メソッドの結果として戻されるユーザーの数値ID番号があります。たとえば、ユーザーsking
には、数値のUserId
として300
が割り当てられています。
例9-16は、前述のprepareSession()
コードで使用される、setUserIdIntoUserDataHashtable()
ヘルパー・メソッドを示しています。このメソッドでは、文字列定数SRConstants.CURRENT_USER_ID
で指定されるキーを使用して、数値ユーザーIDをユーザー・データ・ハッシュテーブルに格納します。
例9-16 エンティティ・オブジェクトからのアクセスのためのユーザー・データ・ハッシュテーブルへの情報の設定
// In SRServiceImpl.java private void setUserIdIntoUserDataHashtable() { Integer userid = getUserIdForLoggedInUser(); Hashtable userdata = getDBTransaction().getSession().getUserData(); if (userdata == null) { userdata = new Hashtable(); } userdata.put(SRConstants.CURRENT_USER_ID, userid); }
ServiceRequest
エンティティ・オブジェクトおよびServiceHistory
エンティティ・オブジェクトでは、どちらも次のようなヘルパー・メソッドを使用して数値ユーザーIDを参照するようcreate()
メソッドがオーバーライドされています。オーバーライドされたメソッドでは、CreatedBy
属性を現在認証されているユーザーの数値ユーザーIDにプログラム的に設定します。
protected Number getCurrentUserId() { Hashtable userdata = getDBTransaction().getSession().getUserData(); Integer userId = (Integer)userdata.get(SRConstants.CURRENT_USER_ID); return userdata != null ? Utils.intToNumber(userId):null; }
エンティティ・オブジェクトのビジネス・ロジックにおいては、現在の日時の参照が役立つ場合があります。例9-17では、時間情報を含めず、現在日付にのみヘルパー・メソッドでアクセスする方法について示しています。
例9-17 時間情報を含めず、現在日付にアクセスするヘルパー・メソッド
/* * Helper method to return current date without time * * Requires import: oracle.jbo.domain.Date */ protected Date getCurrentDate() { return new Date(Date.getCurrentDate()); }
一方、現在の日付に時間情報も含める必要がある場合、例9-18で示すヘルパー・メソッドを使用してください。
afterCommit()
メソッドは、保留中の変更リスト内にあり、データベースに正常に保存された各エンティティ行に対して起動します。このメソッドを使用して、エンティティの状態の変化を電子メールで通知できます。
remove()
メソッドは、エンティティ行削除の前に、その行に対して起動します。このメソッドでは、条件付きでJboException
をスローし、該当する条件を満たさない場合は行の削除を回避するよう設定できます。
注意: このエンティティ・オブジェクトでは、既存の構成される側の子行がある場合、マスター・エンティティ行の削除が宣言的に回避されます。この構成に対して、このオプションはアソシエーション・エディタの「アソシエーション・プロパティ」ページで設定できます。 |
指定された属性を更新可能にするかどうかを、実行時に適切な条件に従ってプログラム的に判断するために、エンティティ・オブジェクト・クラス内のisAttributeUpdateable()
メソッドをオーバーライドできます。例9-19は、現在の認証されたユーザーがスタッフ・メンバーである場合にのみSvhType
属性を更新可能にするため、SRDemoアプリケーション内のServiceHistory
エンティティ・オブジェクトにより、このメソッドがどのようにオーバーライドされるかを示しています。エンティティ・オブジェクトでは、このメソッドを実行する際、更新可能性の確認対象となる整数属性索引を渡します。特定の属性に対する条件付きの更新可能性ロジックは、属性索引に基づいて、if
文またはswitch
文内に実装します。ここでは、SVHTYPE
がエンティティ・オブジェクトのカスタムJavaクラスに自動的に保持されている整数属性の索引定数を参照します。
例9-19 属性の更新可能性の実行時の条件付きの判断
// In ServiceHistoryImpl.java public boolean isAttributeUpdateable(int index) { if (index == SVHTYPE) { if (!currentUserIsStaffMember()) { return super.isAttributeUpdateable(index); } return CUSTOMER_TYPE.equals(getSvhType()) ? false : true; } return super.isAttributeUpdateable(index); }
注意: エンティティ・ベースのビュー・オブジェクトは、エンティティ・オブジェクト内でカプセル化された他の要素と同様に、この条件付きの更新可能性も継承します。この種の条件付き更新可能性ロジックを、一時ビュー・オブジェクト属性に固有の方法で実装する必要がある場合、または、ビュー・オブジェクト内の複数のエンティティ・オブジェクトのデータを含む一部の条件を適用する場合、ビュー・オブジェクトのビュー行クラス内の同じメソッドをオーバーライドすると、目的の結果が得られます。 |
Oracle Consultingによる『Business Rules in ADF Business Components』ホワイトペーパーでは、Oracle ADFを使用してプロジェクトを実装した際に直面したほぼすべての種類の実際のビジネス・ルールを、ADF Business Componentsを使用して分類および実装するための正式なアプローチについて説明しています。このホワイトペーパーには、OTNのOracle JHeadstartプロダクト・センターからアクセスできます。