Oracle® Fusion Middleware Oracle Application Development FrameworkによるFusion Webアプリケーションの開発 12c (12.1.3) E59441-03 |
|
![]() 前 |
![]() 次 |
この章では、Oracle ADFアプリケーションでADFビュー・オブジェクトを設計および利用するときに使用できる高度な手法について説明します。
この章の内容は次のとおりです。
ここでは、これまでの章で解説されていないビュー・オブジェクトの興味深い概念と機能について説明します。
アプリケーションでビュー・オブジェクトの問合せ結果行を反復処理する必要がある場合は、フェッチするレコードの最大数を指定することをお薦めします。ビュー・オブジェクトのデフォルトの最大フェッチ・サイズは-1で、フェッチできる行数に制限がないことを示します。デフォルトでは、行は必要に応じてフェッチされるので、-1という値はビュー・オブジェクトがすべての行をフェッチする必要があることを意味するわけではありません。問合せ結果のすべての行を反復処理しようとした場合に、すべての行が取得されることを意味しているにすぎません。
ビュー・オブジェクトで取得される行の最大数に上限を設けるには、次の設定を使用します。
adf-config.xmlファイルの概要エディタの「ビジネス・コンポーネント」ページで、「行フェッチ制限」
プロパティを使用して、すべてのビュー・オブジェクト問合せのグローバルしきい値を構成できます。このファイルは、「アプリケーション・リソース」パネルで「ディスクリプタ」および「ADF META-INF」ノードを開いて特定できます。
注意: 行フェッチ制限ではそのアプリケーションのすべての問合せ操作のグローバルしきい値が指定されるため(イテレータ結果セットの予測行カウントを決定するイテレータ・バインディング・プロパティRowCountThresholdを含む)、このプロパティを使用すると、その操作のデフォルト動作ではすべての行をフェッチできる場合に、個々の問合せ操作の設定を変更しなくて済みます。個々のビュー・オブジェクトに「最大フェッチ・サイズ」を使用してフェッチ制限を指定する場合、「行フェッチ制限」の設定は無視されます。ビュー・オブジェクトに適した値を返すようにビュー・オブジェクトの実装クラスでgetRowLimit()をオーバーライドする場合は、「行フェッチ制限」
の設定も無視されます。
ビュー・オブジェクトの概要エディタの「一般」ページにある「チューニング」セクションで、「行番号までのみ」フィールドを選択すると、特定のビュー・オブジェクト問合せのしきい値を構成できます。プロパティ・インスペクタでは、この設定が「最大フェッチ・サイズ」プロパティに表示されることに注意してください。
ヒント: 「行フェッチ制限」を使用して問合せ操作のグローバルしきい値を設定する際、特定のビュー・オブジェクト問合せで使用可能なすべての行が戻されるようにする場合は、該当するビュー・オブジェクトの「行番号までのみ」フィールドを非常に大きい数値にして「最大フェッチ・サイズ」を設定できます。 |
たとえば、ORDER BY
句を含む問合せを作成し、ページに上位N項目を表示するために最初のn行を返す場合は、ビュー・オブジェクトの概要エディタを使用して、「一般」ページの「チューニング」セクションにある「行番号までのみ」フィールドの値を指定できます。たとえば、最初の5行のみをフェッチするには、このフィールドに5を入力します。これは、ビュー・オブジェクトでsetMaxFetchSize()
メソッドをコールして最大フェッチ・サイズを5
に設定するのと同じです。ビュー・オブジェクトは、最大フェッチ・サイズに達すると行のフェッチを停止します。通常は、この手法と組み合せて、概要エディタの「一般」ページの「チューニング」セクションで、FIRST_ROWSの「問合せオプティマイザ・ヒント」
も指定します。これにより、すべての行の取得の最適化を試みるのではなく、できるかぎり早く最初の何行かを取得するよう、データベースにヒントが提供されます。
1つのアプリケーション・モジュールで、エンティティ・ベースのビュー・オブジェクトの複数インスタンスが同じエンティティ・オブジェクトに基づいているときは、1つのインスタンスで作成された新しい行を、他のインスタンスの行セットに(再問合せを行うことなく)自動的に追加して、ユーザー・インタフェースの整合性を保つか、保留中のトランザクションの異なるアプリケーション・ページに新しい行を一貫して反映できます。顧客のオーダーのリストを表示するアプリケーションを考えてみます。顧客が新しいオーダーを作成する場合、このタスクは別のビュー・オブジェクトを介して実行され、カスタム・アプリケーション・モジュールのメソッドによって処理されます。このビュー・オブジェクトの新規行の整合機能を使用すると、新しく作成されたオーダーが、データベースに再度問い合せることなく、顧客の未処理オーダーのリストに自動的に表示されます。
歴史的な理由から、この機能はビュー・リンクの一貫性機能と呼ばれています。これは、Oracle Application Development Framework (Oracle ADF)の以前のリリースでは、アソシエーションに基づくビュー・リンクでの詳細ビュー・オブジェクト・インスタンスのサポートは、他の関連する行セットに対する新規行の追加にかぎられていたためです。現在では、このビュー・リンク一貫性機能は、ビュー・リンクに関連性があるかどうかには関係なく、この機能が有効になっているすべてのビュー・オブジェクトで機能します。
同じOrders
エンティティ・オブジェクトに基づく2つのエンティティ・ベースのビュー・オブジェクトOrdersViewSummary
とOrdersView
について考えます。一方のビュー・オブジェクト(OrdersView
など)の行セットで新しい行が作成され、行の主キー行が設定されると、同じOrdersエンティティ・オブジェクトに基づくビュー・オブジェクト(OrdersViewSummary
など)の他
の行セットは、新しい行が作成されたことを示すイベントを受け取ります。ビュー・リンク一貫性フラグが有効になっていると、新しい行のコピーの行セットへの挿入も行われます。
警告: デフォルトでは、ビュー・リンク一貫性のメカニズムにより、新しい行は無条件に追加されます。関係するすべてのビュー・オブジェクトの行セットに新規行が追加されないようにする場合は、RowMatchオブジェクトにフィルタ式を記述して、行セットに追加される前に行を制限できます。詳細は、9.1.2.3項「行セットに追加されるポストされていない新規行を制限するためのRowMatchの使用」を参照してください。 |
「構成の編集」ダイアログを使用し、jbo.viewlink.consistent
構成パラメータを使用して、ビュー・リンク一貫性機能のデフォルト設定を制御できます。図9-1を参照してください。
構成エディタを表示するには、「アプリケーション」ウィンドウでアプリケーション・モジュールをダブルクリックして、概要エディタの「構成」ナビゲーション・タブを選択します。次に、概要エディタの「構成」ページで構成を選択し、「編集」をクリックします。「構成の編集」ダイアログで、「プロパティ」タブを選択します。このパラメータのデフォルト設定はDEFAULT
で、次のような意味があります。ビュー・オブジェクトが次の状態であるものとします。
単一のエンティティ・オブジェクトの慣用名、ビュー・リンク一貫性は有効
複数のエンティティ・オブジェクトの慣用名、かつ
すべてのセカンダリ・エンティティ・オブジェクトの慣用名が寄与する参照情報としてマークされている場合は、ビュー・リンク一貫性が有効
いずれかのセカンダリ・エンティティ・オブジェクトの慣用名が参照としてマークされていない場合は、ビュー・リンク一貫性が無効
この場合は、構成でjbo.viewlink.consistent
に値false
を設定することで、この機能をグローバルに無効にできます。逆に、jbo.viewlink.consistent
に値true
を設定するとこの機能をグローバルに有効にできますが、このように設定することはお薦めしません。有効に設定すると、現時点ではビュー・リンク一貫性機能がサポートされていない参照としてマークされていないセカンダリ・エンティティ・オブジェクトの慣用名を持つビュー・オブジェクトに対しても、強制的にビュー・リンク一貫性が設定されます。
この機能をプログラムで設定するには、任意のRowSet
でsetAssociationConsistent()
APIを使用します。ビュー・オブジェクトでこのメソッドを呼び出すと、デフォルトの行セットに適用されます。
ビュー・オブジェクトでビュー・リンク一貫性が有効になっている場合は、同じエンティティ・オブジェクトに基づく別のビュー・オブジェクトで作成された新しい行が、行セットに追加されます。デフォルトでは、新しい行は無条件に追加されます。ビュー・オブジェクトに、行の特定のサブセットのみを問い合せる設計時WHERE
句がある場合は、ビュー・オブジェクトにRowMatch
オブジェクトを適用して、同じフィルタ処理をメモリー内で実行できます。指定するRowMatch
オブジェクトのフィルタ式により、そのオブジェクトに表示しても意味のない新規行は追加されません。
たとえば、OrdersByStatus
ビュー・オブジェクトには、次のような設計時WHERE
句が含まれることがあります。
WHERE /* ... */ AND STATUS LIKE NVL(:StatusCode,'%')
そのカスタムJavaクラスでは、create()
メソッドが例9-1に示すようにオーバーライドされており、ビュー・リンク一貫性を強制的に有効にします。また、Status
属性が:StatusCode
名前付きバインド変数の値と一致する行(または:StatusCode
='%
'の場合はすべての行)に一致するフィルタ式を含むRowMatch
オブジェクトを適用します。ビュー・リンク一貫性メカニズムは、このRowMatch
フィルタを使用して、行セットに追加する候補の行を限定します。RowMatch
で適格とされた行は追加されます。そうでない行は追加されません。
例9-1 追加される新規行を制御するためのカスタムRowMatchの提供
// In OrdersByStatusImpl.java protected void create() { super.create(); setAssociationConsistent(true); setRowMatch(new RowMatch("Status = :StatusCode or :StatusCode = '%'")); }
RowMatchオブジェクトの作成と使用の詳細は、10.3.4項「RowMatchによるメモリー内フィルタ処理の実行」
を参照してください。サポートされているSQL演算子のリストは、表10-2を参照してください。サポートされているSQL関数のリストは、表10-3を参照してください。
注意: RowMatch 機能では十分に制御できない場合は、ビュー・オブジェクトのrowQualifies() メソッドをオーバーライドして、カスタム・フィルタ処理ソリューションを実装できます。コードを使用して、新しい行をビュー・リンク一貫性メカニズムで追加するかどうかを決定できます。 |
ビュー・オブジェクトでsetWhereClause()
を呼び出して動的WHERE
句を設定すると、そのビュー・オブジェクトのビュー・リンク一貫性機能は無効になります。行セットに追加する新しい行を限定するために適切なカスタムRowMatch
オブジェクトを提供していた場合は、setWhereClause()
の後でsetAssociationConsistent(true)
を呼び出すことで、ビュー・リンク一貫性を再び有効にできます。
行セットでビュー・リンク一貫性が有効になっている場合、他の行セットで作成されたために追加される新しい行は、行セットの最後に追加されます。
行セットでビュー・リンク一貫性が有効になっている場合、executeQuery()
メソッドを呼び出すと、適格なポストされていない新規行は、問合せによってデータベースから取得された行が追加される前に、行セットの先頭に追加されます。
ビュー・オブジェクトは、次の2種類のスタイルのマスター/ディテール調整をサポートします。
データ・モデルのマスター/ディテール調整のためのビュー・リンク・インスタンス。9.1.3.1項「アクティブなマスター/ディテール調整による動的ディテール行セットの有効化」を参照してください。
ディテール行セットにオン・デマンドでプログラム的にアクセスするためのビュー・リンク・アクセッサ属性。9.1.3.2項「ビュー・リンク・アクセッサ属性を使用した固定ディテール行セットへのアクセス」を参照してください。
両方のスタイルを組み合せることができます。9.1.3.3項「アクセッサ属性が作成する内部ビュー・オブジェクトに基づく個別の行セット」を参照してください。
ビュー・リンク・インスタンスをアプリケーション・モジュールのデータ・モデルに追加するときは、2つの特定のビュー・オブジェクト・インスタンスを接続します。ビュー・リンク・インスタンスの使用は、2つの間にアクティブなマスター/ディテール調整が必要なことを示します。実行時には、データ・モデル内のビュー・リンク・インスタンスにより、このコーディネーションを有効にするイベント処理が容易になります。マスター・ビュー・オブジェクト・インスタンスで現在行が変更されるたびに、イベントが発生し、マスター・ビュー・オブジェクトの新しい現在行に対する新しいバインド・パラメータのセットでexecuteQuery()
が自動的に呼び出されることで、ディテール・ビュー・オブジェクトがリフレッシュされます。
このデータ・モデル・マスター/ディテールの重要な特徴は、マスターおよびディテール・ビュー・オブジェクト・インスタンスが、クライアントのユーザー・インタフェースがバインディングを確立できる固定のオブジェクトであることです。マスターで現在行が変化すると、新しいディテール・ビュー・オブジェクト・インスタンスが生成されるのではなく、既存のディテール・ビュー・オブジェクト・インスタンスが、マスターの新しい現在行に関連する行のセットを含むように、デフォルトの行セットを更新します。さらに、ユーザー・インタフェースのバインディング・オブジェクトはイベントを受け取り、ディテール・ビュー・オブジェクトのリフレッシュされた行セットを含むように表示を更新できます。
データ・モデル階層のみが備えるもう1つの重要な機能は、1つのディテール・ビュー・オブジェクト・インスタンスが複数のマスター・ビュー・オブジェクト・インスタンスを持つことができることです。たとえば、PaymentOptions
ビュー・オブジェクト・インスタンスは、Customersビュー・オブジェクト・インスタンスとOrders
ビュー・オブジェクト・インスタンスの両方
のディテールになることができます。Customersまたは
Orders
のビュー・オブジェクト・インスタンスの現在行が変わるたびに、ディテールPaymentOptions
ビュー・オブジェクト・インスタンスのデフォルトの行セットは、現在の顧客および現在のオーダーの支払情報の行を含むようにリフレッシュされます。複数マスターのディテール・ビュー・オブジェクト・インスタンスを設定する方法の詳細は、9.1.6項「複数マスターのデータ・モデルの設定」を参照してください。
ビュー・リンクによりビュー・オブジェクト行に関係するディテール行セットにプログラムでアクセスする必要がある場合は、ビュー・リンク・アクセッサ属性を使用できます。ビュー・リンク・アクセッサ属性のファインダ名は、ビュー・リンクの概要エディタから指定します。「リレーションシップ」ページの「アクセッサ」セクションにある「編集」アイコンをクリックし、「ビュー・リンク・プロパティの編集」ダイアログでビュー・リンク・アクセッサ属性の名前を編集します。
例9-2は、<Attr>
要素の_findername
値を定義するビュー・リンクのXMLを示しています。
例9-2 ビュー・リンク・アクセッサ属性名
<ViewLinkDefEnd Name="Orders" Cardinality="1" Owner="devguide.advanced.multiplemasters.Orders" Source="true"> <AttrArray Name="Attributes"> <Item Value="devguide.advanced.multiplemasters.Orders.PaymentOptionId"/> </AttrArray> <DesignTime> <Attr Name="_minCardinality" Value="1"/> <Attr Name="_isUpdateable" Value="true"/> <Attr Name="_finderName" Value="Orders"/> </DesignTime> </ViewLinkDefEnd>
アクセッサ属性の名前がAccessorAttrName
であるとすると、次のような汎用getAttribute()
APIを使用してディテール行セットにアクセスできます。
RowSet detail = (RowSet)currentRow.getAttribute("AccessorAttrName");
マスター・ビュー・オブジェクトに対するカスタム・ビュー行クラスを生成し、クライアント・ビュー行インタフェースでビュー・リンク・アクセッサ属性に対するgetterメソッドを公開してある場合は、次のような強く型付けされたコードを使用してディテール行セットにアクセスできます。
RowSet detail = (RowSet)currentRow.getAccessorAttrName();
データ・モデル・マスター/ディテールとは異なり、ビュー・リンク・アクセッサ属性のプログラムによるアクセスでは、アプリケーション・モジュールのデータ・モデルにディテール・ビュー・オブジェクト・インスタンスは必要ありません。ビュー・リンク・アクセッサ属性を呼び出すたびに、呼出しを行ったマスター行に関連するディテール行のセットを含むRowSet
が返されます。
ビュー・リンク・アクセッサ属性を使用すると、ディテール・データ行は不変になります。マスター行のビュー・リンク定義に関係する属性値が変わらないかぎり、ディテール・データ行は変化しません。マスターの現在行が変化しても、特定のマスター行にアタッチされているディテール行セットは影響を受けません。このため、ビュー・リンク・アクセッサ属性は、ディテール行の汎用的なプログラム・アクセスに便利なだけでなく、ツリー・コントロールのようなUIオブジェクトにも適しています。ツリー・コントロールでは、ツリー内の各マスター・ノードのデータは、個別のディテール行のセットを維持する必要があります。
データ・モデル・マスター/ディテールと、ビュー・リンク・アクセッサを使用するディテール行セットのプログラム・アクセスを組み合せるときは、これらが個別のメカニズムであることを理解することがいっそう重要になります。たとえば、次のことを行うものとします。
PersonsVO
およびOrdersVO
ビュー・オブジェクトを定義します。
これらの間のビュー・リンクを定義し、ビュー・リンク・アクセッサにPersonsToOrders
という名前を付けます。
これらのインスタンスを、ビュー・リンク・インスタンスによってアクティブに調整されるmaster
(PersonsVO
型)およびdetail
(OrdersVO
型)という名前の、アプリケーション・モジュールのデータ・モデルに追加します。
master
ビュー・オブジェクトのインスタンスで人物を検索すると、detail
ビュー・オブジェクトのインスタンスが更新され、予測どおりに対応する順序が表示されます。この時点で、現在のPersonsVO
行のPersonsToOrders
ビュー・リンク・アクセッサ属性にプログラムでアクセスするカスタム・メソッドを呼び出すと、OrdersVO
行のセットを含むRowSet
が取得されます。一般には、プログラムでアクセスされたこのRowSet
はデータ・モデルのdetail
ビュー・オブジェクト・インスタンスに由来するものと考えられますが、そうではありません。
ビュー・リンク・アクセッサによって返されるRowSet
は、常に内部的に作成されたビュー・オブジェクト・インスタンスからのものであり、ユーザーがデータ・モデルに追加したものではありません。この内部的なビュー・オブジェクト・インスタンスは、必要に応じて作成され、システムが定義する名前でルート・アプリケーション・モジュールに追加されます。
内部的に作成される個別のビュー・オブジェクト・インスタンスが使用される最大の理由は、データ・モデル内のビュー・オブジェクト・インスタンスに対して開発者が行う変更によって、インスタンスが影響を受けないようにするためです。たとえば、ビュー行が、ビュー・リンク・アクセッサのRowSet
に対してデータ・モデル内のディテール・ビュー・オブジェクトを使用する場合、開発者が次のようなことを動的に行うと、結果の行セットが誤って影響を受ける可能性があります。
新しい名前付きバインド・パラメータを含むWHERE
句を追加する。
このようなビュー・オブジェクト・インスタンスがビュー・リンク・アクセッサの結果に使用されると、動的に追加されたWHERE
句のバインド・パラメータ値はビュー・リンク・アクセッサのRowSet
に提供されていないため、予想外の結果またはエラーが発生します。このような値が提供されるのは、データ・モデル内のディテール・ビュー・オブジェクト・インスタンスのデフォルト行セットのみです。
データ・モデルのディテール・ビュー・オブジェクト・インスタンスにマスター・ビュー・オブジェクト・インスタンスを追加する。
このシナリオでは、アクセッサのセマンティクスが変更されます。アクセッサが現在のPersonsVO
行に対するOrdersVO
行を返すかわりに、たとえば現在ログインしている顧客によって作成された現在のPersonsVOに対するOrdersVO
行のみ
を突然返し始めます。
ディテール・ビュー・オブジェクト・インスタンスまたはそれを含むアプリケーション・モジュール・インスタンスを削除する。
このシナリオでは、プログラムでアクセスされたディテール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行のみ含まれます。これは、Fusion Webアプリケーションに対して最善のスタイルです。
RowIterator.ITER_MODE_LAST_PAGE_FULL
このモードでは、最終ページにも1ページ分の行数が表示され、前のページの下部に表示されていた行が、最後のページの上部に表示される場合があります。たとえば、範囲サイズを10に設定し、行セットに23行が含まれる場合は、3ページ目にも10行が表示され、3ページ目の最初から7行には、2ページ目の最後の7行が表示されます。これは、Swingを使用するデスクトップ・フィデリティ・アプリケーションに最適なスタイルです。
一般的な規則として、パフォーマンスを最高にするため、非常に大きな問合せ結果をエンド・ユーザーがスクロールすることがないような方法でアプリケーションを作成することをお薦めします。この推奨事項を実現するには、実際に問合せを実行してユーザーが操作を継続できるようにする前
に、ビュー・オブジェクトでgetEstimatedRowCount()メソッドを呼び出して、ユーザーの問合せで返される行数を判定します。推定される行カウントが不当に大きい場合は、さらに検索基準を提供するようエンド・ユーザーに要求できます。
ただし、非常に大きな結果セット(通常、100行超)を処理する必要がある場合は、範囲ページ移動と呼ばれるビュー・オブジェクトのアクセス・モードを使用して、パフォーマンスを向上することができます。この機能を使用すると、大きなデータ・セットについてデフォルトのスクロール可能アクセス・モードより効率的な方法で、一度にある行範囲のページ単位で、前方および後方にデータを移動できます。
範囲ページ移動アクセス・モードは、通常、読取り専用行セットのページ移動に使用され、読取り専用ビュー・オブジェクトで使用されるのが普通です。ユーザーは範囲ページ移動アクセス・モードを使用し、大きな行セットをページ移動して対象行を検出できます。その後、その行のKey
を使用し、編集用に別のビュー・オブジェクトで選択されている行を検索できます。
ビュー・オブジェクトの場合の範囲ページ移動は、標準アクセス・モードと、範囲ページ移動と結果セット・スクロールの利点を組み合せてデータベース・アクセス回数を最小限に抑える標準アクセス・モードのバリエーションをサポートしています。次に、ビュー・オブジェクト範囲ページ移動機能のこれらのモードについて説明します。
RANGE_PAGING
: 標準アクセス・モードで、範囲サイズで指定された行数をフェッチします。このモードでは、データベースに再問合せを行わずにスクロールできる行数は、設定した範囲サイズで決まります。デフォルトでは1行をフェッチしますが、範囲サイズは、ユーザーが次の結果セットまでスクロールせずに表示できる行数と同じ数に設定するものと想定されています。エンド・ユーザーが範囲外の行にアクセスするたびに、アプリケーションはデータベースに再問合せを行います。このため、行セットを前後にスクロールすると、データベースに再問合せが行われます。このデータベース中心のページ移動戦略の説明は、9.1.5.1項「上位N問合せのサポート方法の理解」を参照してください。
RANGE_PAGING_INCR
: インクリメンタル・アクセス・モードを使用して、一度に表示できる行数をUI設計者がより柔軟に決定でき、データベース問合せ回数も最小限に抑えます。このモードでは、UIは結果セットをメモリー・キャッシュから逐次表示するため、スクロールは1回のデータベース問合せの範囲内でサポートします。エンド・ユーザーが1回の問合せでスクロールできる行数は、範囲サイズと、設定した範囲ページ移動キャッシュ・ファクタによって決まります。たとえば、範囲サイズを4、キャッシュ・ファクタを5に設定したとします。この場合、メモリーにキャッシュする最大行数は、4*5 = 20です。キャッシュの動作の詳細は、9.1.5.4項「範囲ページ移動を使用するときのビュー行のキャッシュ時の処理」を参照してください。
警告: さらに、ビュー・オブジェクトは、行セットの行の挿入と削除に対応するために、RANGE_PAGING_AUTO_POST アクセス・モードをサポートします。このモードはRANGE_PAGING モードと同じように動作しますが、行セットに対してなんらかの変更が行われたときには常に、データベース・トランザクションでpostChanges() を呼び出すところが異なります。ただし、このモードは通常、同一のHTTPリクエスト内でのトランザクションのコミットまたはロールバックが必ず実行される場合を除き、Fusion Webアプリケーションでの使用には適していません。使用した場合、複数の異なるクライアントにより、アプリケーション・モジュールとデータベース接続の両方がプールされ、シリアルに共有されるという事態が環境内で発生する可能性があります。 |
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
の適切な値を管理するコードを記述する必要があります。または、ビュー・オブジェクトの範囲ページ移動アクセス・モードを使用すると、このような機能が自動的に実装されます。
ビュー・オブジェクトの概要エディタの「チューニング」パネルを使用して、アクセス・モードを、標準の範囲ページ移動とインクリメンタル範囲ページ移動のいずれかに設定できます。「範囲ページング・インクリメンタル」を選択すると、「範囲ページング・キャッシュ・ファクタ」フィールドは編集のみ可能になります。図9-2に、ビュー・オブジェクトのアクセス・モードを示します。「範囲ページング」(標準モード)に設定されており、範囲サイズはデフォルトの1です。2つのアクセス・モードでの行セット・キャッシュの動作の詳細は、9.1.5.4項「範囲ページ移動を使用するときのビュー行のキャッシュ時の処理」を参照してください。
ビュー・オブジェクトの標準範囲ページ移動をプログラム的に有効にするには、最初にsetRangeSize()
をコールして1ページの行数を定義した後、必要に応じていずれかのモードで次のメソッドをコールします。
yourViewObject.setAccessMode(RowSet.RANGE_PAGING | RANGE_PAGING_INCR);
RANGE_PAGING_INCR
を設定した場合、次のメソッドもコールして、定義済の範囲サイズに対してキャッシュ・ファクタを設定する必要があります。
yourViewObject.setRangePagingCacheFactor(int f);
ビュー・オブジェクトのアクセス・モードをRANGE_PAGING
に設定すると、ビュー・オブジェクトは次のような問合せをデフォルトとして使用します。
SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
そして、これを自動的にラップし、上位N問合せを生成します。
最善のパフォーマンスのため、文ではBETWEEN
演算子のかわりに、より大と、より小の基準の組合せが使用されますが、論理的な結果は、先に示した上位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_INCR
アクセス・モードで動作するとき、特定の範囲サイズに対して、メモリーにキャッシュする行数は、キャッシュ・ファクタにより決まります。たとえば、範囲サイズを4、キャッシュ・ファクタを5に設定したとします。この場合、メモリーはコレクション中に最大4*5 = 20行を保持します。この例では、範囲が初めてリフレッシュされるとき、範囲ページ移動の問合せが行0 - 19(合計20行)を取得するためにバインドされていても、メモリーには4行のみが保持されます。範囲が4行目を過ぎてスクロールされると、より多くの行が現在の結果セットから読み込まれます。これが、20行すべてが問合せ結果から読み込まれるまで続きます。ユーザーのアクションにより次の行セットが取得される場合、新しい行番号バインド値で問合せが再実行されます。正確な行番号バインド値は、新しい範囲開始位置とキャッシュに保持できる行数で決まります。たとえば、20行すべてにデータが入っており、ユーザーが範囲開始位置を18 (0ベース)に移動するよう指示していると想定します。これは、メモリーが行18と行19を保持でき、範囲を満たすためにはあと2行必要であることを意味します。行20および21に対して問合せが再実行されます。
ビュー・オブジェクトがRANGE_PAGING
アクセス・モードで動作しているときに、ページ番号nにスクロールするには、scrollToRangePage()
メソッドを呼び出し、パラメータ値としてnを渡します。
ビュー・オブジェクトがRANGE_PAGING
アクセス・モードで動作している場合、問合せ結果全体が生成する総ページ数の推定は、getEstimatedRangePageCount()
メソッドを使用して取得できます。
常にRANGE_PAGING
またはRANGE_PAGING_INCR
モードを使用すればよい、というわけではありません。それは、範囲ページ移動を使用すると、ビュー・オブジェクトの行を前後に移動したときに実行される問合せの総量が増加する可能性があるためです。次のような状況では、RANGE_PAGING
モードの使用を避ける必要があります。
行セットのすべての行をただちに読み取ろうと考えている場合(たとえば、ドロップダウン・リストに移入するため)。
この場合は、範囲サイズを-1
に設定し、すべての行を含む単一のページのみが存在するため、範囲ページ移動のメリットはなくなります。
小さいサイズの行セットで前後にページ移動する必要がある場合。
行数が100以下で、RANGE_PAGING
モードを使用して10行ずつページ移動する場合、前後の新しいページに移動するために問合せが実行されます。デフォルトのスクロール可能モードでは、読み取ったビュー・オブジェクト行はキャッシュされ、前のページに戻る方向にページ移動しても、すでに表示された行を表示するために再び問合せが実行されることはありません。別の方法として、RANGE_PAGING_INCR
モードを使用して、指定した行セット・キャッシュ・ファクタに基づいてメモリー内の結果をスクロールできるようにすることもできます。
非常に大きい(または予想不可能な大きさの)行セットの場合、問合せ(それぞれで、データベースから最大でRangeSize
の数だけ行が戻される)の実行回数が多少増える可能性があるというトレードオフは、表示済の行をすべてキャッシュすることに比べれば効率的です。結果のリストの任意のページにジャンプできるようにする場合は、このことが特に顕著です。デフォルトのスクロール可能モードでこれを行うには、現在のページとジャンプ先のページの間にあるすべての行をフェッチしてキャッシュする必要があります。RANGE_PAGING
モードでは、そのページの行をデータベースに要求するのみです。その後、ユーザーがすでに移動したことのあるページの行へ戻る場合、RANGE_PAGING
モードでは現在のページの行のみがメモリー内に保持されているため、これらの行は再度問合せる必要があります。このインクリメンタル範囲ページ移動アクセス・モード、RANGE_PAGING_INCR
では、標準の範囲ページ移動とスクロール可能アクセス・モードの両方の側面が組み合されています。アプリケーションはより多くの行をメモリーにキャッシュでき、ユーザーは再問合せする必要もなく、それらの行の任意の組合せにジャンプできます。
役に立つ場合には、同じディテール・ビュー・オブジェクトのインスタンスに対して複数のマスター・ビュー・オブジェクトのインスタンスが存在するように、データ・モデルを設定できます。Customers
、Orders
、およびPaymentOptions
という名前のビュー・オブジェクトと、次のように定義されたビュー・リンクについて考えます。
Customers
とPaymentOptions
Orders
とPaymentOptions
図9-3は、Customers
とOrders
の両方のビュー・オブジェクト・インスタンスが、同じPaymentOptions
ビュー・オブジェクト・インスタンスのマスターになるように構成した場合の、データ・モデル・パネルの表示を示しています。
図9-3のようなデータ・モデルを設定するには、アプリケーション・モジュールの概要エディタで、「データ・モデル」ページを使用します。
始める前に:
ビュー・オブジェクトに関する知識が役立つ場合があります。詳細は、5.1項「ビュー・オブジェクトについて」を参照してください。
複数のマスター・ビュー・オブジェクトを持つビュー・インスタンスを作成するには:
最初のマスター・ビュー・オブジェクトのインスタンスをデータ・モデルに追加します。
名前はCustomers
とします。
2番目のマスター・ビュー・オブジェクトのインスタンスをデータ・モデルに追加します。
名前はOrders
とします。
「データ・モデル」リストから、最初のマスター・ビュー・オブジェクトのインスタンスを選択します。
「使用可能なビュー・オブジェクト」リストで、最初のマスター・ビュー・オブジェクトの下にインデントされているディテール・ビュー・オブジェクトを選択し、「新規インスタンス名」フィールドにビュー・オブジェクト・インスタンスの名前を入力します。次に、>をクリックして、既存のCustomers
ビュー・オブジェクト・インスタンスのディテールとしてデータ・モデルに移動します。
「データ・モデル」リストから、2番目のマスター・ビュー・オブジェクトのインスタンスを選択します。
「使用可能なビュー・オブジェクト」リストで、2番目のマスター・ビュー・オブジェクトの下にインデントされているディテール・ビュー・オブジェクトを選択し、「新規インスタンス名」フィールドに、直前に入力したディテール・ビュー・オブジェクトの場合と同じビュー・オブジェクト・インスタンス名を入力します。次に、>をクリックして、既存のOrders
ビュー・オブジェクト・インスタンスのディテールとしてデータ・モデルに移動します。
次の警告が表示されます: PaymentOptionsVOという名前のビュー・オブジェクトのインスタンスはデータ・モデルですでに使用されています。同じインスタンスを使用しますか。
「はい」をクリックして、このディテール・ビュー・オブジェクト・インスタンスを、2番目のビュー・オブジェクト・インスタンスのディテールにもすることを確認します。
複数のエンティティ・オブジェクトの慣用名に基づくビュー・オブジェクトでは、部分的に移入されたキーを指定してビュー行を検索できます。部分キーは、複数属性のKey
オブジェクトで、一部の属性がnull
に設定されたものです。ただし、findByKey()
を実行するために使用できる部分キーの種類には、厳密な規則があります。
ビュー・オブジェクトがn個のエンティティ・オブジェクトの慣用名(n > 1)に基づいている場合、ビュー行のキーは、デフォルトで、関連するすべてのエンティティ・オブジェクトの慣用名のすべての主キー属性で構成されます。ビュー行のキーに参加する必要があるのは最初のエンティティ・オブジェクトの主キーのみですが、デフォルトですべての主キーが使用されます。
一部のセカンダリ・エンティティ・オブジェクトの慣用名のキー属性をビュー行レベルのキー属性として残す場合は、そのエンティティ・オブジェクトの主キーを構成するすべての属性をビュー行のキーの一部として残しておく必要があります。n個のエンティティ・オブジェクトの慣用名のうちの
m個について(m <= n)、ビュー行の1つ以上のキー属性を残した場合、findByKey()
を使用し、m個のエンティティ・オブジェクトの慣用名の任意のサブセットに基づいて行を検索できます。Key
オブジェクトで値を指定するそれぞれのエンティティ・オブジェクトの慣用名について、そのエンティティの主キーのすべての属性にNULLではない値を指定する必要があります。
ビュー・オブジェクトが1つ以上のエンティティ・オブジェクトの慣用名に基づいている場合、そのfindByKey()
メソッドは、ビュー行キーでの属性がNULLではない最初のエンティティ・オブジェクトの慣用名に対応するエンティティ定義のfindByPrimaryKey()
メソッドに委譲することで行を検索するため、この規則に従う必要があります。エンティティ定義のfindByPrimaryKey()
メソッドでキャッシュ内のエンティティ行を検索するには、特定のエンティティ・オブジェクトに対するすべてのキー属性がNULL以外である必要があります。
具体的な例としては、OrderInfoVO
ビュー・オブジェクトで、OrderEO
エンティティ・オブジェクトがそのプライマリ・エンティティ・オブジェクト慣用名で、AddressEO
エンティティがセカンダリ参照エンティティ・オブジェクト慣用名である場合を考えてみてください。さらに、次の両方のビュー行属性のキー属性プロパティを、true
に設定したままにするものとします。
OrderId
- OrderEO
エンティティの主キー
AddressId
- AddressEO
エンティティの主キー
したがって、ビュー行のキーは、OrderId
とAddressId
の組合せになります。findByKey()
を呼び出すときは、次のような内容のKey
オブジェクトを渡すことができます。
基礎となるOrderEO
エンティティの完全に指定されたキー
Key k = new Key(new Object[]{new Number(200), null});
基礎となるAddressEO
エンティティの完全に指定されたキー
Key k = new Key(new Object[]{null, new Number(118)});
両方のエンティティの完全に指定されたキー
Key k = new Key(new Object[]{new Number(200), new Number(118)});
有効な部分キーを指定すると、findByKey()
メソッドは、キー・オブジェクトで指定されていないエンティティ・オブジェクト慣用名の属性をワイルドカードとして扱い、結果として複数の行を返すことができます。
一般に、一時属性ではアクセスのたびに主キー属性の再評価が強制されるため、一時属性による主キーの定義が適していないユースケースもあります。特に、複数の問合せが生成される可能性のあるADF Facesコンポーネント(ADFツリー・コンポーネントなど)にバインドされるビュー・オブジェクトでは、一時属性を使用した主キーの定義は避けることをお薦めします。このような状況では、主キーである一時属性の再評価が必要となるため、アプリケーションのパフォーマンスが低下します。
また、プログラムによるビュー・オブジェクトの問合せで主キーが一時属性に依存する場合、ユーザーがビュー・オブジェクトのキャッシュ行の外にUIをスクロールすると、NULLポインタ例外を受け取る可能性があることにも注意してください。この場合、ビュー・オブジェクト問合せがデータベース表から生成されないため、ビュー・オブジェクト実装は、ViewObjectImpl
クラスのretrieveByKey()
メソッドをオーバーライドし、行を返す(または、行がない場合には空の配列を返す)必要があります。
このメソッドをオーバーライドすると、ADFビジネス・コンポーネントでfindByKey()
を実行し、キャッシュからリクエストされた行を最初に検索できます。(主キーが一時属性であるために)これに失敗した場合、ADFビジネス・コンポーネントは、retrieveByKey()
オーバーライドを実行し、入力されるキーと一致する行を取得するように定義した操作を実行します。このメソッドのデフォルトの実装は、データベース問合せを発行し、データベースから行を取得しようとします。
protected Row[] retrieveByKey(ViewRowSetImpl rs, Key key, int maxNumOfRows, boolean skipWhere)
このメソッドには次の引数があります。
maxNumOfRows
は、findByKey()
の呼出しに渡したmaxNumOfRows
です。これは、1 .. nまたは-1です。nは、渡されたキーと一致するキーを持つ行をn行検索していることを意味します。-1はすべての行が一致することを意味します。キーが部分キーでビュー・オブジェクトが複数のエンティティ・オブジェクトに基づいている場合、ビュー・オブジェクトの複数の行がキーと一致する可能性があります。
skipWhere
は、findByKey()
が基本ビュー・オブジェクトとして同じWHERE
句を適用する必要があるかどうかを制御します。基本ビュー・オブジェクトにWHERE
句DEPTNO = 10
があり、skipWhere
がfalse
の場合、バッキング・ストアから行を検索しているときに同じWHERE
句を適用することを想定されます。skipWhere
がtrue
の場合、基本ビュー・オブジェクトからのWHERE
句は関係ありません。
addDynamicAttribute()
メソッドを使用すると、実行時に1つまたは複数の動的属性をビュー・オブジェクトに追加できます。動的属性は、値として任意のシリアライズ可能オブジェクトを保持できます。通常は、グローバルで汎用的な方法でフレームワークに追加する機能を実装するために、行ごとに追加の一時的な状態を格納する必要がある、汎用のフレームワーク拡張コードを作成するときに、動的属性の使用を検討します。
通常はビュー・オブジェクトのデフォルトの行セットを使用しますが、ViewObject
インタフェースのcreateRowSet()
メソッドを呼び出して、同じビュー・オブジェクトの問合せに基づく行セットである、二次的な名前付きの行セットを作成できます。こうすることに意味のある状況の1つは、ビュー・オブジェクトのSQL問合せに名前付きバインド変数が含まれる場合です。各RowSet
オブジェクトはバインド変数値の独自のコピーを格納するので、単一のビュー・オブジェクトを使用して、異なる組合せのバインド変数値に基づく複数の行セットを生成および処理できます。作成した名前付き行セットは、findRowSet()
メソッドを使用して検索できます。セカンダリ行セットの使用が終了したら、closeRowSet()
メソッドを呼び出します。
RowSet
に対し、通常はデフォルトの行セット・イテレータを使用しますが、RowSet
インタフェースのcreateRowSetIterator()
メソッドを呼び出して、二次的な名前付きの行セット・イテレータを作成できます。作成した名前付き行セット・イテレータは、findRowSetIterator()
メソッドを使用して検索できます。セカンダリ行セット・イテレータの使用が終了したら、closeRowSetIterator()
メソッドを呼び出します。
パフォーマンスのヒント: 結果セットに対してプログラム的に反復を実行する必要があるとき、デフォルトの行セット・イテレータの現在行が妨害されることを避けるために、セカンダリ・イテレータを作成します。たとえば、ADF Modelの宣言的データ・バインディング・レイヤーを通して、アプリケーションのユーザー・インタフェース・ページは、アプリケーション・モジュールのデータ・モデル内にあるビュー・オブジェクトのデフォルト行セットのデフォルト行セット・イテレータと連携します。このシナリオでは、ビュー・オブジェクトのデフォルト行セットに対して反復処理を行うビジネス・ロジックを担当する行セットのセカンダリ・イテレータを作成しない場合、結果的にユーザー・インタフェース・レイヤーによって使用されるデフォルトの行セット・イテレータの現在行が変更されます。 |
デフォルトでは、ビュー・リンク・アクセッサの行セットを取得するたびに、ビュー・オブジェクトによって新しいRowSet
オブジェクトが作成され、ユーザーが行を操作できるようにします。これは、そのたびに問合せを再実行して結果を生成するという意味ではなく、RowSet
オブジェクトの新しいインスタンスを作成してデフォルトのイテレータを先頭行の前のスロットにリセットするだけです。行セットをデータベースの行で強制的に更新するには、executeQuery()
メソッドを呼び出します。
新しいディテール行セット作成に関連する若干のオーバーヘッドがアプリケーションで発生しないようにする場合は、ビュー・リンク・アクセッサ行セットのキャッシュを有効にできます。たとえば、ビュー・アクセッサ行セットは、マスター行ビュー・アクセッサ属性が変更されないかぎりは安定した状態を維持し、ツリー・コントロール(ツリー内の各マスター・ノードのデータが個別のディテール行セットを維持する必要がある)のようなUIコンポーネントの行セットを再作成する必要がありません。ビュー・リンク・アクセッサのディテール行セットにプログラムでアクセスすることもできます。この場合、アプリケーションで同じビュー・リンク・アクセッサ属性に対して多数のコールを行うときに、ビュー・リンク・アクセッサ行セットのキャッシングを検討できます。マスター/ディテール関係の管理のスタイルは、データ・モデルでのビュー・リンク・インスタンス作成とは異なります(9.1.3項「ビュー・リンク・アクセッサとデータ・モデル・ビュー・リンク・インスタンスの理解」を参照)。
ビュー・リンク・アクセッサのソースであるビュー・オブジェクトについて、概要エディタを使用してビュー・リンク・アクセッサ行セットの保存を有効にできます。ビュー・オブジェクトの概要エディタの「ビュー・アクセッサ」ページで、「行セットの保存」を選択します。
かわりに、ビュー・オブジェクトでカスタムJavaクラスを有効にし、create()
メソッドをオーバーライドして、パラメータとしてtrue
を渡して、setViewLinkAccessorRetained()
メソッドを呼び出す行を、super.create()
の後に追加できます。これは、そのビュー・オブジェクトのすべてのビュー・リンク・アクセッサ属性に適用されます。
ビュー・オブジェクトに対してこの機能を有効にすると、ビュー・リンク・アクセッサの行セットが毎回再作成されないため、デフォルトの行セット・イテレータの現在行も維持されるという副作用があります。つまり、デフォルト行セット・イテレータの現在行をリセットして先頭行の前のスロットに戻すには、ビュー・リンク・アクセッサから取得した行セットに対して、reset()
メソッドを明示的に呼び出す必要があります。
ただし、アクセッサの保持を有効にすると、アクセッサ行セットの行を反復処理する前にいつでもreset()
を呼び出すようにしないと、微妙で検出が困難なエラーがアプリケーションで発生する可能性があることに注意してください。たとえば、ビュー・リンク・アクセッサの行セットの行を次のようにして反復処理し、合計を計算するものとします。
RowSet rs = (RowSet)row.getAttribute("OrdersShippedToPurchaser"); 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("OrdersShippedToPurchaser");
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インジェクション攻撃からアプリケーションを保護することもできます。バインド変数によるビュー基準の定義の詳細は、5.9.1項「名前付きビュー基準を宣言的に作成する方法」を参照してください。
バインド変数は、SQL文字列内のプレースホルダであり、SQL文字列自体のテキストを変更することなく、実行時に簡単に値を変更できます。問合せのテキストは実行ごとに変化しないので、データベースは同じ解析済の文を毎回効率よく再利用できます。文の再解析を避けることで、データベースでは問合せ最適化プランを絶えず再決定する必要が軽減され、この解析操作の間に使用される他の貴重なデータベース・リソースに対する複数のエンド・ユーザーによる競合をなくすことができます。これらの減少により、実行時のアプリケーションのパフォーマンスが向上します。名前付きバインド変数の使用方法の詳細は、5.10.2項「ビュー・オブジェクト定義にWHERE句のバインド変数を追加する方法」を参照してください。
パラメータを使用するWHERE
句の値に対してバインド変数を使用することは、その値をアプリケーションのエンド・ユーザーが提供する場合に特に重要です。例9-3に示す例について考えてみます。この例は、ユーザーが指定したパラメータ値を文に連結して構成された動的なWHERE
句を追加します。
例9-3 バインド変数のかわりに文字列の連結を使用することによる、SQLインジェクション攻撃に対する脆弱性
// EXAMPLE OF BAD PRACTICE, Do not follow this approach! String userSuppliedValue = ... ; yourViewObject.setWhereClause("BANK_ACCOUNT_ID = "+userSuppliedValue);
悪質な意図を持つユーザーは、アプリケーションの基礎にあるデータベース・スキーマの詳細を知ることができれば、慎重に構成された銀行口座番号を、次のようにフィールド値またはURLパラメータとして提供できます。
BANK_ACCOUNT_ID
例9-3のコードで、動的に適用されるWHERE句にこの値を連結すると、データベースには次のような問合せ述語が示されます。
WHERE (BANK_ACCOUNT_ID = BANK_ACCOUNT_ID)
このWHERE
句では現在のユーザーの口座だけでなくすべての銀行口座が取得され、ハッカーは他人の口座の個人情報を見ることができます。悪質な目的で作成されたパラメータ値をSQL文に提供することでアプリケーションのWHERE
句を短絡するこのような手法のことを、SQLインジェクション攻撃と呼びます。このような場合は、かわりに例9-4に示すように名前付きバインド変数を使用することで、脆弱性を回避できます。
例9-4 文字列の連結にかわる名前付きバインド変数の使用
// Best practice using named bind variables String userSuppliedValue = ... ; yourViewObject.setWhereClause("BANK_ACCOUNT_ID = :BankAcccountId"); yourViewObject.defineNamedWhereClauseParam("BankAcccountId", null, null); yourViewObject.setNamedWhereClauseParam("BankAcccountId",userSuppliedValue);
この場合に悪質なユーザーが不正な値を提供すると、予想しないデータを取得するのではなく、アプリケーションが処理できるエラーを受け取ります。
ビュー・オブジェクト問合せの実行に使用されるバインド・パラメータ値がわかるようにする必要がある場合は、テストの実行前またはデバッグ・セッション中に、ログ・レベルを構成できます。指定するログ・レベルによって、ログ・メッセージのタイプと量が決まります。バインド・パラメータの情報を表示するには、最低でもJavaログ・レベルCONFIG
を設定する必要があります。ログ・アナライザの使用と構成の詳細は、42.6.4項「ログ・アナライザを使用してログ・メッセージを表示する方法」を参照してください。
また、ビュー・オブジェクトのViewObjectImpl
クラスのbindParametersForCollection()
メソッドをオーバーライドすることにより、バインド・パラメータ値をプログラムでログに記録することもできます。このメソッドは、問合せで使用されるバインド・パラメータの名前と値をログに記録する場合に使用されます。これを実装してバインド・パラメータの値をログに記録できます。この機能は、バインド・パラメータがランタイムで生成されるようなシナリオで特に便利です。たとえば、ADF Facesツリー・コンポーネントがビュー・リンク・アソシエーションを使用して作成される場合、ノードにはバインド・パラメータで値が移入されますが、これをログに記録できます。例9-5に、この目的でbindParametersForCollection()
メソッドを定義する方法を示します。
例9-5 バインド・パラメータ値のロギング
@Override protected void bindParametersForCollection(QueryCollection qc, Object[] params, PreparedStatement stmt) throws SQLException { String vrsiName = null; if (qc != null) { ViewRowSetImpl vrsi = qc.getRowSetImpl(); vrsiName = vrsi.isDefaultRS() ? "<Default>" : vrsi.getName(); } String voName = getName(); String voDefName = getDefFullName(); if (qc != null) { System.out.println("----[Exec query for VO=" + voName + ", RS=" + vrsiName + "]----"); } else { System.out.println("----[Exec COUNT query for VO=" + voName + "]----"); } System.out.println("VODef =" + voDefName); System.out.println(getQuery()); if (params != null) { if (getBindingStyle() == SQLBuilder.BINDING_STYLE_ORACLE_NAME) { StringBuilder binds = new StringBuilder("BindVars:("); int paramNum = 0; for (Object param : params) { paramNum++; Object[] nameValue = (Object[])param; String name = (String)nameValue[0]; Object value = nameValue[1]; if (paramNum > 1) { binds.append(","); } binds.append(name).append("=").append(value); } binds.append(")"); System.out.println(binds); } } super.bindParametersForCollection(qc, params, stmt);
一般に、SQLベースの検証のため、およびドロップダウン・リストに有効な選択肢のリストを表示するために使用されるビュー・オブジェクトは、読取り専用でかまいません。アプリケーションに必要な機能の種類を判断し、それに応じてビュー・オブジェクトを設計する必要があります。
ベスト・プラクティス: 以前のリリースでは、読取り専用データに関するベスト・プラクティスとして、読取り専用のカスタムSQLビュー・オブジェクトを作成することが推奨されていました。この推奨の意図は、エンティティ・オブジェクト・キャッシュに関連する行を格納するオーバーヘッドを回避することでした。現在は、このベスト・プラクティスを考慮する必要のない、最適化された対処法があります。現在のリリースの場合、データを参照するために読取り専用ビュー・オブジェクトを作成する必要があるときは、エンティティ・ベースのビュー・オブジェクトを使用し、ビュー・オブジェクトの概要エディタの「エンティティ・オブジェクト」ページで「更新可能」オプションの選択を解除してください。この手法には、2つの利点があります。第一は、SQL問合せの生成に役立つ設計時エディタを利用できることであり、第二は、ビュー・オブジェクトを更新可能にすることにした場合に、すでに更新可能なエンティティ・オブジェクトがその基礎となっていることです。 読取り専用のカスタムSQLビュー・オブジェクトを作成するという、これに替わる方法にも、本格的なSQL問合せを指定できるという利点があり、エンティティ・オブジェクトの使用ではユニオンやグループ化を表現できないような場合に、特に役立ちます。 |
ビュー・オブジェクトは、基礎になっているエンティティ・オブジェクトに関連付けることも、関連付けないこともできます。ビュー・オブジェクトが基礎になっているエンティティ・オブジェクトと関連付けられていると、デフォルト動作で新しい行の作成、および問合せで得られた行の変更や削除がサポートされます。ただし、更新機能は、エンティティ・ベースのビュー・オブジェクトの概要エディタで「更新可能」の選択を解除して無効にできます(図9-4を参照)。
かわりの方法として、読取り専用ビュー・オブジェクトを作成し、概要エディタの「問合せ」ページで「カスタムSQLの書込み」を選択して、SQL問合せを定義することもできます。エディタによって問合せ作成作業が簡素化されるため、複雑なSQL文の構築に慣れていないビジネス・コンポーネント開発者にとっては、エンティティ・オブジェクトに基づいて更新不可のビュー・オブジェクトを作成する方が、常に便利です。更新不可に設定したエンティティ・ベースのビュー・オブジェクトは、読取り専用のカスタムSQLビュー・オブジェクトと比べて、次の点で有利です。
ユーザー・インタフェースに必要とされる属性のみが含まれるように、実行時に選択リストを最適化できます。
エンティティ・オブジェクトを使用したローカル・キャッシュの作成による、大幅なパフォーマンスの劣化はありません。
ビュー・オブジェクト内のデータは、読取り操作ごとにデータベースに戻す必要はなく、むしろローカル・キャッシュの状態を表します。
定義した別のビュー・オブジェクトで更新不可のビュー・オブジェクトのベース・エンティティ・オブジェクトの更新が必要になっても、ローカル・キャッシュ内のデータは一貫性を維持します。
このため、ビュー・オブジェクト行とエンティティ・オブジェクト行の間の調整には、実行時に若干のオーバーヘッド(概算では5%未満のオーバーヘッド)が生じますが、ビュー・オブジェクト定義を完全に宣言的に維持し、カスタマイズ可能なビュー・オブジェクトをメンテナンスする機能と比較検討してください。カスタムSQLビュー・オブジェクトはカスタマイズ不可ですが、エンティティ・オブジェクトでは表現できないUNIONおよびGROUP BY問合せの実行に使用できます。カスタムSQLビュー・オブジェクトは、ビュー・オブジェクト・ベースのキー存在バリデータによって使用されるSQLベースの検証問合せでも便利です。
データが読取り専用でないとき、最善の(そして唯一の)選択肢はエンティティ・ベースのビュー・オブジェクトを作成することです。更新可能(デフォルト動作)なエンティティ・ベースのビュー・オブジェクトは、エンティティから導出される属性のデフォルト値の取得、同じトランザクション内の他のビュー・オブジェクトを介して関連エンティティ・オブジェクトに行われた保留中の変更の反映、およびエンティティ・ベースのビュー・オブジェクトを使用するために外部キー属性値が変更されたときに更新された参照情報の反映の唯一の方法です。
ビュー・オブジェクトをエンティティにマップするかどうかを決定した後は、次に問合せ自体に注目します。ビュー・オブジェクトの概要エディタの「問合せ」ページで、「テストおよび説明」ボタンをクリックし、データベースの問合せオプティマイザで使用される問合せ計画を表示します。計画が全表スキャンを行っている場合は、索引を追加するか、または概要エディタの「一般」ページの「チューニング」セクションで、「問合せオプティマイザ・ヒント」フィールドの値を指定することを検討する必要があります。これにより、使用される問合せ計画を明示的に制御できます。これらの機能は、個別のビュー・オブジェクトSQL文について問合せ計画を評価するための便利なツールを開発者に提供します。ただし、これらのツールの使用は、本番環境のデータ量とエンド・ユーザー数においてパフォーマンスの悪い問合せを識別するための、アプリケーション全体のSQLのトレースにかわるものではありません。
OracleデータベースのSQLトレース機能を使用すると、アプリケーションが実行する全SQL文の完全なログが生成されます。すべてのバージョンのOracleデータベースで動作する方法としては、次のコマンドを発行します。
ALTER SESSION SET SQL_TRACE TRUE
特にOracle 10gでは、このコマンドを実行するために、DBAはALTER SESSION
権限を付与する必要があります。
このコマンドを実行すると、現在のデータベース・セッションのトレースが有効になり、ALTER SESSION SET SQL_TRACE FALSE
を入力するまで、または接続を閉じるまで、すべてのSQL文がサーバー側のトレース・ファイルに記録されます。このオプションの有効化を簡単にしてFusion Webアプリケーションをトレースするには、例9-6
に示すように、アプリケーション・モジュール(またはカスタム・アプリケーション・モジュール・フレームワーク拡張クラス)のafterConnect()
メソッドをオーバーライドし、Javaシステム・プロパティが存在するかどうかに基づいて、条件付きでALTER SESSIONコマンドを実行してSQLトレースを有効にします。
例9-6 アプリケーション・モジュールでの条件付きでのSQLトレースの有効化
// In YourCustomApplicationModuleImpl.java protected void afterConnect() { super.afterConnect(); if (System.getProperty("enableTrace") != null) { getDBTransaction().executeCommand("ALTER SESSION SET SQL_TRACE TRUE"); } }
トレース・ファイルを生成した後は、データベースに付属するTKPROF
ユーティリティを使用して情報を書式設定することで、次のように実行された各問合せに関する情報を理解しやすくなります。
(再)解析された回数
実行された回数
アプリケーション・サーバーとデータベースの間で行われたラウンドトリップの回数
問合せの実行時間に関する様々な定量的測定値
これらの手法を使用することで、アプリケーションが実行する特定の問合せの時間を短縮するために必要な追加索引や、問合せ最適化計画を改善するために変更できる問合せを判断できます。TKPROF
ユーティリティでの作業の詳細は、『Oracle Database SQLチューニング・ガイド』の「アプリケーション・トレースの実行」の章の、SQLトレースとTKPROFの理解に関する項およびSQLトレース機能とTKPROFの使用に関する項を参照してください。
注意: Oracleデータベースが提供するDBMS_MONITOR パッケージでは、SQLトレースがさらに簡単になっており、Oracle Enterprise Managerと統合してアプリケーションが最も頻繁に実行する問合せ文を視覚的にモニタリングできます。 |
ビュー・オブジェクトの概要エディタの「一般」ページの「チューニング」セクションでは、問合せのパフォーマンスに劇的な影響を与える可能性のある様々なオプションを設定できます。図9-5に、新しいビュー・オブジェクトで定義されているデフォルトのオプションを示します。
ビュー・オブジェクトの概要エディタの「一般」ページにある、「データベースから取得」グループ・ボックスは、ビュー・オブジェクトがデータベース・サーバーから行を取得する方法を制御します。フェッチ・モードのオプションは、「すべての行」、「行番号までのみ」、「最大で1行」および「行なし」です。ほとんどのビュー・オブジェクトはデフォルトの「すべての行」オプションを使用し、「必要に応じて」(デフォルト)または「一度にすべて」のいずれか選択されているオプションに従って取得されます。
注意: 「同時」オプションでは、ビュー・オブジェクト問合せで指定された行をフェッチするためのデータベース・ラウンドトリップを一切強制しません。「必要に応じて」および「同時」オプションは、「バッチ」(フェッチ・サイズとも呼ばれます)の値と連携して、ラウンドトリップ数を決定します。データベース・アクセスのパフォーマンスを最高に保つには、9.2.4.2項「一度に1行のフェッチが適切かどうかの検討」の説明に従ってフェッチ・サイズを変更することを検討してください。 |
「必要に応じて」オプションを指定すると、ビュー・オブジェクトでのexecuteQuery()
操作は最初に、表示する最初のページに必要な数の行のみを取得します。この行数は、ビュー・オブジェクトの範囲サイズに基づいて設定されます。「必要に応じて」を使用する場合、最初の範囲サイズ(「レンジ・サイズ」オプションで定義される)で指定された行数を取得するために必要な回数だけ、データベース・ラウンドトリップが要求されます。「同時」を使用する場合、アプリケーションでは、「バッチ」(フェッチ・サイズ)の値と問合せによって識別された行数に基づいて、すべての行を取得するために必要な回数のラウンドトリップが実行されます。
WHERE
句が単一の行を取得すると予想されるビュー・オブジェクトの場合は、オプションを「最大で1行」に設定すると最良のパフォーマンスが得られます。このオプションを指定すると、ビュー・オブジェクトはユーザーがそれ以上の行を期待していないことを認識し、さらに行がある場合に通常行うテストをスキップします。最後に、ビュー・オブジェクトを新規行の作成のみを目的として使用する場合は、最適なパフォーマンスを得るために、このオプションを「行なし」に設定すると、問合せは実行されなくなります。
始める前に:
ビュー・オブジェクトに関する知識が役立つ場合があります。詳細は、5.1項「ビュー・オブジェクトについて」を参照してください。
行オブジェクトの行フェッチ・オプションを設定するには:
「アプリケーション」ウィンドウで、編集するビュー・オブジェクトをダブルクリックします。
概要エディタで、「一般」ナビゲーション・タブをクリックし、「チューニング」セクションを開きます。
「データベースから取得」グループ・ボックスで、データベースからフェッチする行数を制御するオプションを選択します。
ビュー・オブジェクトの問合せを最適化するためにデータベースで使用される、「問合せオプティマイザ・ヒント」を入力します。
実行時、このヒントは問合せのSELECTキーワードの直後に追加されます。たとえば、すべての行をできるだけ迅速に取得する必要がある場合は、ALL_ROWS
と入力します。また、(すべての行の取得を最適化しないで)最初の行をできるだけ迅速に取得する場合は、FIRST_ROWS
と入力します。
フェッチ・サイズは、データベースへの各ラウンドトリップで返される行数を制御します。デフォルトでは、フレームワークは一度に1行のバッチで行をフェッチします。複数の行をフェッチする場合、ビュー・オブジェクト概要エディタの「一般」ページの「チューニング」セクションにあるこの「次の単位で」の値を設定して効率を上げます。
本当に1行のみフェッチするのでないかぎり、「次の単位で」フィールドをデフォルトのフェッチ・サイズである1
のままにしておくことはお薦めしません。これは、アプリケーション・サーバーとデータベースの間で必要以上に多くのラウンドトリップが発生するためです。各ビュー・オブジェクトのフェッチ・サイズに適した値を検討することを強くお薦めします。
ユーザー・インタフェースで一度にn行の結果を表示する場合は、簡単なWebページでは、データベースへの1回のラウンドトリップで各ページの結果を取得できるよう、フェッチ・サイズを少なくともn+3に設定するのがよい方法です。ただし、この値を大きくすると、クライアント側で必要なバッファが大きくなるため、この数を、Oracle JDBCフェッチ・サイズの推奨を超える値に設定しないでください。
通常、フェッチ・サイズはOracleデータベース12cの推奨事項と一致している必要があります。つまり、フェッチ・サイズが100程度であっても、場合によってはそれよりも大きなサイズが適切になります。詳細は、Oracle Database 12cの『Oracle JDBC Memory Management』(http://www.oracle.com/technetwork/database/application-development/jdbc-memory-management-12c-1964666.pdf
)を参照してください。
始める前に:
ビュー・オブジェクトに関する知識が役立つ場合があります。詳細は、5.1項「ビュー・オブジェクトについて」を参照してください。
ビュー・オブジェクトのデータベース・ラウンドトリップ数を制限するには:
「アプリケーション」ウィンドウで、編集するビュー・オブジェクトをダブルクリックします。
概要エディタで、「一般」ナビゲーション・タブをクリックし、「チューニング」セクションを開きます。
「データベースから取得」グループ・ボックスで、「すべての行」または「行番号までのみ」を選択します。
「次の単位で」フィールドに、各データベース・ラウンドトリップで返す行数を入力します。
フェッチされた行数が行セットを表示するWebページに収まるかどうかに関係なく、すべての行をフェッチする場合は、オプション「一度にすべて」を選択します。そうではなく、行セットを表示するWebページの最初のページを埋めるために必要な行数のみをフェッチする場合は、デフォルト設定である「必要に応じて」のままにします。
「一度にすべて」を選択する場合、アプリケーションでは、「次の単位で」(フェッチ・サイズ)の値と問合せによって特定された行数に基づいて、すべての行を取得するために必要な回数のラウンドトリップが実行されます。
「問合せオプティマイザ・ヒント」フィールドでは、Oracle問合せオプティマイザに対するオプションのヒントを指定して、使用される実行計画に影響を与えることができます。図9-5に示すように、このヒントは、ビュー・オブジェクトの概要エディタの「チューニング」ページで設定することができます。
実行時には、指定したヒントが問合せのSELECT
キーワードの直後に追加され、特殊なコマンド構文/*+
YOUR_HINT
*/
でラップされます。一般的な2つのオプティマイザ・ヒントを次に示します。
FIRST_ROWS
- できるかぎり早く最初の行を取得することを示します。
ALL_ROWS
- できるかぎり早くすべての行を取得することを示します。
他にも多くのオプティマイザ・ヒントがありますが、このマニュアルの範囲からは外れます。使用できるヒントの詳細は、Oracleのデータベース・リファレンス・マニュアルを参照してください。
始める前に:
ビュー・オブジェクトに関する知識が役立つ場合があります。詳細は、5.1項「ビュー・オブジェクトについて」を参照してください。
ビュー・オブジェクト定義を編集するには:
「アプリケーション」ウィンドウで、編集するビュー・オブジェクトをダブルクリックします。
概要エディタで、「一般」ナビゲーション・タブをクリックし、「チューニング」セクションを開きます。
「データベースから取得」グループ・ボックスで、データベースからフェッチする行数を制御するオプションを選択します。
ビュー・オブジェクトの問合せを最適化するためにデータベースで使用される、「問合せオプティマイザ・ヒント」を入力します。
実行時、このヒントは問合せのSELECTキーワードの直後に追加されます。たとえば、すべての行をできるだけ迅速に取得する必要がある場合は、ALL_ROWS
と入力します。また、(すべての行の取得を最適化しないで)最初の行をできるだけ迅速に取得する場合は、FIRST_ROWS
と入力します。
ビュー・オブジェクトの結果をプログラムで反復処理するコードを作成することがよくあります。よくある状況は、問合せ結果の複数の行を処理して属性またはエンティティが有効かどうかを判定する必要があるカスタム検証コードです。このような場合、行セット内の各行を1回のみ読み取り、戻る方向にスクロールしたり、後で行セットを再び反復処理したりする必要がない場合は、前方のみモードを使用することで、取得した行のキャッシュを回避できます。前方のみモードを有効にするには、ビュー・オブジェクトでsetForwardOnly(true)
を呼び出します。
注意: 前方のみモードで読取り専用ビュー・オブジェクト(エンティティ・オブジェクトの慣用名がない)を使用し、フェッチ・サイズを適切にチューニングするのが、データをプログラムで読み取る最も効率のよい方法です。 |
また、行セットを後方にスクロールすることがなく、reset()
を呼び出してイテレータを最初の行に設定することがない場合は、データを挿入、更新、または削除するときにも、前方のみモードを使用することで行のキャッシュを避けることができます。前方のみモードは、範囲サイズが1
の場合にのみ機能します。