この章では、ビュー・オブジェクトを設計および利用するときに使用できる高度な手法について説明します。
この章の内容は次のとおりです。
注意: この章の例の稼働バージョンを試すには、サンプルのダウンロード・ページ(http://otn.oracle.com/documentation/jdev/b25947_01/ )からAdvancedViewObjectsExamples ワークスペースをダウンロードしてください。 |
ここでは、これまでの章で解説されていないビュー・オブジェクトの興味深い概念と機能について説明します。
ビュー・オブジェクトのデフォルトの最大フェッチ・サイズは-1で、フェッチできる行数に人為的な制限がないことを示します。デフォルトでは、行は必要に応じてフェッチされるので、このデフォルト値はビュー・オブジェクトがすべての行をフェッチする必要があることを意味してはいないことに留意してください。問合せ結果のすべての行を反復処理しようとした場合、すべての行が取得されることを意味するのみです。
ただし、ビュー・オブジェクトが取得する行の最大数に上限を設けることが必要になる場合があります。ORDER BY
句を含む問合せを作成し、ページに上位N項目を表示するために最初のN
行を返す場合は、ビュー・オブジェクトでsetMaxFetchSize()
メソッドを呼び出して、最大フェッチ・サイズをN
に設定できます。ビュー・オブジェクトは、最大フェッチ・サイズに達すると行のフェッチを停止します。通常は、この手法と組み合せて、ビュー・オブジェクト・エディタの「チューニング」パネルでFIRST_ROWS
の「問合せオプティマイザ・ヒント」を指定します。これにより、すべての行の取得の最適化を試みるのではなく、できるかぎり早く最初の何行かを取得するよう、データベースにヒントが提供されます。
1つのアプリケーション・モジュールで、エンティティ・ベースのビュー・オブジェクトの複数インスタンスが、同じエンティティ・オブジェクトに基づいているときは、1つのインスタンスで作成された新しい行を、他のインスタンスの行セットに(再問合せを行うことなく)自動的に追加して、ユーザー・インタフェースの整合性を保つか、または保留中のトランザクションの異なるアプリケーション・ページに新しい行を一貫して反映することができます。エンド・ユーザーのサービス・リクエストのリストを表示するSRDemoアプリケーションのSRList
ページについて考えます。エンド・ユーザーが新しいサービス・リクエストを作成する場合、このタスクは別のビュー・オブジェクトを介して実行され、カスタム・アプリケーション・モジュールのメソッドによって処理されます。このビュー・オブジェクトの新規行の整合機能を使用すると、新しく作成されたサービス・リクエストが、データベースに再度問い合せることなく、SRList
ページにある開いているサービス・リクエストのエンド・ユーザーのリストに自動的に表示されます。
歴史的な理由から、この機能はビュー・リンクの一貫性機能と呼ばれています。これは、Oracle ADFの以前のリリースでは、アソシエーションに基づくビュー・リンクでの詳細ビュー・オブジェクト・インスタンスのサポートは、他の関連する行セットに対する新規行の追加にかぎられていたためです。現在では、このビュー・リンク一貫性機能は、ビュー・リンクに関連性があるかどうかには関係なく、この機能が有効になっているすべてのビュー・オブジェクトで機能します。
同じServiceRequest
エンティティ・オブジェクトに基づく2つのエンティティ・ベースのビュー・オブジェクトServiceRequestSummary
とServiceRequests
について考えます。一方のビュー・オブジェクト(ServiceRequests
など)の行セットで新しい行が作成され、行の主キーが設定されると、同じServiceRequest
エンティティ・オブジェクトに基づくビュー・オブジェクト(ServiceRequestSummary
など)の他の行セットは、新しい行が作成されたことを示すイベントを受け取ります。ビュー・リンク一貫性フラグが有効になっていると、新しい行のコピーの行セットへの挿入も行われます。
ビュー・リンク一貫性機能のデフォルト設定は、jbo.viewlink.consistent
構成パラメータを使用して制御できます。このパラメータのデフォルト設定はDEFAULT
で、次のような意味があります。ビュー・オブジェクトが次の状態であるものとします。
単一のエンティティ・オブジェクトの慣用名、ビュー・リンク一貫性は有効
複数のエンティティ・オブジェクトの慣用名、かつ
すべてのセカンダリ・エンティティ・オブジェクトの慣用名が寄与する参照情報としてマークされている場合は、ビュー・リンク一貫性が有効
いずれかのセカンダリ・エンティティ・オブジェクトの慣用名が参照としてマークされていない場合は、ビュー・リンク一貫性が無効
この場合は、構成でjbo.viewlink.consistent
に値false
を設定することで、この機能をグローバルに無効にできます。逆に、jbo.viewlink.consistent
に値true
を設定するとこの機能をグローバルに有効にできますが、このように設定することはお薦めしません。有効に設定すると、現時点ではビュー・リンク一貫性機能がサポートされていない参照としてマークされていないセカンダリ・エンティティ・オブジェクトの慣用名を持つビュー・オブジェクトに対しても、強制的にビュー・リンク一貫性が設定されます。
この機能をプログラムで設定するには、任意のRowSet
でsetAssociationConsistent()
APIを使用します。ビュー・オブジェクトでこのメソッドを呼び出すと、デフォルトの行セットに適用されます。
ビュー・オブジェクトでビュー・リンク一貫性が有効になっている場合は、同じエンティティ・オブジェクトに基づく別のビュー・オブジェクトで作成された新しい行が、行セットに追加されます。デフォルトでは、新しい行は無条件に追加されます。ビュー・オブジェクトに、行の特定のサブセットのみを問い合せる設計時WHERE
句がある場合は、ビュー・オブジェクトにRowMatch
オブジェクトを適用して、同じフィルタ処理をメモリー内で実行できます。指定するRowMatch
オブジェクトのフィルタ式により、そのオブジェクトに表示しても意味のない新規行は追加されません。
たとえば、SRDemoアプリケーションのServiceRequestsByStatus
ビュー・オブジェクトには、次のような設計時WHERE
句が含まれます。
WHERE /* ... */ AND STATUS LIKE NVL(:StatusCode,'%')
そのカスタムJavaクラスでは、create()
メソッドが例27-1で示すようにオーバーライドされており、ビュー・リンク一貫性を強制的に有効にします。また、Status
属性が:StatusCode
名前付きバインド変数の値と一致する行(または:StatusCode
= '%
'の場合はすべての行)に一致するフィルタ式を含むRowMatch
オブジェクトを適用します。ビュー・リンク一貫性メカニズムは、このフィルタを使用して、行セットに追加する候補の行を限定します。RowMatch
で適格とされた行は追加されます。そうでない行は追加されません。
例27-1 追加される新規行を制御するためのカスタムRowMatchの提供
// In ServiceRequestsByStatusImpl.java protected void create() { super.create(); setAssociationConsistent(true); setRowMatch(new RowMatch("Status = :StatusCode or :StatusCode = '%'")); }
RowMatch
オブジェクトの作成と使用の詳細は、27.5.4項「RowMatchによるメモリー内フィルタ処理の実行」を参照してください。
注意: RowMatch 機能では十分に制御できない場合は、ビュー・オブジェクトのrowQualifies() メソッドをオーバーライドして、カスタム・フィルタ処理ソリューションを実装できます。コードを使用して、新しい行をビュー・リンク一貫性メカニズムで追加するかどうかを決定できます。 |
ビュー・オブジェクトでsetWhereClause()
を呼び出して動的where句を設定すると、そのビュー・オブジェクトのビュー・リンク一貫性機能は無効になります。行セットに追加する新しい行を限定するために適切なカスタムRowMatch
オブジェクトを提供していた場合は、setWhereClause()
の後でsetAssociationConsistent(true)
を呼び出すことで、ビュー・リンク一貫性を再び有効にできます。
ビュー・オブジェクトは、次の2種類のスタイルのマスター/ディテール調整をサポートします。
ビュー・リンク・インスタンスをアプリケーション・モジュールのデータ・モデルに追加するときは、2つの特定のビュー・オブジェクト・インスタンスを接続し、2つの間にアクティブなマスター/ディテール調整を設定することを示します。実行時には、データ・モデル内のビュー・リンク・インスタンスにより、このコーディネーションを有効にするイベント処理が容易になります。マスター・ビュー・オブジェクト・インスタンスで現在行が変更されるたびに、イベントが発生し、マスター・ビュー・オブジェクトの新しい現在行に対する新しいバインド・パラメータのセットでexecuteQuery()
が自動的に呼び出されることで、ディテール・ビュー・オブジェクトがリフレッシュされます。
このアクティブなデータ・モデル・マスター/ディテールの重要な特徴は、マスターおよびディテール・ビュー・オブジェクト・インスタンスが、クライアントのユーザー・インタフェースがバインディングを確立できる固定のオブジェクトであることです。マスターで現在行が変化すると、新しいディテール・ビュー・オブジェクト・インスタンスが生成されるのではなく、既存のディテール・ビュー・オブジェクト・インスタンスが、マスターの新しい現在行に関連する行のセットを含むように、デフォルトの行セットを更新します。さらに、ユーザー・インタフェースのバインディング・オブジェクトはイベントを受け取り、ディテール・ビュー・オブジェクトのリフレッシュされた行セットを含むように表示を更新できます。
アクティブなデータ・モデル・マスター/ディテールのみが備えるもう1つの重要な機能は、1つのディテール・ビュー・オブジェクト・インスタンスが複数のマスター・ビュー・オブジェクト・インスタンスを持つことができることです。たとえば、ExpertiseAreas
ビュー・オブジェクト・インスタンスは、Products
ビュー・オブジェクト・インスタンスとTechnicians
ビュー・オブジェクト・インスタンスの両方のディテールになることができます。Products
またはTechnicians
のビュー・オブジェクト・インスタンスの現在行が変わるたびに、ディテールExpertiseAreas
ビュー・オブジェクト・インスタンスのディテール行セットは、現在の技術者および現在の製品の専門分野情報の行を含むようにリフレッシュされます。複数マスターのディテール・ビュー・オブジェクト・インスタンスを設定する方法の詳細は、27.1.6項「複数マスターのデータ・モデルの設定」を参照してください。
ビュー・リンクによりビュー・オブジェクト行に関係するディテール行セットにプログラムでアクセスする必要がある場合は、ビュー・リンク・アクセッサ属性を使用できます。ビュー・リンク・アクセッサ属性の名前は、ビュー・リンク・エディタの「ビュー・リンク・プロパティ」パネルで制御します。アクセッサ属性の名前がAccessorAttrName
であるとすると、次のような汎用getAttribute()
APIを使用してディテール行セットにアクセスできます。
RowSet detail = (RowSet)currentRow.getAttribute("AccessorAttrName");
マスター・ビュー・オブジェクトに対するカスタム・ビュー行クラスを生成し、クライアント・ビュー行インタフェースでビュー・リンク・アクセッサ属性に対するgetterメソッドを公開してある場合は、次のような強く型付けされたコードを使用してディテール行セットにアクセスできます。
RowSet detail = (RowSet)currentRow.getAccessorAttrName();
アクティブなデータ・モデル・マスター/ディテールとは異なり、ビュー・リンク・アクセッサ属性のプログラムによるアクセスでは、アプリケーション・モジュールのデータ・モデルにディテール・ビュー・オブジェクト・インスタンスは必要ありません。ビュー・リンク・アクセッサ属性を呼び出すたびに、呼出しを行ったマスター行に関連するディテール行のセットを含むRowSet
が返されます。
ビュー・リンク・アクセッサ属性を使用すると、ディテール・データ行は不変になります。マスター行のビュー・リンク定義に関係する属性値が変わらないかぎり、ディテール・データ行は変化しません。マスターの現在行が変化しても、特定のマスター行にアタッチされているディテール行セットは影響を受けません。このため、ビュー・リンク・アクセッサ属性は、ディテール行の汎用的なプログラム・アクセスに便利なだけでなく、ツリー・コントロールのようなUIオブジェクトにも適しています。ツリー・コントロールでは、ツリー内の各マスター・ノードのデータは、個別のディテール行のセットを維持する必要があります。
アクティブなデータ・モデル・マスター/ディテールと、ビュー・リンク・アクセッサを使用するディテール行セットのプログラム・アクセスを組み合せるときは、これらが個別のメカニズムであることを理解することがいっそう重要になります。たとえば、次のことを行ったものとします。
ServiceRequests
およびServiceHistories
ビュー・オブジェクトを定義しました。
これらの間のビュー・リンクを定義し、ビュー・リンク・アクセッサにHistoriesForRequest
という名前を付けました。
これらのインスタンスを、ビュー・リンク・インスタンスによってアクティブに調整されるmaster
(ServiceRequests
型)およびdetail
(ServiceHistories
型)という名前の、アプリケーション・モジュールのデータ・モデルに追加しました。
master
ビュー・オブジェクトのインスタンスでサービス・リクエストを検索すると、detail
ビュー・オブジェクトのインスタンスが更新され、予測どおりに対応するサービス・リクエスト履歴が表示されます。この時点で、現在のServiceRequests
行のHistoriesForRequest
ビュー・リンク・アクセッサ属性にプログラムでアクセスするカスタム・メソッドを呼び出すと、ServiceHistory
行のセットを含むRowSet
が取得されます。一般には、プログラムでアクセスするこのRowSet
はデータ・モデルのdetail
ビュー・オブジェクト・インスタンスに由来するものと考えられますが、そうではありません。
ビュー・リンク・アクセッサによって返されるRowSet
は、常に内部的に作成されたビュー・オブジェクト・インスタンスからのものであり、ユーザーがデータ・モデルに追加したものではありません。この内部的なビュー・オブジェクト・インスタンスは、必要に応じて作成され、システムが定義する名前でルート・アプリケーション・モジュールに追加されます。
内部的に作成される個別のビュー・オブジェクト・インスタンスが使用される最大の理由は、データ・モデル内のビュー・オブジェクト・インスタンスに対して開発者が行う変更によって、インスタンスが影響を受けないようにするためです。たとえば、ビュー行が、ビュー・リンク・アクセッサのRowSet
に対してデータ・モデル内のディテール・ビュー・オブジェクトを使用する場合、開発者が次のようなことを動的に行うと、結果の行セットが誤って影響を受ける可能性があります。
新しい名前付きバインド・パラメータを含むWHERE
句を追加する。
このようなビュー・オブジェクト・インスタンスがビュー・リンク・アクセッサの結果に使用されると、動的に追加されたWHERE
句のバインド・パラメータ値はビュー・リンク・アクセッサのRowSet
に提供されていないため、予想外の結果またはエラーが発生します。このような値が提供されるのは、データ・モデル内のディテール・ビュー・オブジェクト・インスタンスのディテール行セットのみです。
データ・モデルのディテール・ビュー・オブジェクト・インスタンスにマスター・ビュー・オブジェクト・インスタンスを追加する。
このシナリオでは、アクセッサのセマンティクスが変更されます。アクセッサが現在のServiceRequest
行に対するServiceHistory
行を返すかわりに、たとえば現在の技術者によって作成された現在のServiceRequest
に対するServiceHistory
行のみを不意に返し始めます。
ディテール・ビュー・オブジェクト・インスタンスまたはそれを含むアプリケーション・モジュール・インスタンスを削除する。
このシナリオでは、プログラムでアクセスされたディテールRowSetのすべての行が無効になります。
さらに、Oracle ADFは、特定の操作について、アクティブなデータ・モデル・マスター/ディテールとビュー・リンク・アクセッサの行セットを区別する必要があります。たとえば、ディテール・ビュー・オブジェクトで新しい行を作成すると、フレームワークは、ビュー・リンクに含まれる属性に、マスターの対応する値を自動的に移入します。アクティブなデータ・モデル・マスター/ディテールの場合は、このような値を、データ・モデルの場合によっては複数のマスター・ビュー・オブジェクト・インスタンスの現在行から取得します。ビュー・リンク・アクセッサによって返されるRowSet
に新しい行を作成する場合は、アクセッサが呼び出されたマスター行からの値を移入します。
1ページずつデータを提示およびスクロールするために、適切なサイズの行範囲を管理するようにビュー・オブジェクトを構成できます。範囲機能を使用すると、クライアントは、行セット内の行のサブセットを簡単に表示および更新でき、一度にN
行ずつ簡単に後のページにスクロールできます。1ページに表示するデータの行数を定義するには、setRangeSize()
を呼び出します。デフォルトの範囲サイズは1
行です。-1
という範囲サイズは、範囲が行セットのすべての行を含むことを示します。
注意: ADFモデル・レイヤーの宣言的データ・バインディングを使用する場合、ページ定義のイテレータ・バインディングにRangeSize プロパティがあります。実行時に、イテレータ・バインディングは対応する行セット・イテレータでsetRangeSize() メソッドを呼び出し、このRangeSize プロパティの値を渡します。ADFの設計時には、デフォルトで、ほとんどのイテレータ・バインディングについて、RangeSize プロパティに10 行が設定されます。例外は、ドロップダウン・リストなどのUIコンポーネントに対する有効値の設定を提供するリスト・バインディングに指定される範囲サイズです。この場合は、デフォルトの範囲サイズは-1 であり、範囲は行セットのすべての行を含むことができます。 |
1より大きい範囲サイズを設定するときは、イテレータ・モードを使用して行セットのページ移動動作を制御します。次の2つのイテレータ・モード・フラグを、setIterMode()
メソッドに渡すことができます。
RowIterator.ITER_MODE_LAST_PAGE_PARTIAL
このモードでは、最終ページに含まれる行数は、範囲サイズより少なくてもかまいません。たとえば、範囲サイズを10に設定し、行セットに23行が含まれる場合は、3ページ目には3行のみ含まれます。これは、Webアプリケーションに対して最善のスタイルです。
RowIterator.ITER_MODE_LAST_PAGE_FULL
このモードでは、最終ページにも1ページ分の行数が表示され、前のページの下部に表示されていた行が、最後のページの上部に表示される場合があります。たとえば、範囲サイズを10に設定し、行セットに23行が含まれる場合は、3ページ目にも10行が表示され、3ページ目の最初から7行には、2ページ目の最後の7行が表示されます。これは、Swingを使用するデスクトップ・フィデリティ・アプリケーションに最適なスタイルです。
一般的な規則として、パフォーマンスを最高にするため、非常に大きな問合せ結果をエンド・ユーザーがスクロールすることがないような方法でアプリケーションを作成することをお薦めします。この推奨事項を実現するには、実際に問合せを実行してユーザーが操作を継続できるようにする前に、ビュー・オブジェクトでgetEstimatedRowCount()
メソッドを呼び出して、ユーザーの問合せで返される行数を判定します。推定される行カウントが不当に大きい場合は、さらに検索基準を提供するようエンド・ユーザーに要求できます。
ただし、非常に大きな結果セットを処理する必要がある場合は、範囲ページ移動と呼ばれるビュー・オブジェクトのアクセス・モードを使用して、パフォーマンスを上げることができます。この機能を使用すると、大きなデータ・セットについてデフォルトのスクロール可能アクセス・モードより効率的な方法で、一度にある行範囲のページ単位で、前方および後方にデータを移動できます。
Oracleデータベースは、問合せで順序付けされた最初のN
行を効率よく返すための、上位N問合せと呼ばれる機能をサポートします。たとえば、次のような問合せがあるものとします。
SELECT EMPNO, ENAME,SAL FROM EMP ORDER BY SAL DESC
給与順に上位5人の従業員を取得する場合は、問合せを次のように記述できます。
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN <= 5
結果は次のようになります。
EMPNO ENAME SAL RN ---------- -------- ------ ---- 7839 KING 5000 1 7788 SCOTT 3000 2 7902 FORD 3000 3 7566 JONES 2975 4 7698 BLAKE 2850 5
この機能は、順番に先頭のN
行を取得できるだけではありません。一番外側のWHERE
句の基準を調節することにより、問合せのソートされた順序で任意の範囲の行を効率よく取得できます。たとえば、問合せを次のように変更することで、6
行目から10
行目までを取得できます。
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN BETWEEN 6 AND 10
この考え方を一般化して、問合せ結果のP
ページ目を見たい場合は、1ページがR
行であるとすると、次のような問合せを作成します。
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN BETWEEN ((:P - 1) * :R) + 1 AND (:P) * :R
考えられる結果セットがより大きくなる場合、この手法を使用したページ移動の効率がさらに上がります。何百または何千もの行をデータベースからネットワーク経由で取得し、そのうちの10行のみをページに表示するかわりに、P
ページ目のR
行のみをデータベースから取得する賢明な問合せを作成できます。この方法を使用すると、ネットワーク経由で返す必要のある行数はほんのわずかで済みます。
このデータベース中心のページ移動方法をアプリケーションで実装するには、適切な問合せを自分で作成し、バインド変数:R
および:P
の適切な値を管理するコードを記述する必要があります。または、ビュー・オブジェクトの範囲ページ移動アクセス・モードを使用すると、このような機能が自動的に実装されます。
ビュー・オブジェクトの範囲ページ移動を有効にするには、最初にsetRangeSize()
を呼び出して1ページの行数を定義した後、次のメソッドを呼び出します。
yourViewObject.setAccessMode(RowSet.RANGE_PAGING);
ビュー・オブジェクトのアクセス・モードをRANGE_PAGING
に設定すると、ビュー・オブジェクトは次のような問合せをデフォルトとして使用します。
SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
そして、これを自動的にラップし、上位N問合せを生成します。
最善のパフォーマンスのため、文では演算子のかわりに、より大と、より小の基準の組合せが使用されますが、論理的な結果は、先に示した上位Nのラッピングの問合せと同じになります。基本になる問合せは次のようなものです。
SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
これがラップされて、実際には次のような問合せが生成されます。
SELECT * FROM ( SELECT /*+ FIRST_ROWS */ IQ.*, ROWNUM AS Z_R_N FROM ( SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC ) IQ WHERE ROWNUM < :0) WHERE Z_R_N > :1
2つのバインド変数は、次のようにバインドされます。
:1
は、現在のページの先頭行のインデックスです。
:0
は、現在のページの最終行にバインドされます。
ビュー・オブジェクトがRANGE_PAGING
アクセス・モードで動作するとき、ビュー行キャッシュのメモリー内に一度に保持するのは、現在の行範囲(つまりページ)のみです。つまり、一度に10ずつ結果をページ移動する場合、最初のページでは、1〜10行がビュー行キャッシュに保持されます。2ページ目に移動すると、キャッシュには11〜20行が格納されます。これは、大きな行セットの場合に、単に前後にスクロールできるようにするという理由で大量の行がキャッシュされないようにするためにも役立ちます。
ビュー・オブジェクトがRANGE_PAGING
アクセス・モードで動作しているときに、ページ番号N
にスクロールするには、scrollToRangePage()
メソッドを呼び出し、パラメータ値としてN
を渡します。
ビュー・オブジェクトがRANGE_PAGING
アクセス・モードで動作している場合、問合せ結果全体が生成する総ページ数の推定は、getEstimatedRangePageCount()
メソッドを使用して取得できます。
範囲ページ移動アクセス・モードは、通常、読取り専用行セットのページ移動に使用され、読取り専用ビュー・オブジェクトで使用されるのが普通です。ユーザーは範囲ページ移動アクセス・モードを使用し、大きな行セットをページ移動して対象行を検出できます。その後、その行のKey
を使用し、編集用の別のビュー・オブジェクトで選択されている行を検索します。
さらに、ビュー・オブジェクトは、行セットの行の挿入と削除に対応するために、RANGE_PAGING_AUTO_POST
アクセス・モードをサポートします。このモードはRANGE_PAGING
モードと同じように動作しますが、行セットに対してなんらかの変更が行われたときには常に、データベース・トランザクションでpostChanges()
を呼び出すところが異なります。このメソッドは、適切なINSERT
文、UPDATE
文、またはDELETE
文を通してデータベースに保留中の変更を通知するため、後方または前方にスクロールしても変更は保持されます。
常にRANGE_PAGING
モードを使用すればよい、というわけではありません。それは、範囲ページ移動を使用すると、ビュー・オブジェクトの行を前後に移動したときに実行される問合せの総量が増加する可能性があるためです。次のような状況では、RANGE_PAGING
モードの使用を避ける必要があります。
行セットのすべての行をただちに読み取ろうと考えている場合(たとえば、ドロップダウン・リストに移入するため)。
この場合は、範囲サイズを-1
に設定し、すべての行を含む単一のページのみが存在するため、範囲ページ移動のメリットはなくなります。
小さいサイズの行セットで前後にページ移動する必要がある場合。
行数が100以下で、RANGE_PAGING
モードを使用して10行ずつページ移動する場合、前後の新しいページに移動するために問合せが実行されます。通常のモードでは、読み取ったビュー・オブジェクト行はキャッシュされ、前のページから後方にページ移動しても、すでに表示された行を表示するために再び問合せが実行されることはありません。
非常に大きい(または大きさを予測できない)行セットの場合、問合せ(各問合せでは、データベースから最大でRangeSize
の数だけ行が戻されます)の実行回数が増える可能性がある場合のトレードオフは、表示済の行をすべてキャッシュすることに比べれば効率的です。結果のリストの任意のページにジャンプできるようにする場合は、このことが特に顕著です。通常のスクロール可能モードでこれを行うには、現在のページとジャンプ先のページの間にあるすべての行をフェッチしてキャッシュする必要があります。RANGE_PAGING
モードでは、そのページの行をデータベースに要求するのみです。その後、すでに表示したことのあるページにジャンプして戻る場合、RANGE_PAGING
モードでは現在のページの行しかメモリーに保持されないため、以前に取得した行の問合せが再び実行されます。
役に立つ場合には、同じディテール・ビュー・オブジェクトのインスタンスに対して複数のマスター・ビュー・オブジェクトのインスタンスが存在するように、データ・モデルを設定できます。Technicians
、Products
、およびExpertiseAreas
という名前のビュー・オブジェクトと、次のように定義されたビュー・リンクについて考えます。
Products
とExpertiseAreas
Technicians
とExpertiseAreas
注意: この項の例では、AdvancedViewObjectExamples ワークスペースのMultipleMasters プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。 |
図27-1は、Technicians
とProducts
の両方のビュー・オブジェクト・インスタンスが、同じExpertiseAreas
ビュー・オブジェクト・インスタンスのマスターになるように構成した場合の、データ・モデル・パネルの表示を示しています。
図27-1で示されているようにデータ・モデルを設定するには、アプリケーション・モジュール・エディタを開き、「データ・モデル」パネルで次の手順を行います。
Technicians
ビュー・オブジェクトのインスタンスをデータ・モデルに追加します。
名前はTechnicians
とします。
Products
ビュー・オブジェクトのインスタンスをデータ・モデルに追加します。
名前はProducts
とします。
「データ・モデル」リストで、Technicians
ビュー・オブジェクト・インスタンスを選択します。
「選択可能なビュー・オブジェクト」リストで、Technicians
ビュー・オブジェクトの下にインデントされているExpertiseAreas
ビュー・オブジェクトを選択し、「名前」フィールドにビュー・オブジェクト・インスタンスの名前ExpertiseAreas
を入力します。次に、「>」をクリックして、既存のTechnicians
ビュー・オブジェクト・インスタンスのディテールとしてデータ・モデルに移動します。
「データ・モデル」リストで、Products
ビュー・オブジェクト・インスタンスを選択します。
「選択可能なビュー・オブジェクト」リストで、Products
ビュー・オブジェクトの下にインデントされているExpertiseAreas
ビュー・オブジェクトを選択し、「名前」フィールドに同じビュー・オブジェクト・インスタンスの名前ExpertiseAreas
を入力します。次に、「>」をクリックして、既存のProducts
ビュー・オブジェクト・インスタンスのディテールとしてデータ・モデルに移動します。
「ExpertiseAreasという名前のビュー・オブジェクトのインスタンスはデータ・モデルですでに使用されています。同じインスタンスを使用しますか。」という警告が表示されます。
「はい」をクリックして、ExpertiseAreas
ビュー・オブジェクト・インスタンスを、Products
ビュー・オブジェクト・インスタンスのディテールにもすることを確認します。
複数のエンティティ・オブジェクトの慣用名に基づくビュー・オブジェクトでは、部分的に移入されたキーを指定してビュー行を検索できます。部分キーは、複数属性のKey
オブジェクトで、一部の属性がnull
に設定されたものです。ただし、findByKey()
を実行するために使用できる部分キーの種類には、厳密な規則があります。
ビュー・オブジェクトがN
個のエンティティ・オブジェクトの慣用名(N
> 1)に基づいている場合、ビュー行のキーは、デフォルトで、関連するすべてのエンティティ・オブジェクトの慣用名のすべての主キー属性で構成されます。ビュー行のキーに参加する必要があるのは最初のエンティティ・オブジェクトの主キーのみですが、デフォルトですべての主キーが使用されます。
一部のセカンダリ・エンティティ・オブジェクトの慣用名のキー属性をビュー行レベルのキー属性として残す場合は、ビュー行のキーの一部としてそのエンティティ・オブジェクトの主キーを構成するすべての属性を残しておく必要があります。N
個のエンティティ・オブジェクトの慣用名のうちのM
個について(M
<= N
)、ビュー行の1つ以上のキー属性を残した場合、findByKey()
を使用し、M
個のエンティティ・オブジェクトの慣用名の任意のサブセットに基づいて行を検索できます。Key
オブジェクトで値を指定するそれぞれのエンティティ・オブジェクトの慣用名について、そのエンティティの主キーのすべての属性にNULLではない値を指定する必要があります。
ビュー・オブジェクトが1つ以上のエンティティ・オブジェクトの慣用名に基づいている場合、そのfindByKey()
メソッドは、ビュー行キーでの属性がNULLではない最初のエンティティ・オブジェクトの慣用名に対応するエンティティ定義のfindByPrimaryKey()
メソッドに委譲することで行を検索するため、この規則に従う必要があります。エンティティ定義のfindByPrimaryKey()
メソッドでキャッシュ内のエンティティ行を検索するには、特定のエンティティ・オブジェクトに対するすべてのキー属性がNULL以外である必要があります。
具体的な例としては、ServiceRequests
ビュー・オブジェクトで、ServiceRequest
エンティティ・オブジェクトがそのプライマリ・エンティティ・オブジェクト慣用名で、Product
エンティティがセカンダリ参照エンティティ・オブジェクト慣用名である場合を考えてみてください。さらに、次の両方のビュー行属性のKey Attributeプロパティを、true
に設定したままにするものとします。
SvrId
: ServiceRequest
エンティティの主キー
ProdId1
: Product
エンティティの主キー
したがって、ビュー行のキーは、SvrId
とProdId1
の組合せになります。findByKey()
を呼び出すときは、次のような内容のKey
オブジェクトを渡すことができます。
基礎となるServiceRequest
エンティティの完全に指定されたキー
Key k = new Key(new Object[]{new Number(200), null});
基礎となるProduct
エンティティの完全に指定されたキー
Key k = new Key(new Object[]{null, new Number(118)});
両方のエンティティの完全に指定されたキー
Key k = new Key(new Object[]{new Number(200), new Number(118)});
有効な部分キーを指定すると、findByKey()
メソッドは、キー・オブジェクトで指定されていないエンティティ・オブジェクト慣用名の属性をワイルドカードとして扱い、結果として複数の行を返すことができます。
addDynamicAttribute()
メソッドを使用すると、実行時に1つまたは複数の動的属性をビュー・オブジェクトに追加できます。動的属性は、値として任意のオブジェクトを保持できます。通常は、グローバルで汎用的な方法でフレームワークに追加する機能を実装するために、行ごとに追加の一時的な状態を格納する必要がある、汎用のフレームワーク拡張コードを作成するときに、動的属性の使用を検討します。
通常はビュー・オブジェクトのデフォルトの行セットを使用しますが、ViewObject
インタフェースのcreateRowSet()
メソッドを呼び出して、同じビュー・オブジェクトの問合せに基づく行セットである、二次的な名前付きの行セットを作成できます。こうすることに意味のある状況の1つは、ビュー・オブジェクトのSQL問合せに名前付きバインド変数が含まれる場合です。各RowSet
オブジェクトはバインド変数値の独自のコピーを格納するので、単一のビュー・オブジェクトを使用して、異なる組合せのバインド変数値に基づく複数の行セットを生成および処理できます。作成した名前付き行セットは、findRowSet()
メソッドを使用して検索できます。セカンダリ行セットの使用が終了したら、closeRowSet()
メソッドを呼び出します。
RowSet
に対し、通常はデフォルトの行セット・イテレータを使用しますが、RowSet
インタフェースのcreateRowSetIterator()
メソッドを呼び出して、二次的な名前付きの行セット・イテレータを作成できます。作成した名前付き行セット・イテレータは、findRowSetIterator()
メソッドを使用して検索できます。セカンダリ行セット・イテレータの使用が終了したら、closeRowSetIterator()
メソッドを呼び出します。
注意: ADFモデルの宣言的データ・バインディング・レイヤーを通して、アプリケーションのユーザー・インタフェース・ページまたはパネルは、アプリケーション・モジュールのデータ・モデル内にあるビュー・オブジェクトのデフォルト行セットのデフォルト行セット・イテレータと連携します。このため、セカンダリ行セット・イテレータを作成する最も一般的なシナリオとは、ユーザー・インタフェース・レイヤーによって使用されるデフォルトの行セット・イテレータの現在行に影響を与えずに、ビュー・オブジェクトのデフォルト行セットに対して反復処理を行うビジネス・ロジックを作成することです。 |
デフォルトでは、ビュー・リンク・アクセッサの行セットを取得するたびに、ビュー・オブジェクトによって新しいRowSet
オブジェクトが作成され、ユーザーが行を操作できるようにします。これは、そのたびに問合せを再実行して結果を生成するという意味ではなく、RowSet
オブジェクトの新しいインスタンスを作成してデフォルトのイテレータを先頭行の前のスロットにリセットするだけです。行セットをデータベースの行で強制的に更新するには、executeQuery()
メソッドを呼び出します。
行セットの作成には若干のオーバーヘッドが伴うため、同じビュー・リンク・アクセッサ属性に対して大量の呼出しを行うコードの場合は、ビュー・リンクのリンク元ビュー・オブジェクトに対してビュー・リンク・アクセッサ行セットの保持を有効にすることを検討する余地があります。ビュー・リンク・アクセッサの保持機能を使用するには、ビュー・オブジェクトでカスタムJavaクラスを有効にし、create()
メソッドをオーバーライドして、パラメータとしてtrue
を渡してsetViewLinkAccessorRetained()
メソッドを呼び出す行を、super.create()
の後に追加します。これは、そのビュー・オブジェクトのすべてのビュー・リンク・アクセッサ属性に適用されます。
ビュー・オブジェクトに対してこの機能を有効にすると、ビュー・リンク・アクセッサの行セットが毎回再作成されないため、デフォルトの行セット・イテレータの現在行も維持されるという副作用があります。つまり、デフォルト行セット・イテレータの現在行をリセットして先頭行の前のスロットに戻すには、ビュー・リンク・アクセッサから取得した行セットに対して、reset()
メソッドを明示的に呼び出す必要があります。
ただし、アクセッサの保持を有効にすると、アクセッサ行セットの行を反復処理する前にいつでもreset()
を呼び出すようにしないと、微妙で検出が困難なエラーがアプリケーションで発生する可能性があることに注意してください。たとえば、ビュー・リンク・アクセッサの行セットの行を次のようにして反復処理し、合計を計算するものとします。
RowSet rs = (RowSet)row.getAttribute("ServiceRequestsForProduct"); while (rs.hasNext()) { Row r = rs.next(); // Do something important with attributes in each row }
アクセッサ行セットを最初に処理するときには、このコードは正しく動作します。しかし、行セット(およびそのデフォルトの行セット・イテレータ)が保持されるため、2回目以降の行セットへのアクセスでは、現在行はすでに行セットの末尾にあり、rs.hasNext()
がfalse
になるためwhileループはスキップされます。この機能を有効にする場合は、アクセッサの反復コードは次のように記述する必要があります。
RowSet rs = (RowSet)row.getAttribute("ServiceRequestsForProduct");
rs.reset(); // Reset default row set iterator to slot before first row!
while (rs.hasNext()) {
Row r = rs.next();
// Do something important with attributes in each row
}
ビュー・リンク一貫性が有効になっている場合は、アクセッサが保持されると、ポストされていない新しい行は行セットの最後に表示されます。これは、アクセッサが保持されない(デフォルト)場合と若干異なり、アクセッサが保持されないときは、ポストされていない新しい行は、アクセッサの行セットの先頭に表示されます。
ビュー・オブジェクトを使用すると、データの行の読取り、一時的データの行の作成と格納、およびエンド・ユーザーが基礎のビジネス・オブジェクトに対して行う挿入、更新、削除の自動的な調整を行うことができます。ビュー・オブジェクトを設計および使用する方法が、実行時のパフォーマンスに影響を与える場合があります。ここでは、可能なかぎり最高のパフォーマンスを得るためのビュー・オブジェクトの構成に関するガイダンスを提供します。
問合せのWHERE
句に実行のたびに変化する可能性のある値が含まれる場合は常に、名前付きバインド変数を使用する必要があります。また、名前付きバインド変数を使用することで、悪質なエンド・ユーザーによるSQLインジェクション攻撃からアプリケーションを保護することもできます。
バインド変数は、SQL文字列内のプレースホルダであり、SQL文字列自体のテキストを変更することなく、実行時に簡単に値を変更できます。問合せのテキストは実行ごとに変化しないので、データベースは同じ解析済の文を毎回効率よく再利用できます。文の再解析を避けることで、データベースでは問合せ最適化プランを絶えず再決定する必要が軽減され、この解析操作の間に使用される他の貴重なデータベース・リソースに対する複数のエンド・ユーザーによる競合を除去できます。これらの減少により、実行時のアプリケーションのパフォーマンスが向上します。名前付きバインド変数の使用方法の詳細は、5.9項「名前付きバインド変数の使用」を参照してください。
パラメータを使用するWHERE
句の値に対してバインド変数を使用することは、その値をアプリケーションのエンド・ユーザーが提供する場合に特に重要です。例27-2で示されている例について考えます。この例は、ユーザーが指定したパラメータ値を文に連結して構成された動的なWHERE
句を追加します。
例27-2 バインド変数のかわりに文字列の連結を使用することによる、SQLインジェクション攻撃に対する脆弱性
// EXAMPLE OF BAD PRACTICE, Do not follow this approach! String userSuppliedValue = ... ; yourViewObject.setWhereClause("BANK_ACCOUNT_ID = "+userSuppliedValue);
悪質な意図を持つユーザーは、アプリケーションの基礎にあるデータベース・スキーマの詳細を知ることができれば、慎重に構成された銀行口座番号を、次のようにフィールド値またはURLパラメータとして提供できます。
BANK_ACCOUNT_ID
例27-2のコードがこの値を動的に適用されるWHERE句に連結すると、データベースには次のような問合せ述語が示されます。
WHERE (BANK_ACCOUNT_ID = BANK_ACCOUNT_ID)
このWHERE
句では現在のユーザーの口座だけでなくすべての銀行口座が取得され、ハッカーは他人の口座に関する個人情報を見ることができます。悪質な目的で作成されたパラメータ値をSQL文に提供することでアプリケーションのWHERE
句を短絡するこのような手法のことを、SQLインジェクション攻撃と呼びます。このような場合は、かわりに例27-3で示すような名前付きバインド変数を使用することで、脆弱性を回避できます。
例27-3 文字列の連結にかわる名前付きバインド変数の使用
// Best practice using named bind variables String userSuppliedValue = ... ; yourViewObject.setWhereClause("BANK_ACCOUNT_ID = :BankAcccountId"); yourViewObject.defineNamedWhereClauseParam("BankAcccountId", null, null); yourViewObject.setNamedWhereClauseParam("BankAcccountId",userSuppliedValue);
この場合に悪質なユーザーが不正な値を提供すると、予想しないデータを取得するのではなく、アプリケーションが処理できるエラーを受け取ります。
ビュー・オブジェクトは、基礎になっているエンティティ・オブジェクトに関連付けることも、関連付けないこともできます。ビュー・オブジェクトが基礎になっているエンティティ・オブジェクトと関連付けられていると、新しい行の作成、および問合せで得られた行の変更や削除を行うことができます。ビュー・オブジェクトは基礎となるエンティティ・オブジェクトと協調して、ビジネス・ルールを適用し、変更を永続的に保存します。さらに、エンティティ・ベースのビュー・オブジェクトは、次のことを行います。
同じトランザクションの他のビュー・オブジェクトを介して行われた、関連するエンティティ・オブジェクト属性に対する保留中の変更を即時に反映します。
新しく作成された行の属性値を、基礎となるエンティティ・オブジェクト属性の値で初期化します。
外部キー属性値の変更時に更新された参照情報を反映します。
一方、エンティティ・オブジェクトに関連付けられていないビュー・オブジェクトは、読取り専用であり、エンティティから導出されるデフォルト値の取得、保留中の変更の反映、および更新された参照情報の反映は行いません。アプリケーションに必要な機能の種類を判断し、それに応じてビュー・オブジェクトを設計する必要があります。一般に、SQLベースの検証のため、およびドロップダウン・リストに有効な選択肢のリストを表示するために使用されるビュー・オブジェクトは、読取り専用でかまいません。
ビュー・オブジェクト行とエンティティ・オブジェクト行の間の調整には、実行時に若干のオーバーヘッドが生じるため、エンティティにマップされたビュー・オブジェクトに用意された機能を必要としない場合、関連するエンティティ・オブジェクトのない読取り専用ビュー・オブジェクトを使用すると、パフォーマンスが多少向上します。
ビュー・オブジェクトをエンティティにマップするかどうかを決定した後は、次に問合せ自体に注目します。ビュー・オブジェクト・エディタの「問合せ」パネルの「実行計画」ボタンを使用すると、データベースの問合せオプティマイザが使用する問合せ計画を見ることができます。計画が全表スキャンを行っている場合は、索引を追加するか、または「チューニング」パネルのオプティマイザ・ヒント・フィールドで値を指定して使用される問合せ計画を明示的に制御することを、検討する必要があります。これらの機能は、個別のビュー・オブジェクトSQL文について問合せ計画を評価するための便利なツールを開発者に提供します。ただし、これらのツールの使用は、本番環境のデータ量とエンド・ユーザー数においてパフォーマンスの悪い問合せを識別するための、アプリケーション全体のSQLのトレースにかわるものではありません。
OracleデータベースのSQLトレース機能を使用すると、アプリケーションが実行する全SQL文の完全なログが生成されます。すべてのバージョンのOracleデータベースで動作する方法としては、次のコマンドを発行します。
ALTER SESSION SET SQL_TRACE TRUE
このコマンドを実行すると、現在のデータベース・セッションのトレースが有効になり、ALTER SESSION SET SQL_TRACE FALSE
を入力するまで、または接続を閉じるまで、すべてのSQL文がサーバー側のトレース・ファイルに記録されます。このオプションの有効化を簡単にしてADFアプリケーションをトレースするには、例27-4で示されているように、アプリケーション・モジュール(またはカスタム・アプリケーション・モジュール・フレームワーク拡張クラス)のafterConnect()
メソッドをオーバーライドし、Javaシステム・プロパティが存在するかどうかに基づいて、条件付きでALTER SESSION
コマンドを実行してSQLトレースを有効にします。
例27-4 アプリケーション・モジュールでの条件付きでのSQLトレースの有効化
// In YourCustomApplicationModuleImpl.java protected void afterConnect() { super.afterConnect(); if (System.getProperty("enableTrace") != null) { getDBTransaction().executeCommand("ALTER SESSION SET SQL_TRACE TRUE"); } }
トレース・ファイルを生成した後は、データベースに付属するtkprof
ユーティリティを使用して情報を書式設定することで、次のように実行された各問合せに関する情報を理解しやすくなります。
(再)解析された回数
実行された回数
アプリケーション・サーバーとデータベースの間で行われたラウンドトリップの回数
問合せの実行時間に関する様々な定量的測定値
これらの手法を使用することで、アプリケーションが実行する特定の問合せの時間を短縮するために必要な追加索引や、問合せ最適化計画を改善するために変更できる問合せを判断できます。
注意: Oracle 10gデータベースが提供する新しいDBMS_MONITOR パッケージでは、SQLトレースがさらに簡単になっており、Oracle Enterprise Managerと統合してアプリケーションが最も頻繁に実行する問合せ文をビジュアルに監視できます。 |
ビュー・オブジェクト・エディタの「チューニング」パネルでは、問合せのパフォーマンスに劇的な影響を与える可能性のある様々なオプションを設定できます。
「データベースから取得」セクションでは、ビュー・オブジェクトがデータベース・サーバーから行を取得する方法を制御します。フェッチ・モードのオプションは、「すべての行」、「最大で1行」、および「なし」です。ほとんどのビュー・オブジェクトはデフォルトの「すべての行」オプションを使用し、「必要に応じて」または「同時」のいずれか選択されているオプションに従って取得されます。「必要に応じて」オプションを指定すると、ビュー・オブジェクトでのexecuteQuery()
操作は最初に、表示する最初のページに必要な数の行のみを取得します。この行数は、ビュー・オブジェクトの範囲サイズに基づいて設定されます。
WHERE
句が単一の行を取得すると予想されるビュー・オブジェクトの場合は、オプションを「最大で1行」に設定すると最善のパフォーマンスが得られます。このオプションを指定すると、ビュー・オブジェクトはユーザーがそれ以上の行を期待していないことを認識し、さらに行がある場合に通常行うテストをスキップします。最後に、新しい行を作成するためにのみビュー・オブジェクトを使用する場合は、オプションを「なし」に設定すると、問合せは実行されません。
フェッチ・サイズは、データベースへの各ラウンドトリップで返される行数を制御します。デフォルトでは、フレームワークは一度に1行のバッチで行をフェッチします。複数の行をフェッチしている場合は、この「バッチ」の値を設定することで効率がよくなります。
ただし、この値を大きくすると、クライアント側で必要なバッファが大きくなるため、独断で大きな値を設定しないでください。ユーザー・インタフェースで一度にN
行の結果を表示する場合は、データベースへの1回のラウンドトリップで各ページの結果を取得できるよう、フェッチ・サイズを少なくともN+1
に設定するのがよい方法です。
注意: 問合せで本当に1行のみフェッチするのでないかぎり、「チューニング」パネルの「バッチ」フィールドをデフォルトのフェッチ・サイズである1 のままにしておくことは、アプリケーション・サーバーとデータベースの間で必要以上に多くのラウンドトリップが発生するため、パフォーマンスにとってよい方法ではありません。各ビュー・オブジェクトのフェッチ・サイズに適した値を検討することを強くお薦めします。 |
「問合せオプティマイザ・ヒント」フィールドでは、Oracle問合せオプティマイザに対するオプションのヒントを指定して、使用される実行計画に影響を与えることができます。実行時には、指定したヒントが問合せのSELECT
キーワードの直後に追加され、特殊なコメント構文/*+
YOUR_HINT
*/
でラップされます。一般的な2つのオプティマイザ・ヒントを次に示します。
FIRST_ROWS
: できるかぎり早く最初の行を取得することを示します。
ALL_ROWS
: できるかぎり早くすべての行を取得することを示します。
他にも多くのオプティマイザ・ヒントがありますが、このマニュアルの範囲からは外れます。使用できるヒントの詳細は、Oracle 10gのデータベース・リファレンス・マニュアルを参照してください。
実行時にビュー・オブジェクトを作成する場合のオーバーヘッドについて理解しておくことが重要です。ビジネス要件でどうしても必要でないかぎり、行わないようにしてください。たとえば、アプリケーションで発行する問合せで、対象の表の名前が設計時にわかっていて、取得する列のリストも固定している場合は、設計時にビュー・オブジェクトを作成します。このようにすると、SQL文は適切にカプセル化され、開発中に簡単に説明およびチューニングでき、結果の行の構造とデータ型を検出するために実行時にオーバーヘッドをかけることがなくなります。
これに対し、実行時にApplicationModule
インタフェースでcreateViewObjectFromQueryStmt()
APIを使用すると、問合せはコードに埋もれてしまい、前もってSQLをチューニングすることが困難になり、ビュー・オブジェクトを作成するたびにパフォーマンスが犠牲になります。理論上、動的に作成されるビュー・オブジェクトのSQL問合せ文は実行ごとに異なるので、実行中に問合せ結果の形状を検出するために、余分なデータベース・ラウンドトリップが必要になります。問い合せる表の名前が実行時までわからない場合にのみ、問合せを動的に作成します。それ以外のほとんどの要件には、設計時に作成するビュー・オブジェクトと、固定のWHERE
句にバインド変数を設定すること、または実行時にWHERE
句(オプションのバインド変数を含む)を追加するランタイムAPIを組み合せることで対応できます。
ビュー・オブジェクトの結果をプログラムで反復処理するコードを作成することがよくあります。よくある状況は、問合せ結果の複数の行を処理して属性またはエンティティが有効かどうかを判定する必要があるカスタム検証コードです。このような場合、行セット内の各行を1回のみ読み取り、後方にスクロールしたり、後で行セットを再び反復処理したりする必要がない場合は、前方のみモードを使用することで、取得した行のキャッシュを避けることができます。前方のみモードを有効にするには、ビュー・オブジェクトでsetForwardOnly(true)
を呼び出します。
注意: 前方のみモードで読取り専用ビュー・オブジェクト(エンティティ・オブジェクトの慣用名がない)を使用し、フェッチ・サイズを適切にチューニングするのが、データをプログラムで読み取る最も効率のよい方法です。 |
また、行セットを後方にスクロールすることがなく、reset()
を呼び出してイテレータを最初の行に設定することがない場合は、データを挿入、更新、または削除するときにも、前方のみモードを使用することで行のキャッシュを避けることができます。前方のみモードは、範囲サイズが1
の場合にのみ機能します。
エンティティ・ベースのビュー・オブジェクトを定義するとき、WHERE
句とORDER BY
句はすべてを指定できますが、デフォルトでは、FROM
句とSELECT
リストは自動的に導出されます。FROM
句は関係するエンティティ・オブジェクトの慣用名に関連する表の名前から決定されますが、SELECT
リストは次のものが基になります。
関係するエンティティ・マップ属性の基礎となる列名
SQL計算属性のSQL式
問合せでSELECT
句またはFROM
句を完全に制御する必要がある場合は、エキスパート・モードを有効にできます。
エキスパート・モードを有効にするには、ビュー・オブジェクトの作成ウィザードまたはビュー・オブジェクト・エディタの「SQL文」パネルで「エキスパート・モード」を選択します。
エキスパート・モードを有効にすると、「SQL文」パネルの読取り専用の「生成されたSQL文」セクションが、完全に編集可能な「問合せ文」テキスト・ボックスになり、すべてのSQL文が表示されます。このテキスト・ボックスを使用して、SQL問合せのすべての部分を変更できます。
たとえば、図27-2は、SRDemoアプリケーションのServiceHistories
ビュー・オブジェクトに対するビュー・オブジェクト・エディタの「SQL文」ページを示しています。表示されているのは、エキスパート・モードで、PL/SQLファンクションcontext_pkg.app_user_name
を参照するエンティティ・ベースのビュー・オブジェクトであり、FROM句でUSERS表を結合して、technician
ロールまたはmanager
ロールではないエンド・ユーザーからの非表示のサービス履歴メモをフィルタ処理しています。
ビュー・オブジェクトと基礎になるエンティティ・オブジェクトの自動的な協調は、XMLコンポーネント定義に保存されている正確な属性マッピング・メタデータに依存します。この情報は、ビュー・オブジェクトの属性と、関係するエンティティ・オブジェクトの慣用名の対応する属性を関連付けます。この属性マッピング情報は、通常のエンティティ・ベースのビュー・オブジェクトについては完全に自動的に維持されます。しかし、ビュー・オブジェクトでエキスパート・モードを使用するときは、SELECT
リストに対して行う変更に注意する必要があります。これは、SQL問合せの中で属性マッピングに直接関係する部分です。エキスパート・モードでも、SELECT
リストに対して次のことを行うときは、JDeveloperが属性マッピング・メタデータの維持を引き続きある程度支援します。
列別名を変更しない式の並替え
JDeveloperによって、対応するビュー・オブジェクト属性の並替えおよび属性マッピングの維持が行われます。
新しい式の追加
JDeveloperによって、新しい式の列別名に基づいた対応名(頭文字が大文字表記)を持つ新しいSQL計算済ビュー・オブジェクト属性が追加されます。
式の削除
JDeveloperによって、その式に関連している対応するSQL計算済属性またはエンティティ・マップ済属性が一時属性に変換されます。
ただし、SELECT
リストで列別名を変更した場合は、JDeveloperにはそれを検出する方法がなく、古い列式を削除して別の名前で新しい列式を追加したかのように扱われます。
問合せのSELECT
リストを変更した後は、「属性マッピング」パネルで、属性マッピング・メタデータが正しいことを確認してください。このパネルの表は、標準モードではビュー・オブジェクトに対しては無効になっていますが、エキスパート・モードのビュー・オブジェクトに対しては有効です。各ビュー・オブジェクト属性について、対応するSQL列別名を表で確認できます。「ビュー属性」列のセルをクリックすると、ドロップダウン・リストが表示され、エンティティ・マップ・ビュー属性が対応する必要のある適切なエンティティ・オブジェクト属性を選択できます。
注意: ビュー属性がSQL計算済属性または一時属性の場合は、そのことを示すSQLアイコンの付いた対応する属性が「ビュー属性」列に表示されます。これらの属性は基礎となるエンティティ・オブジェクトと関連していないため、エンティティ属性関連情報は必要ありません。 |
ビュー・オブジェクトに対するエキスパート・モードを無効にすると、SELECT
句とFROM
句は再び導出された状態に戻ります。その際、SQL文に対するカスタム編集が失われることを伝える警告が表示されます。失われてもかまわない場合は、この警告を確認すると、オブジェクトのSQL問合せがデフォルトに戻ります。
Products
ビュー・オブジェクトで、SQL式がSUBSTR(NAME,1,10)
と定義されたShortens
という名前のSQL計算属性について考えます。このビュー・オブジェクトをエキスパート・モードに切り替えると、「問合せ文」ボックスに次のようなSQL問合せが表示されます。
SELECT Products.PROD_ID, Products.NAME, Products.IMAGE, Products.DESCRIPTION, SUBSTR(NAME,1,10) AS SHORT_NAME FROM PRODUCTS Products
Shortens
属性の属性定義に戻り、「SQL式」フィールドをSUBSTR(NAME,1,10)
からSUBSTR(NAME,1,15)
に変更すると、ビュー・オブジェクトのXMLコンポーネント定義に変更が保存されます。ただし、SQL問合せは前記のままであることに注意してください。これは、JDeveloperではエキスパート・モードの問合せのテキストの変更が試行されないためです。エキスパート・モードでは、開発者がすべてを制御します。JDeveloperは、開発者がエキスパート・モードのSQL文に対して行うある種の変更に関連して、前述のメタデータの調節を試みますが、逆の処理は行いません。したがって、ビュー・オブジェクト・メタデータを変更しても、エキスパート・モードのSQL文はそれを反映するために更新されません。
前述の変更をSQL計算済属性Shortens
に対して行うには、エキスパート・モードのSQL文自体で式を更新する必要があります。完全に行うには、属性メタデータとエキスパート・モードSQL文の両方で変更する必要があります。このようにすると、後でエキスパート・モードを無効に切り替えても、自動的に導出されるSELECTリストに正しいSQL導出式が含まれることが保証されます。
注意: エキスパート・モードのビュー・オブジェクトのビュー・オブジェクト・メタデータを大量に変更する必要がある場合は、次の手法を使用すると、変更がSQL文に対して及ぼす影響を手作業で変換する必要がありません。最初に、カスタマイズした問合せのテキストを一時ファイルにコピーします。次に、ビュー・オブジェクトのエキスパート・モードを無効にし、変更が失われることを伝える警告を確認します。この時点で、JDeveloperは新しく行われたメタデータの変更に基づいて、正しく生成されたSQL文を再び導出します。最後に、再度エキスパート・モードを有効にして、SQLのカスタマイズを再適用します。 |
エンティティ・マップ済属性に対応するSELECT
リスト式を変更するときは、データを取得するときに属性の値を変更するSQL計算を導入しないでください。これを行った場合の問題を説明するため、Products
という名前の簡単なエンティティ・ベースのビュー・オブジェクトに対する次のような問合せについて考えます。
SELECT Products.PROD_ID, Products.NAME, Products.IMAGE, Products.DESCRIPTION FROM PRODUCTS Products
ある種のユースケースに対しては名前の先頭10文字のみを表示するよう、名前列を制限するものとします。これを行う正しい方法は、ShortName
という名前でSUBSTR(Products.NAME,1,10)
のような式の新しいSQL計算フィールドを導入することです。しかし、考えられるもう1つの方法として、ビュー・オブジェクトをエキスパート・モードに切り替えて、エンティティ・マップされたNAME列のSELECT
リストの式を、次のように変更することもできます。
SELECT Products.PROD_ID, SUBSTR(Products.NAME,1,10) AS NAME, Products.IMAGE, Products.DESCRIPTION FROM PRODUCTS Products
この代替方法は、最初はうまくいくように見えます。実行時には、名前の値が意図したとおりに切り捨てられます。しかし、行を変更すると、基礎となるエンティティ・オブジェクトは、行をロックしようとして次の処理を行います。
SELECT FOR UPDATE
文を発行し、行をロックしようとしてすべての列を取得します。
エンティティ・オブジェクトは、行のロックに成功すると、データベースから最後に取得されてエンティティ・キャッシュに格納されているすべての一時属性の元の値と、ロック操作の間にデータベースから取得した属性の値を比較します。
いずれかの値が異なっていると、次のエラーがスローされます。
(oracle.jbo.RowInconsistentException) JBO-25014: Another user has changed the row with primary key [...]
システムをテストしているユーザーが1人のみでもこのようなエラーが発生する場合は、エキスパート・モードのビュー・オブジェクトでエンティティ・マップされた属性の選択値を変更したSQL機能を導入したためであると考えられます。前の例では、導入したSUBSTR(Products.NAME,1,10)
機能は、Name
属性の選択された元の値を切り捨てます。行ロックSQL文は、NAME
列の値を選択するときに、値全体を選択します。これにより前述の比較が失敗し、別のユーザーが行を変更した場合のようなエラーが発生します。
エキスパート・モードでSQL機能を適用し、エンティティ・マップ済属性に対して取得された値を切り捨てたり変更したりすると、NUMBER
値やDATE
値の属性でも同じことが発生します。エンティティ・マップ済データの変更されたバージョンを表示する必要がある場合は、適切な式を含む新しいSQL計算属性を導入して処理を行います。
ビュー・オブジェクトをエキスパート・モードに切り替えると、そのXMLコンポーネント定義は、別のXML属性に問合せの一部を格納する状態から、問合せ全体をSQLQuery要素に保存する状態に切り替わります。問合せはXML CDATAセクションにラップされて、複雑な問合せを理解しやすくするために開発者が行った可能性のある行の書式設定を保持します。
エキスパート・モードのビュー・オブジェクトには次のような場合があります。
「問合せ句」パネルの「ORDER BY」フィールドで指定されている設計時のORDER BY
句を含む場合
setWhereClause()
またはsetOrderByClause()
を使用して実行時に適用される動的なWHERE句またはORDER BY句を含む場合
このような場合、問合せは句を適用する前にインライン・ビューにネストされます。たとえば、エキスパート・モードの問合せが次のように定義されているものとします。
select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from USERS union all select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from INACTIVE_USERS
実行時に、email = :TheUserEmail
のような追加のWHERE
句を設定すると、ビュー・オブジェクトは元の問合せを次のようにインライン・ビューにネストします。
SELECT * FROM( select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from USERS union all select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from INACTIVE_USERS) QRSLT
そして、動的なWHERE句の条件が最後に追加され、最終的な問合せは次のようになります。
SELECT * FROM( select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from USERS union all select USER_ID, EMAIL, FIRST_NAME, LAST_NAME from INACTIVE_USERS) QRSLT WHERE email = :TheUserEmail
元の問合せが複雑で、SLQのUNION
、INTERSECT
、MINUS
または複数の問合せを単一の結果に結合する他の演算子を含んでいる可能性があるため、エキスパート・モードの問合せの場合は一般にこのような問合せのラッピングが必要です。このような場合、実行時に追加のWHERE
句を問合せテキストの最後に単純に付加したのでは、予想外の結果が生成される可能性があります。たとえば、UNION
で結合された複数の文の最後のものに対してのみ適用される可能性があります。元の問合せをそのままインライン・ビューにネストすることで、ビュー・オブジェクトは、元の問合せがどれほど複雑であっても、元の問合せの結果をフィルタ処理するために追加のWHERE
句が正しく使用されることを保証します。
エキスパート・モードのビュー・オブジェクトはインライン・ビュー・ラッピングされるので、動的に追加されるWHERE
句は、元の問合せのSELECT
リストの列のみを参照できます。このような制限を回避する必要がある場合は、setNestedSelectForFullSql(false)
を呼び出すことで、インライン・ビュー・ラッピングの使用を無効にできます。
次のものをすでに作成した後で、問合せをエキスパート・モードに変更するものとします。
それを含むビュー・リンク
それを拡張する他のビュー・オブジェクト
このような場合は、図27-3の警告が表示され、これらの依存コンポーネントでSQL文がまだ正しい問合せを反映していることを確認するよう示されます。
たとえば、SRDemoアプリケーションのServiceRequests
ビュー・オブジェクトをエキスパート・モードを使用するように変更する場合は、ServiceRequestsByStatus
ビュー・オブジェクトがそれを拡張しているため、変更された親コンポーネントの拡張がまだ問合せに論理的に反映されていることを、拡張されたコンポーネントで確認する必要があります。
複数の名前付きビュー基準を定義し、必要に応じて実行時にその任意の組合せをビュー・オブジェクトに選択的に適用できます。
注意: この項の例では、AdAdvancedViewObjectExamples ワークスペースのMultipleViewCriterias プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。 |
名前付きビュー基準を定義するには、ビュー・オブジェクトのカスタムJavaクラスでcreate()
メソッドをオーバーライドし、putViewCriteria()
メソッドを呼び出して、名前付きViewCriteria
オブジェクトを定義します。
たとえば、SRDemoスキーマのUSERS
表に基づくUsers
ビュー・オブジェクトがあるとすると、create()
メソッドを例27-5で示すようにオーバーライドし、適切なヘルパー・メソッドを呼び出すことで、CountryIsUS
、CountryIsNotUS
、IsStaff
、およびIsCustomer
という名前の名前付きビュー基準を定義できます。
例27-5 オーバーライドされたcreate()メソッドでの複数の名前付きビュー基準の定義
package devguide.advanced.multiplevc; // Imports omitted public class UsersImpl extends ViewObjectImpl implements Users { // etc. protected void create() { super.create(); defineCountryIsUSCriteria(); defineCountryIsNotUSCriteria(); defineIsStaffCriteria(); defineIsCustomerCriteria(); } private void defineCountryIsUSCriteria() { ViewCriteria vc = createViewCriteria(); ViewCriteriaRow vcr = vc.createViewCriteriaRow(); vcr.setAttribute("CountryId","US"); vc.add(vcr); putViewCriteria("CountryIsUS",vc); } private void defineCountryIsNotUSCriteria() { ViewCriteria vc = createViewCriteria(); ViewCriteriaRow vcr = vc.createViewCriteriaRow(); vcr.setAttribute("CountryId","US"); vcr.setConjunction(ViewCriteriaRow.VCROW_CONJ_NOT); vc.add(vcr); putViewCriteria("CountryIsNotUS",vc); } private void defineIsStaffCriteria() { ViewCriteria vc = createViewCriteria(); ViewCriteriaRow vcr = vc.createViewCriteriaRow(); vcr.setAttribute("UserRole","IN ('technician','manager')"); vc.add(vcr); putViewCriteria("IsStaff",vc); } private void defineIsCustomerCriteria() { ViewCriteria vc = createViewCriteria(); ViewCriteriaRow vcr = vc.createViewCriteriaRow(); vcr.setAttribute("UserRole","user"); vc.add(vcr); putViewCriteria("IsCustomer",vc); } // etc. }
名前付きビュー基準を適用するには、setApplyViewCriteriaNames()
メソッドを使用します。このメソッドは、適用する基準の名前のString配列を受け取ります。複数の名前付き基準を適用する場合は、実行時に生成されるWHERE句の中でAND
により結合されます。その後、ビュー・オブジェクトのクライアント・インタフェースでカスタム・メソッドを公開し、適用する名前付きビュー基準の組合せをカプセル化できます。たとえば、例27-6で示されているカスタム・メソッドshowStaffInUS()
、showCustomersOutsideUS()
、およびshowCustomersInUS()
は、それぞれがsetApplyViewCriteriaNames()
メソッドを使用して、名前付きビュー基準の適切な組合せを適用しています。これらのメソッドをビュー・オブジェクトのクライアント・インタフェースで公開すると、実行時に、クライアントは必要に応じてこれらのメソッドを呼び出し、ビュー・オブジェクトによって表示される情報を変更できます。
例27-6 適切な名前付き基準を有効にするためのクライアント・メソッドの公開
// In UsersImpl.java public void showStaffInUS() { setApplyViewCriteriaNames(new String[]{"CountryIsUS","IsStaff"}); executeQuery(); } public void showCustomersOutsideUS() { setApplyViewCriteriaNames(new String[]{"CountryIsNotUS","IsCustomer"}); executeQuery(); } public void showCustomersInUS() { setApplyViewCriteriaNames(new String[]{"CountryIsUS","IsCustomer"}); executeQuery(); }
現在適用されている名前付きビュー基準を除去するには、setApplyViewCriteriaNames(null)
を使用します。たとえば、例27-7のshowAll()
メソッドをUsers
ビュー・オブジェクトに追加し、クライアント・インタフェースで公開できます。このようにすると、クライアントは必要に応じてフィルタ処理されないデータのビューに戻すことができます。
例27-7 適用されたすべての名前付きビュー基準の除去
// In UsersImpl.java public void showAll() { setApplyViewCriteriaNames(null); executeQuery(); }
注意: setApplyViewCriterias(null) は適用されているすべてのビュー基準を除去しますが、後でビュー基準の任意の組合せを再度適用できます。これに対し、clearViewCriterias() メソッドはすべての名前付きビュー基準を削除します。clearViewCriterias() を呼び出した後、ビュー基準を適用するには、まず再びputViewCriteria() を使用して新しい名前付き基準を定義する必要があります。 |
例27-8は、前述のUsers
ビュー・オブジェクトを使用するTestClient
クラスの行を示しています。ここでは、Users
ビュー・オブジェクト・インタフェースで異なるクライアント・メソッドを呼び出し、異なるフィルタ処理が行われたデータのセットを表示しています。showRows()
メソッドは、ビュー・オブジェクトの行を反復処理して属性を表示するヘルパー・メソッドです。
例27-8 名前付きビュー基準を使用するテスト・クライアントのコード
// In TestClientMultipleViewCriterias.java Users vo = (Users)am.findViewObject("Users"); vo.showCustomersOutsideUS(); showRows(vo,"After applying view criterias for customers outside US"); vo.showStaffInUS(); showRows(vo,"After applying view criterias for staff in US"); vo.showCustomersInUS(); showRows(vo,"After applying view criterias for customers in US"); vo.showAll(); showRows(vo,"After clearing all view criterias");
TestClient
プログラムを実行すると、次のような出力が生成されます。
--- After applying view criterias for customers outside US --- Hermann Baer [user, DE] John Chen [user, TH] : --- After applying view criterias for staff in US --- David Austin [technician, US] Bruce Ernst [technician, US] : --- After applying view criterias for customers in US --- Shelli Baida [user, US] Emerson Clabe [user, US] : --- After clearing all view criterias --- David Austin [technician, US] Hermann Baer [user, DE] :
デフォルトでは、ビュー・オブジェクトはデータベースに対して問合せを実行し、結果の行セットに行を取得します。ただし、ビュー・オブジェクトを使用して、メモリー内で検索とソートを実行し、データベースへの不必要なトリップを避けることもできます。
注意: この項の例では、AdAdAdvancedViewObjectExamples ワークスペースのInMemoryOperations プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。例では、oracle.jbo パッケージのインタフェースのメソッドを使用した、クライアント側からのメモリー内のソート処理とフィルタ処理の機能が示されています。同じ機能を、アプリケーション・モジュールまたはビュー・オブジェクト・コンポーネントのカスタム・メソッドの内部にカプセル化し、それぞれのクライアント・インタフェースで公開することができますが、通常はそのようにする必要があります。 |
ビュー・オブジェクトの問合せモードは、行を取得して行セットに移入するために使用されるソースを制御します。setQueryMode()
を使用すると、使用するモードまたはモードの組合せを制御できます。
ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES
データベースから結果を取得するデフォルトのモードです。
ViewObject.QUERY_MODE_SCAN_VIEW_ROWS
すでに行セットに存在する行をソースとして使用し、メモリー内フィルタ処理をとおして行セットの内容を段階的に調整できます。
ViewObject.QUERY_MODE_SCAN_ENTITY_ROWS
エンティティ・ベースのビュー・オブジェクトに対してのみ有効なモードで、エンティティ・キャッシュに現在存在するエンティティ行をソースとして使用し、キャッシュの内容に基づいて結果を生成します。
モードを個別に使用することも、Javaの論理OR
演算子(X
|
Y
)を使用して組み合せることもできます。たとえば、エンティティ・キャッシュでポストされていない新しいエンティティ行を問い合せ、同時にデータベースで既存の行を問い合せるビュー・オブジェクトを作成するには、次のようなコードを使用します。
setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES | ViewObject.QUERY_MODE_SCAN_ENTITY_ROWS)
問合せモードを組み合せると、ビュー・オブジェクトは重複する行のスキップを自動的に処理します。さらに、検出された結果には暗黙の順序があります。
スキャン・ビュー行(指定されていた場合)
スキャン・エンティティ・キャッシュ(指定されていた場合)
SQL問合せの発行によるスキャン・データベース表(指定されていた場合)
setQueryMode()
メソッドを呼び出して問合せモードを変更した場合、新しい設定は次にexecuteQuery()
メソッドを呼び出すと有効になります。
実行時にビュー・オブジェクトの行をソートするには、setSortBy()
メソッドを使用します。SQLのORDER BY句のようなソート式を渡します。ただし、表の列名を参照するかわりに、ビュー・オブジェクトの属性名を使用します。たとえば、DaysOpen
およびCreatedByUser
という名前の属性を含むビュー・オブジェクトの場合、次のメソッドを呼び出すことで、最初にDaysOpen
の降順に、次にCreatedByUser
で、ビュー・オブジェクトをソートできます。
setSortBy("DaysOpen desc, CreatedByUser");
または、次のように、ソート処理句の中ではゼロから始まる属性インデックス位置を使用することもできます。
setSortBy("2 desc, 3");
setSortBy()
メソッドを呼び出した後、次にexecuteQuery()
メソッドを呼び出すと行がソートされます。ビュー・オブジェクトは、このソート句を適切な書式に変換し、ビュー・オブジェクトの問合せモードに基づいた行の並べ替えに使用します。デフォルトの問合せモードを使用すると、SortBy
句は適切なORDER BY
句に変換され、データベースに送信されるSQL文の一部として使用されます。いずれかのメモリー内問合せモードの場合は、SortBy
句は1つまたは複数のSortCriteria
オブジェクトに変換され、メモリー内ソートの実行で使用されます。
注意: SQLのORDER BY式では列名の大文字と小文字は区別されませんが、SortBy 式の属性名では大文字と小文字が区別されます。 |
例27-9は、読取り専用のビュー・オブジェクトResolvedServiceRequests
によって生成された行に対してメモリー内ソートを実行するために、setSortBy()
とsetQueryMode()
を使用するTestClientSetSortBy
クラスの興味深いコード行を示しています。
例27-9 メモリー内ソートでのsetSortByとsetQueryModeの組合せ
// In TestClientSetSortBy.java am.getTransaction().executeCommand("ALTER SESSION SET SQL_TRACE TRUE"); ViewObject vo = am.findViewObject("ResolvedServiceRequests"); vo.executeQuery(); showRows(vo,"Initial database results"); vo.setSortBy("DaysOpen desc"); vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS); vo.executeQuery(); showRows(vo,"After in-memory sorting by DaysOpen desc"); vo.setSortBy("DaysOpen desc, CreatedByUser"); vo.executeQuery(); showRows(vo,"After in-memory sorting by DaysOpen desc, CreatedByUser");
この例を実行すると、次のような結果が生成されます。
--- Initial database results --- 106,Ice machine not working,1,mhartste 103,Washing machine leaks,4,ngreenbe 105,Air in dryer not hot,4,jmurman 109,Freezer is not cold,4,jwhalen : --- After in-memory sorting by DaysOpen desc --- 100,I have noticed that every time I do a...,9,dfaviet 101,Agitator does not work,8,sbaida 103,Washing machine leaks,4,ngreenbe 105,Air in dryer not hot,4,jmurman : --- After in-memory sorting by DaysOpen desc, CreatedByUser --- 100,I have noticed that every time I do a...,9,dfaviet 101,Agitator does not work,8,sbaida 105,Air in dryer not hot,4,jmurman 109,Freezer is not cold,4,jwhalen :
executeCommand()
の呼出しを含む例27-9の1行目では、ALTER SESSION SET SQL TRACE
コマンドを発行して、現在のデータベース・セッションでSQLトレースを有効にしています。これにより、Oracleデータベースは、実行されるすべてのSQL文をサーバー側のトレース・ファイルに記録します。データベースが文を解析した回数や、問合せ結果を取得する間に行のバッチをフェッチするためにクライアントが行ったラウンドトリップの回数など、各SQL文のテキストに関する情報が記録されます。
注意: ALTER SESSION コマンドを実行してSQL出力のトレースを行うには、DBAがSRDEMO アカウントに権限を付与することが必要になる場合があります。 |
生成されたトレース・ファイルは、データベースに付属するtkprof
ユーティリティを使用して書式設定できます。
tkprof xe_ora_3916.trc trace.prf
このユーティリティは、ResolvedServiceRequests
ビュー・オブジェクトによって実行されたSQL文に関する、例27-10で示されているような興味深い情報を含むtrace.prf
ファイルを生成します。最初に1回の実行で6行のデータを問い合せてデータベースからフェッチした後、その後に行われたこれらの結果に対する2回のソートでは実行が行われていないことがわかります。コードで問合せモードがViewObject.QUERY_MODE_SCAN_VIEW_ROWS
に設定されているため、setSortBy()
の後のexecuteQuery()
ではメモリー内でソートが実行されました。
例27-10 メモリー内でソートが行われたことを示すトレースファイルのTKPROF出力
************************************************************* SELECT * FROM (select sr.svr_id, case when length(sr.problem_description) > 37 then rtrim(substr(sr.problem_description,1,37))||'...' else sr.problem_description end as problem_description, ceil( (select trunc(max(svh_date)) from service_histories where svr_id = sr.svr_id) - trunc(request_date) ) as days_open, u.email as created_by_user from service_requests sr, users u where sr.created_by = u.user_id and status = 'Closed') QRSLT ORDER BY days_open call count cpu elapsed disk query current rows ------- ----- ------ -------- ---- ------ -------- ------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 1 0.00 0.00 0 22 0 6 ------- ----- ------ -------- ---- ------ -------- ------- total 3 0.00 0.00 0 22 0 6 *************************************************************
メモリー内での行のソート方法をカスタマイズする必要がある場合は、次の2つの拡張ポイントがあります。
次のメソッドをオーバーライドできます。
public void sortRows(Row[] rows)
このメソッドは、行のメモリー内ソート処理を実際に行います。このメソッドをオーバーライドすることで、必要に応じてかわりのソート方法を組み込むことができます。
次のメソッドをオーバーライドできます。
public Comparator getRowComparator()
このメソッドのデフォルトの実装はoracle.jbo.RowComparator
を返します。RowComparator
はcompareTo()
メソッドを呼び出して、2つのデータ値を比較します。これらのメソッドとオブジェクトをオーバーライドし、カスタム比較ルーチンを提供できます。
ViewCriteria
を使用して行セットの内容をフィルタするために、次のメソッドを呼び出すことができます。
applyViewCriteria()
またはsetApplyViewCriteriaNames()
を呼び出してからexecuteQuery()
を呼び出すと、フィルタ処理された新しい行セットが生成されます。
findByViewCriteria()
を呼び出すと、元の行セットの内容を変更することなく、プログラムで処理するための新しい行セットが取得されます。
これらの方法はどちらも、ビュー基準モードに応じて、データベースに対して、またはメモリー内フィルタ処理を実行するために、あるいはその両方に使用できます。基準モードは、ViewCriteria
オブジェクトでsetCriteriaMode()
メソッドを使用して設定します。このメソッドには、次の整数フラグのどちらか一方、または両方の論理OR
を渡すことができます。
ViewCriteria.CRITERIA_MODE_QUERY
ViewCriteria.CRITERIA_MODE_CACHE
メモリー内フィルタ処理に使用する場合、ViewCriteria
がサポートする演算子は、=
、>
、<
、<=
、>=
、<>
およびLIKE
です。
例27-11は、前述のデータベースに対するものとメモリー内の2つの機能を使用するTestClientFindByViewCriteria
クラスの興味深い行を示しています。この例は、CustomerList
ビュー・オブジェクトのインスタンスを使用し、次の基本的な手順を実行します。
データベースで姓がCで始まる顧客を問い合せて、次のような出力を生成します。
--- Initial database results with applied view criteria --- John Chen Emerson Clabe Karen Colmenares
メモリー内で、手順1の結果から、名がJで始まる顧客のみのサブセットを作成します。そのために、ビュー基準に2番目のビュー基準行を追加し、ANDを使用する論理積を設定しています。次のような出力が生成されます。
--- After augmenting view criteria and applying in-memory --- John Chen
結合をOR
に戻し、基準を再びデータベースに適用して、姓がJ%または名がC%の顧客を問い合せます。次のような出力が生成されます。
--- After changing view criteria and applying to database again --- John Chen Jose Manuel Urman Emerson Clabe Karen Colmenares Jennifer Whalen
姓または名に文字oを含む顧客をメモリー内で検索する新しい基準を定義します。
サブセットを作成するのではなく、findByViewCriteria()
を使用して新しい行セットを作成し、出力を生成します。
--- Rows returned from in-memory findByViewCriteria --- John Chen Jose Manuel Urman Emerson Clabe Karen Colmenares
findByViewCriteria()
を使用しても元の行セットが変化していないことを示し、出力を生成します。
--- Note findByViewCriteria didn't change rows in the view --- John Chen Jose Manuel Urman Emerson Clabe Karen Colmenares Jennifer Whalen
例27-11 ビュー基準によるデータベースおよびメモリー内でのフィルタ処理の実行
// In TestClientFindByViewCriteria.java ViewObject vo = am.findViewObject("CustomerList"); // 1. Show customers with a last name starting with a 'C' ViewCriteria vc = vo.createViewCriteria(); ViewCriteriaRow vcr1 = vc.createViewCriteriaRow(); vcr1.setAttribute("LastName","LIKE 'C%'"); vo.applyViewCriteria(vc); vo.executeQuery(); vc.add(vcr1); vo.executeQuery(); showRows(vo, "Initial database results with applied view criteria"); // 2. Subset results in memory to those with first name starting with 'J' vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS); ViewCriteriaRow vcr2 = vc.createViewCriteriaRow(); vcr2.setAttribute("FirstName","LIKE 'J%'"); vcr2.setConjunction(ViewCriteriaRow.VCROW_CONJ_AND); vc.setCriteriaMode(ViewCriteria.CRITERIA_MODE_CACHE); vc.add(vcr2); vo.executeQuery(); showRows(vo,"After augmenting view criteria and applying in-memory"); // 3. Set conjuction back to OR and re-apply to database query to find // customers with last name like 'J%' or first name like 'C%' vc.setCriteriaMode(ViewCriteria.CRITERIA_MODE_QUERY); vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES); vcr2.setConjunction(ViewCriteriaRow.VCROW_CONJ_OR); vo.executeQuery(); showRows(vo,"After changing view criteria and applying to database again"); // 4. Define new critera to find customers with first or last name like '%o%' ViewCriteria nameContainsO = vo.createViewCriteria(); ViewCriteriaRow lastContainsO = nameContainsO.createViewCriteriaRow(); lastContainsO.setAttribute("LastName","LIKE '%o%'"); ViewCriteriaRow firstContainsO = nameContainsO.createViewCriteriaRow(); firstContainsO.setAttribute("FirstName","LIKE '%o%'"); nameContainsO.add(firstContainsO); nameContainsO.add(lastContainsO); // 5. Use findByViewCriteria() to produce new rowset instead of subsetting nameContainsO.setCriteriaMode(ViewCriteria.CRITERIA_MODE_CACHE); RowSet rs = (RowSet)vo.findByViewCriteria(nameContainsO, -1,ViewObject.QUERY_MODE_SCAN_VIEW_ROWS); showRows(rs,"Rows returned from in-memory findByViewCriteria"); // 6. Show that original rowset hasn't changed showRows(vo,"Note findByViewCriteria didn't change rows in the view");
RowMatch
オブジェクトは、メモリー内フィルタ処理の基準を表現するためのさらに便利な方法を提供します。RowMatch
オブジェクトを作成するには、次のように、問合せ条件式をコンストラクタに渡します。
RowMatch rm = new RowMatch("LastName = 'Popp' or (FirstName like 'L%' and LastName like 'D%')");
SortBy
句と同じように、ビュー・オブジェクトの属性名に関するRowMatch
の式を、サポートされる=
、>
、<
、<=
、>=
、<>
およびLIKE
などの演算子を使用して表現します。カッコを使用して副次式をグループ化でき、副次式の間にはand
とor
の演算子を使用できます。
注意: SQL問合せ条件では列名の大文字と小文字は区別されませんが、RowMatch 式の属性名では大文字と小文字が区別されます。 |
ビュー・オブジェクトにRowMatch
を適用するには、setRowMatch()
メソッドを呼び出します。ViewCriteria
とは異なり、RowMatch
はメモリー内フィルタ処理にのみ使用するため、設定する一致モードはありません。サポートされるすべての問合せモードのビュー・オブジェクトに対してRowMatch
を使用でき、適用した結果は、次にexecuteQuery()
メソッドを呼び出したときに表示されます。
ビュー・オブジェクトにRowMatch
を適用するとき、RowMatch
の式では、SQL文で使用するものと同じ:VarName
表記法を使用して、ビュー・オブジェクトの名前付きバインド変数を参照できます。たとえば、ビュー・オブジェクトにStatusCode
という名前の名前付きバインド変数がある場合は、次の式でRowMatchを適用できます。
Status = :StatusCode or :StatusCode = '%'
例27-12は、活動中のRowMatch
を示すTestClientRowMatch
クラスの興味深い行を示しています。例で使用されているCustomerListビュー・オブジェクトには、Selected
という名前の一時Boolean
属性があります。このコードが実行する基本的な手順は次のとおりです。
顧客の完全なリストを問い合せて、出力を生成します。
--- Initial database results --- Neena Kochhar [null] Lex De Haan [null] Nancy Greenberg [null] :
奇数行のSelected
属性にBoolean.TRUE
を設定し、奇数番号の行を選択済としてマークします。出力を生成します。
--- After marking odd rows selected --- Neena Kochhar [null] Lex De Haan [true] Nancy Greenberg [null] Daniel Faviet [true] John Chen [null] Ismael Sciarra [true] :
RowMatch
を使用して、選択されている行、つまりSelected = true
の行のみを含む行セットのサブセットを作成します。次のような出力が生成されます。
--- After in-memory filtering on only selected rows --- Lex De Haan [true] Daniel Faviet [true] Ismael Sciarra [true] Luis Popp [true] :
より複雑なRowMatch
式を使用して、行セットのサブセットをさらに絞り込みます。出力を生成します。
--- After in-memory filtering with more complex expression --- Lex De Haan [true] Luis Popp [true]
例27-12 RowMatchによるメモリー内フィルタ処理の実行
// In TestClientRowMatch.java // 1. Query the full customer list ViewObject vo = am.findViewObject("CustomerList"); vo.executeQuery(); showRows(vo,"Initial database results"); // 2. Mark odd-numbered rows selected by setting Selected = Boolean.TRUE markOddRowsAsSelected(vo); showRows(vo,"After marking odd rows selected"); // 3. Use a RowMatch to subset row set to only those with Selected = true RowMatch rm = new RowMatch("Selected = true"); vo.setRowMatch(rm); vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_VIEW_ROWS); vo.executeQuery(); showRows(vo, "After in-memory filtering on only selected rows"); // 4. Further subset rowset using more complicated RowMatch expression rm = new RowMatch("LastName = 'Popp' "+ "or (FirstName like 'L%' and LastName like 'D%')"); vo.setRowMatch(rm); vo.executeQuery(); showRows(vo,"After in-memory filtering with more complex expression"); // 5. Remove RowMatch, set query mode back to database, requery to see full list vo.setRowMatch(null); vo.setQueryMode(ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES); vo.executeQuery(); showRows(vo,"After re-querying to see a full list again");
RowMatch
を使用して行セットをフィルタするだけでなく、rowQualifies()メソッドを使用すると、それがカプセル化している基準に個別の行が一致するかどうかをテストすることもできます。たとえば、次のように入力します。
RowMatch rowMatch = new RowMatch("CountryId = 'US'"); if (rowMatch.rowQualifies(row)) { System.out.println("Customer is from the United States "); }
RowMatch
を適用すると、ビュー・オブジェクトの問合せモードがデータベースからの行の取得に設定されている場合、executeQuery()
を呼び出すと、フェッチされた行にRowMatch
が適用されます。フェッチされた行が認定されない場合、その行は行セットに追加されません。
SQLのWHERE
句とは異なり、RowMatch
は、一時ビュー・オブジェクト属性およびまだポストされていない属性値を含む式でも評価できます。これは、Javaで値が計算される一時ビュー行属性を含むRowMatch
式に基づいて、問い合された行をフィルタするのに便利です。ただし、この興味深い機能は、アプリケーションが大きな行セットを処理する必要がある場合には注意して使用する必要があります。最初にデータベース・レベルのフィルタ処理を使用してできるかぎり小さい行セットを取得した後、RowMatchを使用してメモリーでそのリストからサブセットを作成することをお薦めします。
26.6項「ビジネス・ドメイン・レイヤーでの継承の使用」では、User
、Technician
およびManager
の各エンティティ・オブジェクトの継承階層を作成する方法を説明しました。時にはTechnician
のような単一の型のエンティティ行と連携するビュー・オブジェクトを作成する場合があり、これにはTechnician
固有の属性が含まれていると考えられます。また、場合によっては、同じ行セットでユーザーと技術者と管理者の行を問い合せて更新することもあり、すべてが共有する共通の属性を処理します。
注意: この項で説明する例を試すには、26.6項「ビジネス・ドメイン・レイヤーでの継承の使用」で使用したものと同じAdvancedEntityExamples ワークスペースのInheritanceAndPolymorphicQueries プロジェクトを使用します。 |
多相エンティティ・オブジェクトの慣用名とは、継承階層のベース・エンティティ・オブジェクトを参照し、そのエンティティのサブタイプも処理するように構成されているものです。図27-4は、多相エンティティ・オブジェクトの慣用名でビュー・オブジェクトを使用した結果を示しています。エンティティ・ベースのUserList
ビュー・オブジェクトには、1次エンティティ・オブジェクトの慣用名としてUser
エンティティ・オブジェクトがあります。ビュー・オブジェクトは、データベースから取得された各行を、適切なエンティティ・オブジェクト・サブタイプであるUser
のエンティティ行部分に分割します。識別子属性の値の検査に基づいて、適切なエンティティ行サブタイプを作成します。たとえば、UserList
問合せが、ユーザーngreenbe
の1行、管理者sking
の1行、技術者ahunold
の1行を取得した場合、基礎になっているエンティティ行部分は図で示されているようになります。
多相エンティティ・オブジェクトの慣用名でビュー・オブジェクトを作成するには、次の手順を使用します。
使用するエンティティ継承階層内のベース型を表すエンティティ・オブジェクトを識別します。
そのベース・エンティティをエンティティ・オブジェクトの慣用名として、エンティティ・ベースのビュー・オブジェクトを作成します。
注意: エンティティ・ベースのビュー・オブジェクトが識別子属性を持つエンティティ・オブジェクトを参照していると、JDeveloperはその識別子属性も(主キー属性に加えて)問合せにも含まれるようにします。 |
ビュー・オブジェクトの作成ウィザードの「エンティティ・オブジェクト」パネルの「選択済」リストでエンティティ・オブジェクトの慣用名を選択し、「サブタイプ」をクリックします。
表示される「サブタイプ」ダイアログで、許可するエンティティ・サブタイプを「使用可能」から「選択済」リストに移動し、「OK」をクリックします。
その後、「OK」をクリックしてビュー・オブジェクトを作成します。
多相エンティティ・オブジェクトの慣用名でエンティティ・ベースのビュー・オブジェクトを作成すると、JDeveloperは許されるエンティティ・サブタイプに関する情報をビュー・オブジェクトのXMLコンポーネント定義に追加します。たとえば、前記のUserList
ビュー・オブジェクトを作成すると、許されるサブタイプ・エンティティ・オブジェクトの名前が次のようなAttrArrayタグに記録されます。
<ViewObject Name="UserList" ... > <EntityUsage Name="TheUser" Entity="devguide.advanced.inheritance.entities.User" > </EntityUsage> <AttrArray Name="EntityImports"> <Item Value="devguide.advanced.inheritance.entities.Manager" /> <Item Value="devguide.advanced.inheritance.entities.Technician" /> </AttrArray> <!-- etc. --> </ViewObject>
ビュー・オブジェクトが階層で使用可能なエンティティ・サブタイプのサブセットのみを使用することを期待する場合は、識別子列が期待するエンティティ・タイプと一致する行のみを返すように問合せを制限する適切なWHERE句を含める必要があります。
設計上、クライアントはエンティティ・オブジェクトを直接使用しません。かわりに、当面のタスクに関連する情報のセットを表す適切なビュー・オブジェクトのビュー行を通して、間接的にエンティティ・オブジェクトを使用します。ビュー・オブジェクトは、当面のタスクに関連する1つまたは複数のエンティティ・オブジェクトの基礎となる属性の特定のセットを公開できるのと同じように、これらのエンティティから選択されたメソッドのセットも公開できます。そのためには、カスタム・ビュー行のJavaクラスを有効にし、次のビュー行クラスでメソッドを記述します。
ビュー行で生成されるエンティティ・アクセッサを使用して基礎となる適切なエンティティ行にアクセス
そのメソッドの呼出し
たとえば、UserImpl
クラスにperformUserFeature()
メソッドを含むUser
エンティティ・オブジェクトがあるものとします。このメソッドをUserList
ビュー行でクライアントに公開するには、カスタム・ビュー行のJavaクラスを有効にし、例27-13で示されているメソッドを記述します。JDeveloperは、エンティティ・オブジェクトの慣用名の別名に基づいて、関係する各エンティティ・オブジェクトの慣用名に対するビュー行クラスで、エンティティ・アクセッサ・メソッドを生成します。UserList
ビュー・オブジェクトでのUser
エンティティの別名はTheUser
であるため、そのエンティティ・オブジェクトの慣用名に関係するエンティティ行部分を返すためのgetTheUser()
メソッドを生成します。
例27-13 選択したエンティティ・オブジェクト・メソッドの委譲によるビュー行での公開
// In UserListRowImpl.java public void performUserFeature() { getTheUser().performUserFeature(); }
ビュー行のperformUserFeature()
メソッドのコードは、このgetTheUser()
メソッドを使用して基礎となるUserImpl
エンティティ行クラスにアクセスし、そのperformUserFeature()
メソッドを呼び出します。このコーディング・スタイルは委譲と呼ばれるもので、ビュー行メソッドはそのメソッドの1つの実装を、基礎となるエンティティ・オブジェクトの対応するメソッドに委譲します。多相エンティティ・オブジェクトの慣用名のあるビュー行で委譲が使用されると、委譲されたメソッド呼出しは、基礎となる適切なエンティティ行サブタイプによって処理されます。つまり、UserImpl
クラス、ManagerImpl
クラス、およびTechnicianImpl
クラスが異なる方法でperformUserFeature()
メソッドを実装している場合は、現在行のエンティティ・サブタイプに応じて、適切な実装が使用されます。
クライアント行インタフェースでこのメソッドを公開すると、クライアント・プログラムは、カスタム行インタフェースを使用して、特定のビュー行でカスタム・ビジネス機能を呼び出すことができます。例27-14は、TestEntityPolymorphism
クラスのコード行を示しています。UserList
ビュー・オブジェクト・インスタンスのすべての行を反復し、各行をカスタムUserListRow
インタフェースにキャストして、performUserFeature()
メソッドを呼び出しています。
例27-14 エンティティ・オブジェクトに委譲するビュー行メソッドの呼出し
UserList userlist = (UserList)am.findViewObject("UserList"); userlist.executeQuery(); while (userlist.hasNext()) { UserListRow user = (UserListRow)userlist.next(); System.out.print(user.getEmail()+"->"); user.performUserFeature(); }
例27-14のクライアント・コードを実行すると、次のような出力が生成されます。
austin->## performUserFeature as Technician hbaer->## performUserFeature as User : sking->## performUserFeature as Manager :
User
エンティティに関連する行では、UserImpl
クラスのperformUserFeature()
メソッドが使用されたことを示すメッセージが表示されます。Technician
エンティティおよびManager
エンティティに関連する行では異なるメッセージが表示され、それぞれTechnicianImplクラスおよびManagerImplクラスが継承されたperformUserFeature()
メソッドに対して持っている異なる実装が示されます。
多相エンティティ・オブジェクトの慣用名のあるビュー・オブジェクトでは、新しいビュー行を作成すると、それにはベース・エンティティ・オブジェクトの慣用名と一致する型を持つ新しいエンティティ行部分が含まれます。かわりにいずれかのエンティティ・サブタイプで新しいビュー行を作成するには、createAndInitRow()
メソッドを使用します。例27-15では、UserList
ビュー・オブジェクトのJavaクラスの2つのカスタム・メソッドが示されており、これらはcreateAndInitRow()
を使用して、クライアントがManager
またはTechnician
サブタイプのエンティティ行を持つ新しい行を作成できるようにします。createAndInitRow()
を使用するには、例で示されているように、NameValuePairs
オブジェクトのインスタンスを作成し、それに識別子属性の適切な値を設定します。次に、そのNameValuePairs
をcreateAndInitRow()
メソッドに渡し、指定した識別子属性の値に基づいて、適切なエンティティ行サブタイプを持つ新しいビュー行を作成します。
例27-15 エンティティ・サブタイプを持つ新しい行を作成するためのカスタム・メソッドの公開
// In UserListImpl.java public UserListRow createManagerRow() { NameValuePairs nvp = new NameValuePairs(); nvp.setAttribute("UserRole","manager"); return (UserListRow)createAndInitRow(nvp); } public UserListRow createTechnicianRow() { NameValuePairs nvp = new NameValuePairs(); nvp.setAttribute("UserRole","technician"); return (UserListRow)createAndInitRow(nvp); }
このようなメソッドをビュー・オブジェクトのカスタム・インタフェースで公開すると、実行時に、クライアントはそれを呼び出して、適切なエンティティ・サブタイプを持つ新しいビュー行を作成できます。例27-16は、TestEntityPolymorphism
クラスのこの機能に関連する行を示しています。最初に、createRow()
メソッド、createManagerRow()
メソッド、およびcreateTechnicianRow()
メソッドを使用して、3つの新しいビュー行を作成します。次に、新しい行のそれぞれでUserListRow
カスタム・インタフェースからperformUserFeature()
メソッドを呼び出します。
各行は期待したとおりに関連するエンティティ行のサブタイプに固有の方法でメソッドを処理し、結果を生成します。
## performUserFeature as User ## performUserFeature as Manager ## performUserFeature as Technician
例27-16 異なるエンティティ・サブタイプでの新しいビュー行の作成
// In TestEntityPolymorphism.java UserListRow newUser = (UserListRow)userlist.createRow(); UserListRow newMgr = userlist.createManagerRow(); UserListRow newTech = userlist.createTechnicianRow(); newUser.performUserFeature(); newMgr.performUserFeature(); newTech.performUserFeature();
27.6項「ビュー・オブジェクトを使用した複数の行タイプの処理」の例では、ポリモフィズムはエンティティ・オブジェクト・レベルで目に見えずに行われていました。クライアント・コードは同じUserListRow
インタフェースを使用してすべてのビュー行を処理するので、Manager
エンティティ・オブジェクトに基づく行と、User
エンティティ・オブジェクトに基づく行を区別することはできません。コードは、基礎となるエンティティ・サブタイプのすべての型に共通するビュー行属性とメソッドの同じセットを使用して、すべてのビュー行を処理します。
多相ビュー行をサポートするようにビュー・オブジェクトを構成すると、クライアントは、行の型に固有のビュー行インタフェースを使用して、異なる型のビュー行を処理できます。このようにすると、クライアントは、必要に応じて、特定のサブタイプに固有のビュー属性にアクセスしたり、ビュー行メソッドを呼び出したりできます。図27-5は、前述のUserList
の例に対してこの機能を有効にするビュー・オブジェクトの階層を示しています。TechnicianList
とManagerList
は、ベースのUserList
ビュー・オブジェクトを拡張するビュー・オブジェクトです。それぞれが、エンティティ・オブジェクトの慣用名として保持するUser
のサブタイプに固有の追加属性を含んでいることに注意してください。TechnicianList
は追加属性Certified
を含み、ManagerList
は追加属性NextReview
を含んでいます。次の項で説明するように、クライアントがビュー行ポリモフィズム用に構成されていると、次のものを使用してUserList
ビュー・オブジェクトの結果を処理できます。
ユーザーに関係するビュー行のUserListRow
インタフェース
技術者に関係するビュー行のTechnicianListRow
インタフェース
管理者に関係するビュー行のManagerListRow
インタフェース
これにより、クライアントは、特定のサブタイプのビュー行に固有の追加属性とビュー行メソッドにアクセスできます。
多相ビュー行を持つビュー・オブジェクトを作成するには、次の手順を使用します。
ベースにする既存のビュー・オブジェクトを識別します。
前の例では、UserList
ビュー・オブジェクトがベースです。
ビュー行の識別子属性を識別し、デフォルト値を設定します。
使用するビュー行インタフェースを識別するものとして属性をマークするには、属性パネルの「識別子」 チェック・ボックスを選択します。ベース・ビュー・オブジェクトのビュー行インタフェースが使用するものと予想される属性値に一致する値を、「デフォルト」フィールドで指定する必要があります。たとえば、UserList
ビュー・オブジェクトでは、UserRole
属性を識別子属性としてマークし、デフォルト値をuser
にします。
ベース・ビュー・オブジェクトのカスタム・ビュー行クラスを有効にし、少なくとも1つのメソッドをクライアント行インタフェースで公開します。このメソッドとしては、ビュー行属性アクセッサ・メソッドの1つまたは全部、および任意のカスタム・ビュー行メソッドを使用できます。
ベース・ビュー・オブジェクトを拡張する新しいビュー・オブジェクトを作成します。
前の例では、TechnicianList
がベース・ビュー・オブジェクトUserList
を拡張します。
拡張されたビュー・オブジェクトのカスタム・ビュー行クラスを有効にします。
妥当な場合は、新しいカスタム・ビュー行メソッドを追加するか、または親ビュー・オブジェクトの行クラスから継承するカスタム・ビュー行メソッドをオーバーライドします。
拡張されたビュー・オブジェクトの識別子属性に個別の値を設定します。
TechnicianList
ビュー・オブジェクトは、UserRole
識別子属性にtechnician
という値を設定しています。
必要に応じて、手順4〜6を繰り返し、他の拡張されたビュー・オブジェクトを追加します。
たとえば、ManagerList
ビュー・オブジェクトはUserList
を拡張する2番目のものです。このオブジェクトは、UserRole
識別子属性に値manager
を設定します。
ビュー・オブジェクトの階層を設定した後、ビュー行ポリモフィズムに参加するビュー・オブジェクト・サブタイプのリストを定義する必要があります。これは次のようにして行います。
階層内のビュー・オブジェクトの各型のインスタンスを、アプリケーション・モジュールのデータ・モデルに追加します。
たとえば、例のUserModule
アプリケーション・モジュールには、UserList
、TechnicianList
、ManagerList
の各ビュー・オブジェクトのインスタンスがあります。
アプリケーション・モジュール・エディタの「データ・モデル」パネルで、「サブタイプ」をクリックします。
表示される「サブタイプ」ダイアログで、ビュー行ポリモフィズムに加えるビュー・オブジェクト・サブタイプを「使用可能」から「選択済」リストに移動し、「OK」をクリックします。
拡張されたビュー・オブジェクトを作成すると、そのオブジェクトは親のエンティティ・オブジェクトの慣用名を継承します。親のビュー・オブジェクトのエンティティ・オブジェクトの慣用名がドメイン・レイヤーのサブタイプを持つエンティティ・オブジェクトに基づいている場合は、継承した親のエンティティ・オブジェクトの慣用名型ではなく、これらのサブタイプのいずれかを拡張されたビュー・オブジェクトが使用するようにする場合があります。このようにするには2つの理由があります。
エンティティ・サブタイプに固有の属性を選択するため
エンティティ・サブタイプに固有のメソッドに委譲するビュー行メソッドを作成できるようにするため
これを行うには、継承されたエンティティ・オブジェクトの慣用名をオーバーライドして、目的のエンティティ・サブタイプを参照する必要があります。そのためには、拡張されたビュー・オブジェクトに対するビュー・オブジェクト・エディタで次の手順を行います。
「エンティティ・オブジェクト」パネルで、拡張されたエンティティ・オブジェクトの慣用名を使用していることを確認します。
たとえば、UserList
ビュー・オブジェクトを拡張するTechnicianList
ビュー・オブジェクトを作成するときは、最初、TheUser
という別名を使用するエンティティ・オブジェクトの慣用名が、「選択済」リストに、「TheUser (User): 拡張済」のように表示されます。エンティティ・オブジェクトの慣用名の型はカッコ内で示され、「拡張済」というラベルはエンティティ・オブジェクトの慣用名が現在は親から継承されていることを示します。
「使用可能」リストで、継承されたものをオーバーライドする目的のエンティティ・サブタイプを選択します。既存のエンティティ・オブジェクトの慣用名型のサブタイプ・エンティティである必要があります。
たとえば、User
エンティティ型に基づいて継承されたエンティティ・オブジェクトの慣用名をオーバーライドするには、「使用可能」リストでTechnician
エンティティ・オブジェクトを選択します。
「>」をクリックして、「選択済」リストに移動します。
表示される警告に応答し、既存の継承されたエンティティ・オブジェクトの慣用名をオーバーライドすることを確認します。
この手順を実行すると、「選択済」リストが更新されて、オーバーライドされたエンティティ・オブジェクトの慣用名を反映するようになります。たとえば、TechnicianList
ビュー・オブジェクトの場合は、User
ベースのエンティティ・オブジェクトの慣用名をTechnician
エンティティ・サブタイプでオーバーライドすると、表示が「TheUser (Technician): 上書き済」に更新されます。
エンティティ・オブジェクトの慣用名をエンティティ・サブタイプに関係するようにオーバーライドした後は、エディタの「属性」タブを使用して、そのサブタイプに固有の追加属性を選択できます。たとえば、TechnicianList
ビュー・オブジェクトには、Technician
エンティティ・オブジェクトに固有の、Certified
という名前の追加属性が含まれます。
拡張されたビュー・オブジェクトのエンティティ・オブジェクトの慣用名をサブタイプ・エンティティを参照するようにオーバーライドした後は、サブタイプ・エンティティ・クラスに固有のメソッドに委譲するビュー行メソッドを作成できます。例27-17は、TechnicianList
ビュー・オブジェクトに対するカスタム・ビュー行クラスのperformTechnicianFeature()
メソッドのコードを示しています。getTheUser()
エンティティ行アクセッサからの戻り値をサブタイプTechnicianImpl
にキャストした後、Technician
エンティティ・オブジェクトに固有のperformTechnicianFeature()
メソッドを呼び出します。
例27-17 サブタイプ・エンティティのメソッドに委譲するビュー行メソッド
// In TechnicianListRowImpl.java public void performTechnicianFeature() { TechnicianImpl tech = (TechnicianImpl)getTheUser(); tech.performTechnicianFeature(); }
注意: JDeveloperでは、TechnicianListRowImpl のようなサブクラスがgetTheUser() のようなメソッドをオーバーライドしてその戻り型を変更できるようにする共変戻り型と呼ばれるJDK 5.0の新機能がまだ採用されていないため、ここではエンティティ・サブタイプに明示的にキャストする必要があります。 |
例27-18は、次の手順を実行するTestViewRowPolymorphism
クラスのコード行を示しています。
UserList
ビュー・オブジェクトの行を反復処理します。
ループでは、各行について、Javaのinstanceof
演算子を使用して、現在の行がManagerListRow
またはTechnicianListRow
のインスタンスかどうかを検査しています。
行がManagerListRow
の場合は、行をこのさらに具体的な型にキャストした後、次の処理を行います。
ManagerListRow
インタフェースに固有のperformManagerFeature()
メソッドを呼び出します。
ManagerList
ビュー・オブジェクトに固有のNextReview
属性の値にアクセスします。
行がTechnicianListRow
の場合は、行をこのさらに具体的な型にキャストした後、次の処理を行います。
TechnicianListRow
インタフェースに固有のperformTechnicianFeature()
メソッドを呼び出します。
TechnicianList
ビュー・オブジェクトに固有のCertified
属性の値にアクセスします。
それ以外の場合は、UserListRow
でメソッドを呼び出します。
例27-18 クライアント・コードでのビュー行ポリモフィズムの使用
// In TestViewRowPolymorphism.java ViewObject vo = am.findViewObject("UserList"); vo.executeQuery(); // 1. Iterate over the rows in the UserList view object while (vo.hasNext()) { UserListRow user = (UserListRow)vo.next(); System.out.print(user.getEmail()+"->"); if (user instanceof ManagerListRow) { // 2. If the row is a ManagerListRow, cast it ManagerListRow mgr = (ManagerListRow)user; mgr.performManagerFeature(); System.out.println("Next Review:"+mgr.getNextReview()); } else if (user instanceof TechnicianListRow) { // 3. If the row is a ManagerListRow, cast it TechnicianListRow tech = (TechnicianListRow)user; tech.performTechnicianFeature(); System.out.println("Certified:"+tech.getCertified()); } else { // 4. Otherwise, just call a method on the UserListRow user.performUserFeature(); } }
例27-18のコードを実行すると、次のような出力が生成されます。
daustin->## performTechnicianFeature called Certified:Y hbaer->## performUserFeature as User : sking->## performManagerFeature called Next Review:2006-05-09 :
これは、ビュー行ポリモフィズムの機能を使用することで、クライアントが異なる型のビュー行を区別し、ビュー行の各サブタイプに固有のメソッドと属性にアクセスできたことを示します。
通常は一緒に使用する方が役に立ちますが、ビュー行ポリモフィズム機能と多相エンティティ・オブジェクト慣用名機能は異なるものであり、個別に使用できます。特に、ビュー行ポリモフィズムの機能は、読取り専用ビュー・オブジェクトにも、エンティティ・ベースのビュー・オブジェクトにも使用できます。両方のメカニズムを組み合せると、多相のエンティティ行部分とビュー行型の両方を持つことができます。
ビュー・オブジェクトまたはエンティティ・オブジェクトでビュー行ポリモフィズムを使用するには、それぞれに個別の識別子属性プロパティを構成する必要があることに注意してください。これが必要になるのは、読取り専用ビュー・オブジェクトには、識別子情報を推測する関連エンティティ・オブジェクトの慣用名が含まれていないためです。
つまり、ビュー行ポリモフィズムを使用するには、次のことが必要です。
継承階層のルート・ビュー・オブジェクトのビュー・オブジェクト・レベルで、識別子にする属性を構成します。
階層の継承されるビュー・オブジェクトのそれぞれで、そのビュー・オブジェクト・レベルの識別子属性のデフォルト値プロパティに、個別の値を設定します。
アプリケーション・モジュールのサブタイプのリストで、この階層内にあるサブクラス化されたビュー・オブジェクトのリストを作成します。
一方、多相エンティティ・オブジェクトの慣用名を含むビュー・オブジェクトを作成するには、次のことを行います。
継承階層のルート・エンティティ・オブジェクトのエンティティ・オブジェクト・レベルで、識別子にする属性を構成します。
階層の継承されるエンティティ・オブジェクトのそれぞれで、オーバーライドし、そのエンティティ・オブジェクト・レベルの識別子属性のデフォルト値プロパティに個別の値を設定します。
ビュー・オブジェクトのサブタイプのリストで、サブクラス化されたエンティティ・オブジェクトのリストを作成します。
Worldwide Web Consortium(W3C)が定めるExtensible Markup Language(XML)標準では、電子データ交換用の言語に依存しない方式が定義されています。厳密な一連の規則により、判読可能なテキスト・ドキュメントを使用して、データに固有の構造を簡単にエンコードし、正確に解釈できます。
ビュー・オブジェクトでは、問い合されたデータに基づいて、XMLドキュメントを記述することができます。また、XMLドキュメントを読み取って、挿入、更新、削除などの変更をデータに適用することもできます。ビュー・リンクを導入すると、このXML機能は、任意の複雑さのマスター/ディテール階層の複数レベルでネストされた情報の読取りと書込みをサポートします。ビュー・オブジェクトが生成および使用するXMLは正規書式に従いますが、ビュー・オブジェクトのXML機能とXML Stylesheet Language Transformations(XSLT)を組み合せることで、正規のXML書式と使用する必要のある任意の書式の間の変換を簡単に行うことができます。
注意: この項の例では、AdvancedViewObjectExamples ワークスペースのReadingAndWritingXML プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。 |
ビュー・オブジェクトからXMLを生成するために、writeXML()
メソッドを使用します。2種類の方法で生成されるXMLを制御できます。
生成されるXMLを正確に制御するには、ネストされた詳細情報に対してアクセスする必要のあるビュー・リンク・アクセッサ属性など、使用する必要のある属性を示すビュー・オブジェクト属性マップを指定できます。
Node writeXML(long options, HashMap voAttrMap)
すべての属性を含むXMLを生成するには、結果を生成するためにトラバースする必要のあるビュー・リンク・アクセッサ属性のレベル数を示す深さレベルを指定するだけで済みます。
Node writeXML(int depthCount, long options)
options
パラメータは整数のフラグ・フィールドで、次のビット・フラグのいずれかを設定できます。
XMLInterface.XML_OPT_ALL_ROWS
設定するビュー・オブジェクトのすべての行をXMLに含めます。
XMLInterface.XML_OPT_LIMIT_RANGE
現在の範囲内の行のみをXMLに含めます。
現在のトランザクションにあるポストされていない新しい行をXML出力に含める場合は、論理OR演算子を使用して、前記のフラグとXMLInterface.XML_OPT_ASSOC_CONSISTENT
フラグを組み合せることができます。
どちらのバージョンのwriteXML()
メソッドも、オプションで(指定された場合には)、返される前のXML出力を変換するために使用されるXSLTスタイルシートとして第3の引数を受け取ります。
writeXML()
を使用してXMLを生成すると、ビュー・オブジェクトは最初に、ラップするXML要素を作成します。そのデフォルト名は、ビュー・オブジェクト定義の名前と一致しています。たとえば、devguide.advanced.xml.queries
パッケージのUsers
ビュー・オブジェクトの場合は、生成されるXMLをラップする一番外側のタグはUsersタグになります。
次に、ビュー・オブジェクトは、適切な行の属性データをXML要素に変換します。デフォルトでは、各行のデータは、ビュー・オブジェクトの名前に接尾辞Row
を付加した名前の行要素でラップされます。たとえば、Users
という名前のビュー・オブジェクトの各データ行は、UsersRow要素でラップされます。各行の属性データを表す要素は、この行要素内にネストされた子として記述されます。
いずれかの属性がビュー・リンク・アクセッサ属性であり、writeXML()
に渡されたパラメータによって有効になっている場合は、ビュー・リンク・アクセッサによって返されるディテール行セットのデータも含められます。このネストされたデータは、ビュー・リンク・アクセッサ属性の名前によって名前が決まる要素でラップされます。writeXML()
メソッドの戻り値は、W3Cの標準のNode
インタフェースを実装するオブジェクトで、生成されたXMLのルート要素を表します。
注意: writeXML() メソッドは、ビュー・リンク・アクセッサ属性を使用して、プログラムでディテール・コレクションにアクセスします。データ・モデルにビュー・リンクのインスタンスを追加する必要はありません。 |
たとえば、Users
ビュー・オブジェクト・インスタンスのすべての行に対するXML要素を生成し、存在する最大レベルの深さまでビュー・リンク・アクセッサをたどる場合は、例27-19に示すようなコードが必要です。
例27-19 ビュー・オブジェクトの全行と全ビュー・リンク・レベルのXMLの生成
ViewObject vo = am.findViewObject("Users"); printXML(vo.writeXML(-1,XMLInterface.XML_OPT_ALL_ROWS));
Users
ビュー・オブジェクトは、そのユーザーが作成したサービス・リクエストを表すServiceRequests
ビュー・オブジェクトにリンクされています。そして、ServiceRequests
ビュー・オブジェクトは、顧客と技術者がサービス・リクエストに対して入力したメモの詳細を提供するServiceHistories
ビュー・オブジェクトにリンクされています。例27-19のコードを実行すると、ビュー・リンクによって定義されているネストされた構造を反映した、例27-20で示されるXMLが生成されます。
例27-20 Usersビュー・オブジェクトと2レベルのビュー・リンクされたディテールから生成されるXML
<Users> : <User> <UserId>316</UserId> <UserRole>user</UserRole> <EmailAddress>sbaida</EmailAddress> <FirstName>Shelli</FirstName> <LastName>Baida</LastName> <StreetAddress>4715 Sprecher Rd</StreetAddress> <City>Madison</City> <StateProvince>Wisconsin</StateProvince> <PostalCode>53704</PostalCode> <CountryId>US</CountryId> <UserRequests> <ServiceRequestsRow> <SvrId>101</SvrId> <Status>Closed</Status> <RequestDate>2006-04-16 13:32:54.0</RequestDate> <ProblemDescription>Agitator does not work</ProblemDescription> <ProdId>101</ProdId> <CreatedBy>316</CreatedBy> <AssignedTo>304</AssignedTo> <AssignedDate>2006-04-23 13:32:54.0</AssignedDate> <ServiceHistories> <ServiceHistoriesRow> <SvrId>101</SvrId> <LineNo>1</LineNo> <SvhDate>2006-04-23 13:32:54.0</SvhDate> <Notes>Asked customer to ensure the lid was closed</Notes> <SvhType>Technician</SvhType> <CreatedBy>304</CreatedBy> </ServiceHistoriesRow> <ServiceHistoriesRow> <SvrId>101</SvrId> <LineNo>2</LineNo> <SvhDate>2006-04-24 13:32:54.0</SvhDate> <Notes>Problem is fixed</Notes> <SvhType>Customer</SvhType> <CreatedBy>316</CreatedBy> </ServiceHistoriesRow> </ServiceHistories> </ServiceRequestsRow> </UserRequests> </User> : </Users>
ビュー・オブジェクトの正規XML書式で使用されるデフォルトのXML要素名は、カスタム・プロパティを設定することで変更できます。
属性レベルのカスタム・プロパティXML_ELEMENT
に値SomeOtherName
を設定すると、その属性に使用されるXML要素の名前がSomeOtherNameに変わります。
たとえば、Users
ビュー・オブジェクトのEmail
属性で定義されているこのプロパティは、例27-20ではXML要素をEmailからEmailAddressに変更しています。
ビュー・オブジェクト・レベルのカスタム・プロパティXML_ROW_ELEMENT
に値SomeOtherRowName
を設定すると、その属性に使用されるXML要素の名前がSomeOtherRowNameに変わります。
たとえば、Users
ビュー・オブジェクトで定義されているこのプロパティは、例27-20では行のXML要素名をUsersRowからUserに変更しています。
ビュー・リンク・アクセッサ属性からのネストされた行セット・データをラップする要素の名前を変更するには、ビュー・リンク・エディタの「ビュー・リンク・プロパティ」パネルを使用して、ビュー・リンク・アクセッサ属性の名前を変更する必要があります。
デフォルトでは、ビュー行属性がnull
の場合、生成されるXMLから対応する要素が省略されます。属性レベルのカスタム・プロパティXML_EXPLICIT_NULL
に任意の値(たとえば、true
やyes
)を設定すると、値がnullの属性についても要素が生成されます。たとえば、AssignedDate
という名前の属性にこのプロパティを設定すると、AssignedDateがnull
の行には、対応する要素AssignedDate null="true"/が生成されます。この動作をビュー・オブジェクトのすべての属性に適用する場合は、属性ごとにプロパティを定義するかわりに、ビュー・オブジェクト・レベルでXML_EXPLICIT_NULL
カスタム・プロパティを定義できます。
writeXML()
から返されるXML Node
オブジェクトに関する最も一般的な処理は次の2つです。
ネットワーク経由での送信やファイルへの保存などのために、ノードをシリアライズされたテキスト表現に印刷
生成されたXMLをW3C XPath式を使用して検索
残念ながら、標準のW3C Document Object Model(DOM)APIには、これらの役に立つ操作を行うためのメソッドは含まれません。しかし大丈夫です。ADF Business ComponentsはDOMのOracle XMLパーサーの実装を使用しているので、writeXML()
からのNode
戻り値をOracle固有のクラスであるXMLNode
やXMLElement
(oracle.xml.parser.v2
パッケージ内)にキャストして、次のような便利な追加機能にアクセスできます。
print()
メソッドを使用した、シリアライズされたテキスト形式へのXML要素の印刷
selectNodes()
メソッドを使用した、メモリー内のXML要素のXPath式による検索
valueOf()
メソッドを使用した、XML要素に関連するXPath式の値の検出
例27-21は、TestClientWriteXML
のprintXML()
メソッドです。このメソッドは、Node
パラメータをXMLNodeにキャストし、print()
メソッドを呼び出してXMLをコンソールにダンプします。
生成されるXMLに含める属性をきめ細かく制御する必要がある場合は、HashMap
を受け取るバージョンのwriteXML()
メソッドを使用します。例27-22は、この手法を使用するTestClientWriteXML
クラスの行を示しています。HashMap
を作成した後、String[]
値のエントリを設定します。このエントリは、XMLに含める属性の名前であり、キーとしてはこれらの属性が属しているビュー定義の完全修飾名を使用します。例には、Users
ビュー・オブジェクトの属性UserId
、Email
、StateProvince
、UserRequests
と、ServiceRequests
ビュー・オブジェクトの属性SvrId
、Status
、AssignedDate
、およびProblemDescription
が含まれています。
注意: 旧バージョンのADF Business Componentsとの互換性のため、writeXML() メソッドが受け取るHashMap はcom.sun.java.util.collections パッケージのものです。 |
特定のビュー・オブジェクト・インスタンスのビュー行に対しては、次のような処理が行われます。
そのビュー・オブジェクトの完全修飾ビュー定義名とキーが一致する項目が属性マップに存在する場合は、対応するString
配列に名前のある属性のみがXMLに含められます。
さらに、文字列配列にビュー・リンク・アクセッサ属性の名前が含まれる場合は、そのディテール行セットのネストされた内容がXMLに含められます。ビュー・リンク・アクセッサ属性名が文字列配列に出現しない場合は、ディテール行セットの内容は含められません。
キーが一致する項目がマップ内に存在しない場合は、その行のすべての属性がXMLに含められます。
例27-22 生成されるXMLをきめ細かく制御するためのビュー定義属性マップの使用
HashMap viewDefMap = new HashMap(); viewDefMap.put("devguide.advanced.xml.queries.Users", new String[]{"UserId", "Email", "StateProvince", "UserRequests" /* View link accessor attribute */ }); viewDefMap.put("devguide.advanced.xml.queries.ServiceRequests", new String[]{"SvrId","Status","AssignedDate","ProblemDescription"}); printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap));
例を実行すると、例27-23で示されるXMLが生成されます。これには、提供された属性マップで指定されている属性とビュー・リンク・アクセッサのみが含まれます。
例27-23 属性マップを使用して生成されたUsersビュー・オブジェクトからのXML
<Users> <User> <UserId>300</UserId> <EmailAddress>sking</EmailAddress> <StateProvince>Washington</StateProvince> <UserRequests> <ServiceRequestsRow> <SvrId>200</SvrId> <Status>Open</Status> <AssignedDate null="true"/> <ProblemDescription>x</ProblemDescription> </ServiceRequestsRow> </UserRequests> </User> <User> <UserId>301</UserId> <EmailAddress>nkochhar</EmailAddress> <StateProvince>Maryland</StateProvince> </User> : </Users>
ビュー・オブジェクトが、双方向に構成されたビュー・リンクを通して関連付けられている場合は、属性マップを使用するwriteXML()
方式を使用する必要があります。双方向ビュー・リンクの状況でwriteXML()
方式を使用し、最大深度に-1
を指定して存在するビュー・リンクの全レベルを含めるようにすると、writeXML()
メソッドは、双方向のビュー・リンクを前後に移動して無限ループに陥り、メモリーを使い果すまで重複したデータを含む深くネストしたXMLを生成します。このような場合は、かわりに属性マップを指定してwriteXML()
を使用してください。この方法を使用すること以外に、XMLに含める、または含めないビュー・リンク・アクセッサを制御し、XML生成中の無限再帰を回避する方法はありません。
writeXML()
によって生成される正規のXML書式が要件を満たさない場合は、オプションの引数として、XSLTスタイルシートを提供できます。通常と同じようにXMLが生成されますが、最終的なXMLが呼出し元に返される前に、指定したスタイルシートを使用して結果の変換が行われます。
例27-24で示すようなXSLTスタイルシートについて考えます。これは、例27-23で生成されるXMLのルート要素と一致して、新しいCustomerEmailAddresses要素を結果に作成する、テンプレートが1つのみの簡単な変換です。xsl:for-each命令を使用して、ネストされたUserRequests要素の中で複数のServiceRequestsRow子要素を含むUser要素のすべての子を処理します。対象であることが確認された要素ごとに、結果にCustomer要素を作成し、そのContact
属性には、UserのEmailAddress子要素の値を移入します。
例27-24 生成されたXMLを別の書式に変換するXSLTスタイルシート
<?xml version="1.0" encoding="windows-1252" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <CustomerEmailAddresses> <xsl:for-each select="/Users/User[count(UserRequests/ServiceRequestsRow) > 1]"> <xsl:sort select="EmailAddress"/> <Customer Contact="{EmailAddress}"/> </xsl:for-each> </CustomerEmailAddresses> </xsl:template> </xsl:stylesheet>
例27-25は、writeXML()
を呼び出すとこのXSLTスタイルシートを実行するTestClientWriteXML
クラスの行です。
例27-25 結果のXMLを変換するためのwriteXML()へのXSLTスタイルシートの引渡し
// In TestClientWriteXML.java XSLStylesheet xsl = getXSLStylesheet(); printXML(vo.writeXML(XMLInterface.XML_OPT_ALL_ROWS,viewDefMap,xsl));
例27-25のコードを実行すると、次のような変換されたXMLが生成されます。
<CustomerEmailAddresses> <Customer Contact="dfaviet"/> <Customer Contact="jchen"/> <Customer Contact="ngreenbe"/> </CustomerEmailAddresses>
例27-26で示されているgetXSLStylesheet()
ヘルパー・メソッドは、実行時にクラスパスからXSLTスタイルシートなどのリソースを読み込む方法を学習するためのよい例です。このコードでは、Example.xsl
スタイルシートがTestClientWriteXML
クラスと同じディレクトリにあるものと想定しています。.class
演算子を使用してTestClientWriteXML
クラスのClass
オブジェクトを参照することで、コードはgetResource()
メソッドを使用してリソースに対するURL
を取得します。その後、URLをXSLProcessor
クラスのnewXSLStylesheet()
メソッドに渡して、返すための新しいXSLStylesheet
オブジェクトを作成します。このオブジェクトは、*.xsl
ファイルから読み込まれたXSLTスタイルシートのコンパイル済バージョンを表しています。
例27-26 クラスパスからのリソースとしてのXSLTスタイルシートの読込み
private static XSLStylesheet getXSLStylesheet() throws XMLParseException, SAXException,IOException,XSLException { String xslurl = "Example.xsl"; URL xslURL = TestClientWriteXML.class.getResource(xslurl); XSLProcessor xslProc = new XSLProcessor(); return xslProc.newXSLStylesheet(xslURL); }
注意: XSLTスタイルシートのように、コンパイル済のJavaクラスやXMLメタデータとともに出力ディレクトリに格納するリソースを扱うときは、「プロジェクト・プロパティ」ダイアログの「コンパイラ」ページで、「ファイル・タイプの出力ディレクトリへのコピー」フィールドを更新し、セミコロンで区切られたリストに.xsl を含めることができます。 |
ビュー・オブジェクトがXMLドキュメントを使用して挿入、更新、および削除を行うようにするには、readXML()
メソッドを使用します。
void readXML(Element elem, int depthcount)
readXML()
が受け入れる正規の書式は、同じビュー・オブジェクトでwriteXML()
メソッドを呼び出すと生成されるものと同じです。処理するXMLドキュメントが正規の書式に対応していない場合は、readXML()
に対するオプションの第3引数としてXSLTスタイルシートを提供し、ファイルを読み込んで処理する前に、入力XMLドキュメントを正規の書式に変換できます。
ビュー・オブジェクトは、正規の書式のXMLドキュメントを使用するとき、ドキュメントを処理して、行要素、その属性要素の子、およびビュー・リンク・アクセッサ属性を表すネストされた要素を認識します。depthcount
パラメータで指定されている最大レベルまで、再帰的にドキュメントを処理します。XMLドキュメントの全レベルを処理するよう要求するには、depthcount
に-1
を渡します。
認識する行要素ごとに、readXML()
メソッドは次の処理を行います。
行を処理するために関連するビュー・オブジェクトを識別します。
子属性要素を読み取り、行の主キー属性の値を取得します。
主キー属性を使用してfindByKey()
を実行し、行がすでに存在するかどうかを判定します。
行が存在する場合は、次の処理を行います。
行要素にマーカー属性bc4j-action="remove"
が含まれる場合は、既存の行を削除します。
それ以外の場合は、XMLの現在の行要素の、属性要素の子の値を使用して、行の属性を更新します。
行が存在しない場合は、新しい行を作成し、ビュー・オブジェクトの行セットに挿入します。その属性は、XMLの現在の行要素の、属性要素の子の値を使用して移入します。
Row
オブジェクトに対しても、同じreadXML()
メソッドを使用できます。このメソッドが受け入れる正規のXMLの書式は、同じ行に対してwriteXML()
を呼び出すと生成される書式と同じです。行でreadXML()
メソッドを呼び出し、次の処理を行うことができます。
属性の値をXMLで更新します。
対応する行要素にbc4j-action="remove"
マーカー属性がある場合は、行を削除します。
ビュー・リンク・アクセッサを介して、ネストされた行を挿入、更新、または削除します。
例27-27で示すようなXMLドキュメントについて考えます。これは、Technicians
ビュー・オブジェクトの単一行で期待される正規の書式です。ルートのTechniciansRow要素の内部にネストされているCity属性は、技術者の都市を表します。ネストされたExpertiseAreas要素はExpertiseAreas
ビュー・リンク・アクセッサ属性に対応し、3つのExpertiseAreasRow要素を含みます。それぞれが、ExpertiseAreas
行の2つの属性から成る主キーの一部を表すProdId要素を含みます。他の主キー属性UserId
は、カプセル化している親のTechniciansRow要素から推定されます。
例27-27 行を挿入、更新、および削除するための正規の書式のXMLドキュメント
<TechniciansRow> <!-- This will update Techncian's City attribute --> <City>Padova</City> <ExpertiseAreas> <!-- This will be an update since it does exist --> <ExpertiseAreasRow> <ProdId>100</ProdId> <ExpertiseLevel>Expert</ExpertiseLevel> </ExpertiseAreasRow> <!-- This will be an insert since it doesn't exist --> <ExpertiseAreasRow> <ProdId>110</ProdId> <ExpertiseLevel>Expert</ExpertiseLevel> </ExpertiseAreasRow> <!-- This will be deleted --> <ExpertiseAreasRow bc4j-action="remove"> <ProdId>112</ProdId> </ExpertiseAreasRow> </ExpertiseAreas> </TechniciansRow>
例27-28は、TestClientReadXML
クラスのコード行を示します。このクラスは、Technicians
ビュー・オブジェクトの特定の行、およびExpertiseAreas
ビュー・オブジェクトに対するビュー・リンク・アクセッサ属性を介して技術者の専門分野のネストされたセットに、このXMLデータグラムを適用します。TestClientReadXML
クラスが実行する基本的な手順は次のとおりです。
キー(技術者の場合はahunoldなど)を使用して対象の行を検索します。
変更を適用する前に、行に対して生成されたXMLを表示します。
ヘルパー・メソッドを使用して適用する変更を含む解析されたXMLドキュメントを取得します。
行に変更を適用するためにXMLドキュメントを読み込みます。
適用された保留中変更を含むXMLを表示します。
TestClientReadXML
クラスは、27.7.1項「問合せ済データのXMLの生成方法」で説明されているXMLInterface.XML_OPT_ASSOC_CONSISTENT
フラグを使用して、ポストされていない新しい行をXMLに含めています。
例27-28 readXML()による既存行への変更の適用
ViewObject vo = am.findViewObject("Technicians"); Key k = new Key(new Object[] { 303 }); // 1. Find a target row by key (e.g. for technician "ahunold") Row ahunold = vo.findByKey(k, 1)[0]; // 2. Show the XML produced for the row before changes are applied printXML(ahunold.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS)); // 3. Obtain parsed XML document with changes to apply using helper method Element xmlToRead = getInsertUpdateDeleteXMLGram(); printXML(xmlToRead); // 4. Read the XML document to apply changes to the row ahunold.readXML(getInsertUpdateDeleteXMLGram(), -1); // 5. Show the XML with the pending changes applied printXML(ahunold.writeXML(-1, XMLInterface.XML_OPT_ALL_ROWS | XMLInterface.XML_OPT_ASSOC_CONSISTENT));
例27-28のコードを実行すると、最初に、Alexander Hunoldの変更前の情報が表示されます。次のことに注意してください。
City
属性の値はSouthlakeです。
製品100
に対する専門分野のレベルはQualifiedです。
製品112
に対して専門分野の行があります。
製品110
に関する専門分野の行はありません。
<TechniciansRow> <UserId>303</UserId> <UserRole>technician</UserRole> <Email>ahunold</Email> : <City>Southlake</City> : <ExpertiseAreas> <ExpertiseAreasRow> <ProdId>100</ProdId> <UserId>303</UserId> <ExpertiseLevel>Qualified</ExpertiseLevel> </ExpertiseAreasRow> : <ExpertiseAreasRow> <ProdId>112</ProdId> <UserId>303</UserId> <ExpertiseLevel>Expert</ExpertiseLevel> </ExpertiseAreasRow> : </ExpertiseAreas> </TechniciansRow>
readXML()
を使用してXMLドキュメントの変更を行に適用した後、writeXML()を使用して再びXMLを表示すると、次のようになります。
City
はPadovaになっています。
製品100
に対する専門分野のレベルはExpertです。
製品112
に対する専門分野の行は削除されています。
製品110
に対する専門分野の行が新しく作成されています。
<TechniciansRow> <UserId>303</UserId> <UserRole>technician</UserRole> <Email>ahunold</Email> : <City>Padova</City> : <ExpertiseAreas> <ExpertiseAreasRow> <ProdId>110</ProdId> <UserId>303</UserId> <ExpertiseLevel>Expert</ExpertiseLevel> </ExpertiseAreasRow> <ExpertiseAreasRow> <ProdId>100</ProdId> <UserId>303</UserId> <ExpertiseLevel>Expert</ExpertiseLevel> </ExpertiseAreasRow> </TechniciansRow>
注意: この例では、readXML() を使用して単一の行に変更を適用する場合が示されています。XMLドキュメントに、ラップするTechnicians行が含まれ、そのネストされたTechniciansRow要素のそれぞれに主キー属性が含まれている場合は、Technicians ビュー・オブジェクトでreadXML()メソッドを使用してドキュメントを処理し、複数のTechnician 行に対する操作を処理できます。 |
デフォルトのビュー・オブジェクトは、データベースからデータを読み取り、データベースの結果セットを処理するためにJava Database Connectivity (JDBC)レイヤーを使用するタスクを自動化します。これに対し、カスタムJavaクラスで適切なメソッドをオーバーライドすることにより、REF CURSOR
、メモリー内の配列、Javaの*.properties
ファイルなどの様々な代替データ・ソースからプログラムでデータを取得するビュー・オブジェクトを作成できます。
プログラムで読取り専用のビュー・オブジェクトを作成するには、ビュー・オブジェクトの作成ウィザードを使用し、次の手順を実行します。
手順1の「名前」パネルで、ビュー・オブジェクトの名前とパッケージを指定します。「このビュー・オブジェクトを管理するために必要なデータ」ラジオ・グループで、「問合せベースではなく、プログラムによって移入された行」を選択します。
手順2の「属性」パネルで、「新規」を必要なだけクリックし、プログラムによるビュー・オブジェクトが必要とするビュー・オブジェクト属性を定義します。
手順3では、「属性の設定」パネルで、定義した属性に必要な設定を調節します。
手順4では、「Java」パネルで、カスタム・ビュー・オブジェクト・クラスを有効にして、独自のコードを組み込みます。
「終了」をクリックして、ビュー・オブジェクトを作成します。
ビュー・オブジェクトのカスタムJavaクラスで、27.8.3項「プログラムのビュー・オブジェクト用にオーバーライドする主要なフレームワーク・メソッド」で説明されているメソッドをオーバーライドし、独自のデータ取得方法を実装します。
プログラムでデータを取得するエンティティ・ベースのビュー・オブジェクトを作成するには、通常の方法でビュー・オブジェクトを作成し、カスタムJavaクラスを有効にし、次の項で説明されているメソッドをオーバーライドして独自のデータ取得方法を実装します。
プログラムによるビュー・オブジェクトでは、通常、ViewObjectImpl
ベース・クラスの次のメソッドをすべてオーバーライドして、データ取得の独自方法を実装します。
create()
このメソッドは、ビュー・オブジェクトのインスタンスが作成されるときに呼び出され、プログラムによるビュー・オブジェクトが必要とする任意の状態を初期化するために使用できます。このオーバーライドされるメソッドには、少なくとも次の行を組み込んで、プログラムによるビュー・オブジェクトが関係するSQL問合せをトレースしないようにします。
// Wipe out all traces of a query for this VO getViewDef().setQuery(null); getViewDef().setSelectClause(null); setQuery(null);
executeQueryForCollection()
このメソッドは、ビュー・オブジェクトの問合せを実行する(または再実行する)必要があるたびに呼び出されます。
hasNextForCollection()
このメソッドは、このビュー・オブジェクトから作成される行セットの行セット・イテレータでhasNext()
メソッドをサポートするために呼び出されます。実装では、プログラムのデータ・ソースから取得する行がまだある場合はtrue
を返します。
createRowFromResultSet()
このメソッドは、フェッチされたデータの各行に値を移入するために呼び出されます。実装では、createNewRowForCollection()
を呼び出して新しい空白の行を作成した後、populateAttributeForRow()
を呼び出して行に対するデータの各属性に値を移入します。
getQueryHitCount()
このメソッドは、getEstimatedRowCount()
メソッドをサポートするために呼び出されます。実装では、プログラムによるビュー・オブジェクトの問合せで取得される行数のカウントまたは予想されるカウントを返します。
protected void releaseUserDataForCollection()
コードでは、各行セットでユーザー・データ・コンテキスト・オブジェクトを格納および取得できます。このメソッドを呼び出すと、閉じられた行セットと関連付けられているリソースを解放できます。
ビュー・オブジェクト・コンポーネントは、実行時に複数のアクティブな行セットと関連付けられる可能性があるため、前記のフレームワーク・メソッドの多くは、qc
という名前のObject
パラメータを受け取ります。このパラメータで、フレームワークは、コードが設定していると予想される行のコレクションと、特定のコレクションに移入される行に影響する可能性のあるバインド変数値の配列を渡します。
行のコレクションごとにユーザー・データ・オブジェクトを格納できるので、カスタム・データ・ソースの実装は、必要なデータ・ソース・コンテキスト情報を関連付けることができます。フレームワークでは、このコレクションごとのコンテキスト情報を取得および設定するために、setUserDataForCollection()
メソッドとgetUserDataForCollection()
メソッドが提供されています。オーバーライドされたフレームワーク・メソッドのいずれかが呼び出されるたびに、getUserDataForCollection()
メソッドを使用して、フレームワークが移入を望んでいる行のコレクションと関連付けられた正しいResultSet
オブジェクトを取得できます。
以降の項の各例では、これらのメソッドをオーバーライドして、異なる種類のプログラムによるビュー・オブジェクトを実装しています。
アプリケーションでは、ストアド・プロシージャ内にカプセル化された問合せの結果を使用することが必要になる場合があります。PL/SQLを使用すると、カーソルを開いて問合せの結果を反復処理し、このカーソルへの参照をクライアントに返すことができます。このいわゆるREF CURSOR
は、クライアントが問合せの結果を反復処理するために使用できるハンドルです。これは、クライアントが元のSQL SELECT
文を実際に発行していない場合であっても可能です。
注意: この項の例では、AdvancedViewObjectExamples ワークスペースのViewObjectOnRefCursor プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。プロジェクトに必要な追加のデータベース・オブジェクトを設定するには、「リソース」フォルダにあるCreateRefCursorPackage.sql スクリプトを、SRDemo 接続に対して実行してください。 |
REF CURSOR
を返すファンクションを含むPL/SQLパッケージの宣言は簡単です。たとえば、パッケージは次のようなものです。
CREATE OR REPLACE PACKAGE RefCursorExample IS TYPE ref_cursor IS REF CURSOR; FUNCTION get_requests_for_tech(p_email VARCHAR2) RETURN ref_cursor; FUNCTION count_requests_for_tech(p_email VARCHAR2) RETURN NUMBER; END RefCursorExample;
ServiceRequest
エンティティ・オブジェクトに対するエンティティ・オブジェクトの慣用名を使用してエンティティ・ベースのRequestForTech
ビュー・オブジェクトを定義した後、そのカスタムJavaクラスRequestForTechImpl.java
に移動します。ビュー・オブジェクト・クラスの最上位で、ストアド・ファンクションを呼び出すためにJDBCのCallableStatement
オブジェクトを使用して実行するの匿名ブロックを保持する定数のStringをいくつか定義します。
/* * Execute this block to retrieve the REF CURSOR */ private static final String SQL = "begin ? := RefCursorSample.getEmployeesForDept(?);end;"; /* * Execute this block to retrieve the count of service requests that * would be returned if you executed the statement above. */ private static final String COUNTSQL = "begin ? := RefCursorSample.countEmployeesForDept(?);end;";
次に、以降の項で説明するように、ビュー・オブジェクトのメソッドをオーバーライドします。
create()
メソッドは、このビュー・オブジェクトに対するSQL問合せのすべてのトレースを削除します。
protected void create() { getViewDef().setQuery(null); getViewDef().setSelectClause(null); setQuery(null); }
executeQueryForCollection()
メソッドは、ヘルパー・メソッドretrieveRefCursor()
を呼び出して、ストアド・ファンクションを実行してREF CURSOR
戻り値を返し、JDBC ResultSet
としてキャストします。次に、ヘルパー・メソッドstoreNewResultSet()
を呼び出します。このヘルパー・メソッドは、setUserDataForCollection()
メソッドを使用して、フレームワークが問合せの実行を要求している行のコレクションとともにこのResultSet
を格納します。
protected void executeQueryForCollection(Object qc,Object[] params, int numUserParams) { storeNewResultSet(qc,retrieveRefCursor(qc,params)); super.executeQueryForCollection(qc, params, numUserParams); }
retrieveRefCursor()
は、25.5項「ストアド・プロシージャとストアド・ファンクションの呼出し」で説明されているヘルパー・メソッドを使用して、ストアド・ファクションを呼び出し、REF CURSOR
を返します。
private ResultSet retrieveRefCursor(Object qc, Object[] params) { ResultSet rs = (ResultSet)callStoredFunction(OracleTypes.CURSOR, "RefCursorExample.get_requests_for_tech(?)", new Object[]{getNamedBindParamValue("Email",params)}); return rs ; }
フレームワークは、データ・ソースからフェッチする必要のある行ごとに、オーバーライドされたcreateRowFromResultSet()
メソッドを呼び出します。実装は、コレクション固有のResultSet
オブジェクトをユーザー・データ・コンテキストから取得し、createNewRowForCollection()
メソッドを使用して新しい空白の行をコレクション内に作成した後、populateAttributeForRow()
メソッドを使用して、ビュー・オブジェクト・エディタで設計時に定義された各属性に属性値を移入します。
protected ViewRowImpl createRowFromResultSet(Object qc, ResultSet rs) { /* * We ignore the JDBC ResultSet passed by the framework (null anyway) and * use the resultset that we've stored in the query-collection-private * user data storage */ rs = getResultSet(qc); /* * Create a new row to populate */ ViewRowImpl r = createNewRowForCollection(qc); try { /* * Populate new row by attribute slot number for current row in Result Set */ populateAttributeForRow(r,0, rs.getLong(1)); populateAttributeForRow(r,1, rs.getString(2)); populateAttributeForRow(r,2, rs.getString(3)); } catch (SQLException s) { throw new JboException(s); } return r; }
フレームワーク・メソッドhasNextForCollection()
のオーバーライドされた実装は、フェッチする行がまだあるかどうかに基づいて、true
またはfalse
を返します。最後に達したら、setFetchCompleteForCollection()
を呼び出して、このコレクションでは移入が終了したことをビュー・オブジェクトに伝えます。
protected boolean hasNextForCollection(Object qc) { ResultSet rs = getResultSet(qc); boolean nextOne = false; try { nextOne = rs.next(); /* * When were at the end of the result set, mark the query collection * as "FetchComplete". */ if (!nextOne) { setFetchCompleteForCollection(qc, true); /* * Close the result set, we're done with it */ rs.close(); } } catch (SQLException s) { throw new JboException(s); } return nextOne; }
コレクションのフェッチ処理が終了すると、オーバーライドされたreleaseUserDataForCollection()
メソッドが呼び出されて、データベース・カーソルが開いたままにならないように確実にResultSet
を閉じます。
protected void releaseUserDataForCollection(Object qc, Object rs) { ResultSet userDataRS = getResultSet(qc); if (userDataRS != null) { try { userDataRS.close(); } catch (SQLException s) { /* Ignore */ } } super.releaseUserDataForCollection(qc, rs); }
最後に、ビュー・オブジェクトのgetEstimatedRowCount()
メソッドを正しくサポートするため、オーバーライドされたgetQueryHitCount()
メソッドは、すべての行が行セットからフェッチされた場合に取得された行のカウントを返します。ここで、コードはCallableStatement
を使用して処理を行います。問合せはストアド・ファンクションAPIの背後に完全にカプセル化されているため、コードもカウント・ロジックの実装およびこの機能のサポートをPL/SQLパッケージに依存しています。
public long getQueryHitCount(ViewRowSetImpl viewRowSet) { Object[] params = viewRowSet.getParameters(true); BigDecimal id = (BigDecimal)params[0]; CallableStatement st = null; try { st = getDBTransaction().createCallableStatement(COUNTSQL, DBTransaction.DEFAULT); /* * Register the first bind parameter as our return value of type CURSOR */ st.registerOutParameter(1,Types.NUMERIC); /* * Set the value of the 2nd bind variable to pass id as argument */ if (id == null) st.setNull(2,Types.NUMERIC); else st.setBigDecimal(2,id); st.execute(); return st.getLong(1); } catch (SQLException s) { throw new JboException(s); } finally {try {st.close();} catch (SQLException s) {}} }
SRDemoアプリケーションのFrameworkExtensions
プロジェクトのSRStaticDataViewObjectImpl
クラスが提供するプログラムによるビュー・オブジェクトの実装を拡張し、コードと記述の検索データを静的データからメモリー内配列に移入できます。
例27-29で示されているように、次のタスクが、プログラムによるビュー・オブジェクトの主要なメソッドのオーバーライドされた実装で実行されます。
create()
ビュー・オブジェクトが作成されると、メモリー内配列からデータがロードされます。ヘルパー・メソッドを呼び出してコードと記述のcodesAndDescriptions
配列を設定し、このビュー・オブジェクトに対する問合せのすべてのトレースを削除します。
executeQueryForCollection()
データは静的であるため、実際には問合せを実行する必要はありません。しかし、super
を呼び出して、行セットに対するフレームワークの他のセットアップが正常に行われるようにする必要があります。create()
メソッドで問合せのトレースは無効になっているため、ビュー・オブジェクトはsuper
を呼び出す間に実際に問合せを実行することはありません。
hasNextForCollection()
fetchPosition
がまだメモリー内配列の行数より小さい場合、コードはtrue
を返します。
createRowFromResultSet()
ベース・ビュー・オブジェクトの実装から要求されると、フェッチされたデータを1行に移入します。codesAndDescriptions
配列からデータを取得し、1番目と2番目の属性をビュー・オブジェクトの行に移入します(ゼロから始まるインデックス位置)。
getQueryHitCount()
コードは、前にrows
メンバー・フィールドに格納されているcodesAndDescriptions
配列の行数を返します。
さらに、他にも次のメソッドがデータの設定を助けます。
setFetchPos()
問合せコレクションの現在のフェッチ位置を設定します。ビュー・オブジェクトを使用して複数の行セットを作成できるので、ユーザー・データのコンテキストで各行セットの現在のフェッチ位置を追跡する必要があります。setFetchCompleteForCollection()
を呼び出して、行のフェッチが完了したことをビュー・オブジェクトに通知します。
getFetchPos()
問合せコレクションの現在のフェッチ位置を取得します。ユーザー・データ・コンテキストに格納された特定の行セットに対するフェッチ位置を返します。
initializeStaticData()
サブクラスはこのメソッドをオーバーライドして、表示用の静的データを初期化します。
setCodesAndDescriptions()
このビュー・オブジェクトに対する静的なコードと記述を設定します。
例27-29 静的な配列からデータを移入するためのカスタム・ビュー・オブジェクト・クラス
package oracle.srdemo.model.frameworkExt; // Imports omitted public class SRStaticDataViewObjectImpl extends SRViewObjectImpl { private static final int CODE = 0; private static final int DESCRIPTION = 1; int rows = -1; private String[][] codesAndDescriptions = null; protected void executeQueryForCollection(Object rowset, Object[] params, int noUserParams) { // Initialize our fetch position for the query collection setFetchPos(rowset, 0); super.executeQueryForCollection(rowset, params, noUserParams); } // Help the hasNext() method know if there are more rows to fetch or not protected boolean hasNextForCollection(Object rowset) { return getFetchPos(rowset) < rows; } // Create and populate the "next" row in the rowset when needed protected ViewRowImpl createRowFromResultSet(Object rowset,ResultSet rs) { ViewRowImpl r = createNewRowForCollection(rowset); int pos = getFetchPos(rowset); populateAttributeForRow(r, 0, codesAndDescriptions[pos][CODE]); populateAttributeForRow(r, 1, codesAndDescriptions[pos][DESCRIPTION]); setFetchPos(rowset, pos + 1); return r; } // When created, initialize static data and remove trace of any SQL query protected void create() { super.create(); // Setup string arrays of codes and values from VO custom properties initializeStaticData(); rows = (codesAndDescriptions != null) ? codesAndDescriptions.length : 0; // Wipe out all traces of a query for this VO getViewDef().setQuery(null); getViewDef().setSelectClause(null); setQuery(null); } // Return the estimatedRowCount of the collection public long getQueryHitCount(ViewRowSetImpl viewRowSet) { return rows; } // Subclasses override this to initialize their static data protected void initializeStaticData() { setCodesAndDescriptions(new String[][]{ {"Code1","Description1"}, {"Code2","Description2"} }); } // Allow subclasses to initialize the codesAndDescriptions array protected void setCodesAndDescriptions(String[][] codesAndDescriptions) { this.codesAndDescriptions = codesAndDescriptions; } // Store the current fetch position in the user data context private void setFetchPos(Object rowset, int pos) { if (pos == rows) { setFetchCompleteForCollection(rowset, true); } setUserDataForCollection(rowset, new Integer(pos)); } // Get the current fetch position from the user data context private int getFetchPos(Object rowset) { return ((Integer)getUserDataForCollection(rowset)).intValue(); } }
SRDemoアプリケーションのServiceRequestStatusList
ビュー・オブジェクトは、Code
およびDescription
という名前の2つのString属性を定義し、SRStaticDataViewObjectImpl
クラスを拡張します。initializeStaticData()
メソッドをオーバーライドして、正しいサービス・リクエスト・ステータス・コードの値を提供します。
public class ServiceRequestStatusListImpl extends SRStaticDataViewObjectImpl { protected void initializeStaticData() { setCodesAndDescriptions(new String[][]{ {"Open","Open"}, {"Pending","Pending"}, {"Closed","Closed"} }); } }
ビュー・オブジェクトに対する静的データをJavaクラス自体にコンパイルするのではなく、次のようなName
=
Value
の形式の標準のJavaプロパティ・ファイルに外部化すると便利な場合があります。
#This is the property file format. Comments like this are ok US=United States IT=Italy
SRDemoアプリケーションのSRPropertiesFileViewObjectImpl
は、SRStaticDataViewObjectImpl
を拡張してinitializeStaticData()
メソッドをオーバーライドし、例27-30で示されているloadDataFromPropertiesFile()
メソッドを呼び出して、プロパティ・ファイルから静的データを読み取ります。このメソッドが実行する基本的な手順は次のとおりです。
ビュー定義名に基づいてプロパティ・ファイルの名前を導出します。
たとえば、このクラスを拡張するx
.
y
.
z
.queries
パッケージのCountryList
ビュー・オブジェクトは、./
x
/
y
/
z
/queries/CountryList.properties
という名前のプロパティ・ファイルから読み取るものと予想します。
名前=値のペアを保持するリストを初期化します。
入力ストリームを開き、クラス・パスからプロパティ・ファイルを読み込みます。
プロパティ・ファイルの各行をループします。
行に等号が含まれ、シャープで始まるコメント行でない場合は、{
code
,
description
}
の文字列配列をリストに追加します。
行番号リーダーと入力ストリームを閉じます。
2次元String配列として格納しているリストを返します。
例27-30 ビュー・オブジェクトに対する静的データのプロパティ・ファイルからの読込み
// In SRPropertiesFileViewObjectImpl.java private synchronized String[][] loadDataFromPropertiesFile() { // 1. Derive the property file name based on the view definition name String propertyFile = getViewDef().getFullName().replace('.', '/') + ".properties"; // 2. Initialize a list to hold the name=value pairs List codesAndDescriptionsList = new ArrayList(20); try { // 3. Open an input stream to read the properties file from the class path InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream(propertyFile); LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is)); String line = null; // 4. Loop over each line in the properties file while ((line = lnr.readLine()) != null) { line.trim(); int eqPos = line.indexOf('='); if ((eqPos >= 1) && (line.charAt(0) != '#')) { // 5. If line contains "=" and isn't a comment, add String[] // of {code,description} to the list codesAndDescriptionsList.add(new String[]{ line.substring(0, eqPos), line.substring(eqPos + 1)}); } } // 6. Close the line number reader and input stream lnr.close(); is.close(); } catch (IOException iox) { iox.printStackTrace(); return new String[0][0]; } // 7. Return the list contains as a two-dimensional String array return (String[][])codesAndDescriptionsList.toArray(); }
アプリケーションで提供されている例のクラスのいずれかを拡張する独自のビュー・オブジェクトを静的データで作成するには、Code
およびDescription
という名前のString属性を持つ新しい読取り専用のプログラムによるビュー・オブジェクトを定義します。ビュー・オブジェクト・エディタの「Java」パネルで、「クラスの拡張」をクリックして、「オブジェクト」フィールドのカスタム・クラスとして、SRStaticDataViewObjectImpl
クラスまたはSRPropertiesFileViewObjectImpl
クラスの完全修飾名を指定します。次に、ビュー・オブジェクトに対するカスタムJavaクラスを有効にして、次のことを行います。
SRStaticDataViewObjectImpl
を拡張する場合...initializeStaticData()
メソッドをオーバーライドし、示されているloadDataFromPropertiesFile()
メソッドを呼び出します。
SRPropertiesFileViewObjectImpl
を拡張する場合...適切な*.propertiesファイルを、ビュー・オブジェクトのXMLコンポーネント定義と同じディレクトリに、ビュー・オブジェクトの名前と一致する名前(ViewObjectName
.properties
)で作成します。
注意: この項で説明する例を試すには、26.7項「制約違反を防ぐためのエンティティ・ポスト順序の制御」で使用したものと同じAdvancedEntityExamples ワークスペースのControllingPostingOrder プロジェクトを使用します。 |
複数のエンティティ・オブジェクトの慣用名を持つビュー・オブジェクトを作成するときは、ビュー・オブジェクト・エディタの「エンティティ・オブジェクト」パネルの「選択済」リストで選択し、次の設定を行うことで、2次エンティティ・オブジェクトの慣用名を更新可能にすることができます。
「参照」チェック・ボックスを選択
「更新可能」チェック・ボックスを選択解除
ビュー・オブジェクトを使用して既存データの更新または削除のみを行う場合は、必要な手順はこれのみです。ユーザーは非参照の更新可能なエンティティ・オブジェクト慣用名に関連する属性を更新でき、ビュー行は適切な基礎のエンティティ行に変更を委譲します。
しかし、新しい行の作成をサポートするために複数の更新可能エンティティを持つビュー・オブジェクトが必要な場合は、コードを少し記述して、この機能が正しく動作するようにする必要があります。複数の更新可能エンティティを持つビュー・オブジェクトでcreateRow()
を呼び出すと、更新可能なエンティティ・オブジェクトの慣用名ごとに新しいエンティティ行の部分が作成されます。このシナリオの複数エンティティはアソシエーションによって関連付けられているので、新しい関連のあるエンティティ行を正常に保存できるようにするには、3つのコードを実装する必要があります。
正しいポスト順序を制御するために含まれるエンティティ・オブジェクトで、postChanges()
メソッドをオーバーライドすることが必要な場合があります。
関連付けられたエンティティの主キーがDBSequence
を使用してデータベース順序によって移入され、複数のエンティティ・オブジェクトが関連付けられてはいても、コンポジットではない場合は、postChanges()
メソッドとrefreshFKInNewContainees()
メソッドをオーバーライドし、更新された主キー値を、一時的な値を参照していた関連行にカスケードする必要があります。
ビュー・オブジェクトのカスタム・ビュー行クラスのcreate()
メソッドをオーバーライドし、親エンティティ・オブジェクトのコンテキストを新しく作成される子エンティティに渡すよう、デフォルトの行作成動作を変更する必要があります。
前述の1および2に必要なコードについては、26.7項「制約違反を防ぐためのエンティティ・ポスト順序の制御」の関連付けられたProduct
およびServiceRequest
エンティティ・オブジェクトの例ですでに説明しました。残っているのは、ビュー行でのオーバーライドされたcreate()
メソッドのみです。1次エンティティ・オブジェクトの慣用名がServiceRequest
、2次エンティティ・オブジェクトの慣用名がProduct
およびUser
である、ServiceRequestAndProduct
ビュー・オブジェクトについて考えます。Product
エンティティ・オブジェクトの慣用名は更新可能で非参照としてマークされ、User
エンティティ・オブジェクトの慣用名は参照エンティティ・オブジェクトの慣用名であるものとします。
例27-31は、ビュー行作成操作の間に、複数の更新可能エンティティ行部分を正しい順序で作成するために必要なコメント付きのコードを示しています。
例27-31 複数の更新可能エンティティの場合のビュー行create()メソッドのオーバーライド
/** * By default, the framework will automatically create the new * underlying entity object instances that are related to this * view object row being created. * * We override this default view object row creation to explicitly * pre-populate the new (detail) ServiceRequestImpl instance using * the new (master) ProductImpl instance. Since all entity objects * implement the AttributeList interface, we can directly pass the * new ProductImpl instance to the ServiceRequestImpl create() * method that accepts an AttributeList. */ protected void create(AttributeList attributeList) { // The view row will already have created "blank" entity instances ProductImpl newProduct = getProduct(); ServiceRequestImpl newServiceRequest = getServiceRequest(); try { // Let product "blank" entity instance to do programmatic defaulting newProduct.create(attributeList); // Let service request "blank" entity instance to do programmatic // defaulting passing in new ProductImpl instance so its attributes // are available to the EmployeeImpl's create method. newServiceRequest.create(newProduct); } catch (JboException ex) { newProduct.revert(); newServiceRequest.revert(); throw ex; } catch (Exception otherEx) { newProduct.revert(); newServiceRequest.revert(); throw new RowCreateException(true /* EO Row? */, "Product" /* EO Name */, otherEx /* Details */); } }
このビュー行クラスが、Product
およびServiceRequest
エンティティ・オブジェクトで保護されたcreate()
メソッドを呼び出せるようにするには、create()
メソッドをオーバーライドする必要があります。ビュー・オブジェクトとエンティティ・オブジェクトが同じパッケージ内にある場合、オーバーライドされたcreate()
メソッドは保護されたアクセスが可能です。それ以外の場合は、public
アクセスが必要です。
/** * Overridding this method in this class allows friendly access * to the create() method by other classes in this same package, like the * ServiceRequestsAndProduct view object implementation class, whose overridden * create() method needs to call this. * @param nameValuePair */ protected void create(AttributeList nameValuePair) { super.create(nameValuePair); }
Oracle Formsなどの一部の4GLツールでは、特定のデータ・コレクションが挿入、更新または削除を許可するかどうかを制御する宣言的プロパティが提供されています。現在のリリースでは、まだビュー・オブジェクトはこれを組込み機能としてサポートしていませんが、ビュー・オブジェクトでの挿入、更新または削除を制御するための開発者提供のフラグとしてカスタム・メタデータ・プロパティを利用するフレームワーク拡張クラスを使用すれば、この機能を簡単に追加できます。
注意: この項の例では、AdvancedViewObjectExamples ワークスペースのDeclarativeBlockOperations プロジェクトを参照します。ダウンロード方法については、この章の最初にある注意を参照してください。 |
開発者が個別のビュー・オブジェクト・インスタンスを制御できるようにするには、ビュー・オブジェクト・インスタンスと同じ名前でアプリケーション・モジュール・カスタム・プロパティを使用するという規則を導入します。たとえば、アプリケーション・モジュールにProductsInsertOnly
、ProductsUpdateOnly
、ProductsNoDelete
およびProducts
という名前のビュー・オブジェクト・インスタンスがある場合、汎用コードでは、これらと同じ名前でアプリケーション・モジュール・カスタム・プロパティを検索します。プロパティ値にInsert
が含まれている場合は、そのビュー・オブジェクト・インスタンスでは挿入が有効です。プロパティにUpdate
が含まれる場合は、更新が許可されています。同様に、プロパティ値にDelete
が含まれる場合は、削除が許可されています。次のようなヘルパー・メソッドを使用してこれらのアプリケーション・モジュール・プロパティを検査し、挿入、更新および削除が特定のビュー・オブジェクトに対して許可されているかどうかを判定できます。
private boolean isInsertAllowed() { return isStringInAppModulePropertyNamedAfterVOInstance("Insert"); } private boolean isUpdateAllowed() { return isStringInAppModulePropertyNamedAfterVOInstance("Update"); } private boolean isDeleteAllowed() { return isStringInAppModulePropertyNamedAfterVOInstance("Delete"); } private boolean isStringInAppModulePropertyNamedAfterVOInstance(String s) { String voInstName = getViewObject().getName(); String propVal = (String)getApplicationModule().getProperty(voInstName); return propVal != null ? propVal.indexOf(s) >= 0 : true; }
例27-32は、ビュー行が実装を完了するために、カスタム・フレームワーク拡張クラスで必要な他のコードを示しています。これは、次のメソッドをオーバーライドします。
isAttributeUpdateable()
挿入が許可されていない場合に新しい行のフィールドを無効にするため、または更新が許可されていない場合に既存の行のフィールドを無効にするために、ユーザー・インタフェースを有効化します。
setAttributeInternal()
挿入が許可されていない場合に新しい行の属性値が設定されないようにします。また、更新が許可されていない場合に既存の行の属性が設定されないようにします。
remove()
削除が許可されない場合に削除を禁止します。
create()
挿入が許可されない場合に作成を禁止します。
例27-32 カスタム・プロパティに基づく挿入、更新、または削除の禁止
public class CustomViewRowImpl extends ViewRowImpl { public boolean isAttributeUpdateable(int index) { if (hasEntities() && ((isNewOrInitialized() && !isInsertAllowed()) || (isModifiedOrUnmodified() && !isUpdateAllowed()))) { return false; } return super.isAttributeUpdateable(index); } protected void setAttributeInternal(int index, Object val) { if (hasEntities()) { if (isNewOrInitialized() && !isInsertAllowed()) throw new JboException("No inserts allowed in this view"); else if (isModifiedOrUnmodified() && !isUpdateAllowed()) throw new JboException("No updates allowed in this view"); } super.setAttributeInternal(index, val); } public void remove() { if (!hasEntities() || isDeleteAllowed() || isNewOrInitialized()) super.remove(); else throw new JboException("Delete not allowed in this view"); } protected void create(AttributeList nvp) { if (isInsertAllowed()) { super.create(nvp); } else { throw new JboException("Insert not allowed in this view"); } } // private helper methods omitted }