ヘッダーをスキップ
Oracle Application Development Framework Forms/4GL開発者のための開発者ガイド
10g(10.1.3.0)
B40013-02
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

28 アプリケーション・モジュールの状態管理

この章では、アプリケーション・モジュールの状態管理機能とその使用方法について説明します。

この章の内容は次のとおりです。

28.1 状態管理が必要な理由

現実世界のほとんどのビジネス・アプリケーションでは、マルチステップのユーザー・タスクをサポートする必要があります。最近のサイトは、ステップ・バイ・ステップ・スタイルのユーザー・インタフェースを使用し、論理的な順序のページを通してエンド・ユーザーをタスクの完了まで導く傾向があります。タスクが終了すると、ユーザーは全体をまとめて保存またはキャンセルできます。

28.1.1 マルチステップ・タスクの例

一般的な検索後に編集を行うシナリオでは、エンド・ユーザーは、更新する適切な行を検索して発見した後、作業を保存するかキャンセルするかを決定する前に、関連するマスター/ディテール情報の複数のページを開いて編集を行うことができます。エンド・ユーザーが休暇の予約をオンラインで行う別のシナリオを考えてみます。プロセスには、エンド・ユーザーによる次の詳細情報の入力が含まれる場合があります。

  • 旅行に含まれる1つ以上の飛行機利用区間

  • 旅行する1人以上の乗客

  • 座席の選択と食事の指定

  • 異なる都市での1つ以上のホテルの部屋

  • 借りる自動車

ユーザーは途中で、トランザクションの完了、後で行うための予約の保存またはすべての中止などを決定します。

これらのシナリオには複数のWebページにまたがる論理的な作業単位が含まれることは明らかです。これまでの章では、JDeveloperのJSFページ・ナビゲーション・ダイアグラムを使用してこのようなユースケースに対するページ・フローを設計する方法を見てきましたが、それはパズルのほんの一部にすぎません。途中のビジネス・ドメイン・オブジェクト(TripFlightPassengerSeatHotelRoomAutoなど)に対してエンド・ユーザーが行う保留変更は、各エンド・ユーザーに対するアプリケーションの進行中状態を表します。これとともに、前のステップで行われた選択に関する他の種類の記録された情報が、アプリケーション状態の全体像を構成します。

28.1.2 ステートフルなアプリケーションを複雑にするステートレスなHTTPプロトコル

これらのマルチステップ・シナリオを容易に想像できる場合もありますが、Webアプリケーションでの実装は、HTTP(Hypertext Transfer Protocol)のステートレスな性格のために複雑になります。図28-1は、サイトへのエンド・ユーザーの訪問がHTTPの一連のリクエスト/レスポンスのペアで構成される様子を示しています。しかし、HTTPには、Webサーバーが異なるユーザーのリクエストを区別したり、同じユーザーがサイトとの対話中に行った異なるリクエストを区別したりするための手段がありません。サーバーは、ユーザーからのリクエストを、常にそれが最初にして唯一のものであるかのように受け取ります。

図28-1 Webアプリケーションが使用するステートレスなHTTPプロトコル

WebアプリケーションとステートレスなHTTPプロトコルの図

しかし、これまでにWebアプリケーションを実装した経験はなくても、Webアプリケーションを使用して本を購入したり、休暇を計画したり、電子メールを読んだりした経験は間違いなくあると思われます。そのため、ユーザーを区別するための解決策が存在することは明らかです。

28.1.3 Cookieを使用してユーザー・セッションを追跡する方法

図28-2で示されているように、ステートレスなHTTPプロトコルを介した同じエンド・ユーザーからのリクエスト・シーケンスの継続を認識する手法には、Cookieと呼ばれる一意の識別子が関係します。Cookieは名前と値のペアであり、サイトに対してユーザーが行う各HTTPリクエストのヘッダー情報で送信されます。ユーザーが行う最初のリクエストには、Cookieは含まれていません。サーバーは、Cookieが存在しないことを利用して、ユーザーとサイトとの対話セッションの開始を検出し、そのユーザーに対するそのセッションを表す一意の識別子をブラウザに返します。実際のCookieの値は文字と数字から成る長い文字列ですが、便宜上、一意の識別子はサイトを使用する異なるユーザーに対応するAやZなどの1文字であるものとします。

Webブラウザは、サーバーから返されるCookieを認識し、Cookieを送信したサイトやCookie値を保持する必要のある期間などを識別するための標準的な方法をサポートします。そのユーザーがそれ以降に行う各リクエストにおいて、ブラウザは、Cookieの有効期限が切れるまで、リクエストのヘッダーとともにCookieを送信します。

サーバーは、Cookieの値を使用して、異なるユーザーによって行われたリクエストを区別します。ユーザーがブラウザを閉じると有効期限が切れるCookieがセッションCookieと呼ばれるのに対し、単一のブラウザ・セッションを超えて持続するように設定されている他のCookieは、最初に作成されてから週、月、または年が経過してから有効期限が切れる場合があります。

図28-2 セッションCookieとサーバー側セッションを使用した状態の追跡

サーバー側セッション・フローで状態Cookieを追跡する図

J2EEに準拠するWebサーバーが提供するHttpSessionと呼ばれる標準のサーバー側機能を利用すると、特定のユーザーのセッションに関連するJavaオブジェクトを、属性と値の名前付きのペアとして保管できます。あるリクエストでこのセッションMapに格納されたオブジェクトは、同じセッションの以降のリクエストが処理されている間に、開発者が取得できます。web.xmlファイルのセッション・タイムアウト要素によって構成される時間枠内で、ユーザーが新しいリクエストの送信を続けている間、セッションはアクティブ状態を保ちます。セッションのデフォルトの長さは35分です。

28.1.4 HttpSessionの使用がパフォーマンスと信頼性に与える影響

HttpSession機能はほとんどのアプリケーション状態管理戦略の構成要素ですが、使用方法を誤ると、パフォーマンスと信頼性の問題につながる可能性があります。まず、作成されるセッション・スコープのJavaオブジェクトはJ2EE Webサーバーのメモリーに保持されるため、サーバーで障害が発生すると、HTTPセッションのオブジェクトは失われます。

図28-3で示されているように、信頼性を高める1つの方法は、複数のJ2EEサーバーをクラスタ構成にすることです。このようにすると、J2EEアプリケーション・サーバーはユーザーごとのHTTPセッションのオブジェクトをクラスタ内の複数のサーバーにレプリケートするため、1台のサーバーが停止しても、オブジェクトはクラスタ内の他のサーバーのメモリーに存在し、ユーザーのリクエストの処理を続けることができます。クラスタは異なるサーバーで構成されるため、サーバー間でのHTTPセッション・コンテンツのレプリケーションには、HTTPセッション・オブジェクトに対して行われた変更の、ネットワークを通したブロードキャストが含まれます。

図28-3 サーバー・クラスタでのセッション・レプリケーション

サーバー・クラスタでのセッション・レプリケーションのフローを示す図

HTTPセッションの乱用は、パフォーマンスに影響を与える可能性があります。

  • アクティブなユーザーが増えると、サーバーで作成されるHTTPセッションが増加します。

  • HTTPセッションごとに格納されるオブジェクトが増えると、必要なメモリーが増加します。

  • クラスタにおいて、各HTTPセッションで変化するオブジェクト増えると、変化したオブジェクトをクラスタの他のサーバーにレプリケートするために生成されるネットワーク・トラフィックが増加します。

最初は、セッションで保管されるオブジェクトの数を最小限に保つことで問題に対処できるように思われます。しかし、これは、各ユーザーの保留中のアプリケーション状態を一時的に格納するために代替メカニズムを利用することを意味します。最も一般的な代替手段としては、リクエスト間にアプリケーションの状態をデータベース、または共有ファイル・システムのなんらかの種類のファイルに保存するという方法があります。

当然ながら、実際に実行するのは容易ではありません。可能性のある方法として、保留中の変更を基礎のデータベース表に保存し、各HTTPリクエストの終了時にトランザクションをコミットするというものがあります。しかし、このアイデアには2つの重要な障害があります。

  • データベース制約が失敗する可能性があります。

    マルチステップ・プロセスの特定のステップで情報が部分的に不完全になり、このために変更を保存しようとする際にデータベース・レベルのエラーが発生する可能性があります。

  • 変更のロールバックが複雑になります。

    作業の論理的な単位をキャンセルするには、複数になる可能性のある表でコミットしたすべての行を慎重に削除する必要があります。

これらの制限事項のため、制約がなく、すべての列の型が文字ベースとして定義されているデータベース表のシャドウ・セットを利用する解決策が発明されました。このような解決策の使用は、短期間に非常に複雑になります。最終的に、より汎用的で実現可能な方法でこのような問題に対処するには、なんらかの種類の汎用アプリケーション状態管理機能が必要であるという結論に達するでしょう。ADF Business Componentsは、このソリューションをすぐに実装できる状態で提供します。

28.2 ADF Business Componentsの状態管理機能

アプリケーション・モジュール・コンポーネントとアプリケーション・モジュール・プールが連携して、データベースを利用したアプリケーション状態管理の汎用ソリューションを提供します。この機能を利用すると、前の項で説明したメモリー、信頼性、または実装の複雑さの問題に陥ることなく、マルチステップのユースケースをサポートするWebアプリケーションを簡単に作成できます。

ADF Business Componentsベースのアプリケーションは、各ユーザー・セッションのアプリケーション状態を自動的に管理します。これまでの4GLツールでのようなステートフル・プログラミング・モデルの簡単さを提供しながら、純粋なステートレス・アプリケーションに近いスケーラビリティを提供する方法で実装されます。この重要な機能を最大限に活用するには、背後で行われていることを理解しておく必要があります。

28.2.1 状態管理機能の基本アーキテクチャ

アプリケーション・モジュール・コンポーネントを使用して、完全にステートレスなアプリケーションを実装したり、複数のブラウザ・ページが関係する作業単位をサポートしたりできます。図28-4は、これらのマルチステップ・シナリオをサポートするための状態管理機能の基本アーキテクチャを示しています。アプリケーション・モジュールは、保留中トランザクション状態のXMLドキュメントへの受動化をサポートします。XMLドキュメントは、データベースの単一の汎用的な表に、一意の受動化スナップショットIDをキーとして格納されます。また、保存されているXMLスナップショットの1つから保留中トランザクション状態を能動化する逆の操作もサポートします。この受動化と能動化は、必要に応じて、アプリケーション・モジュール・プールにより自動的に行われます。

図28-4 ADFが提供するデータベースを利用した汎用的な状態管理

ADFの状態管理を説明する図

ADFバインディング・コンテキストは、エンド・ユーザーごとのHttpSessionに存在する1つのオブジェクトです。このコンテキストが保持するのは、軽量アプリケーション・モジュール・データ・コントロール・オブジェクトへの参照であり、このオブジェクトは、各リクエストの開始時におけるプールからのアプリケーション・モジュール・インスタンスの取得と、各リクエストの終了時のプールへの解放を管理します。データ・コントロールは、ユーザー・セッションを識別するADFセッションCookieへの参照を保持します。具体的には、保留中トランザクションで作成または変更されるビジネス・ドメイン・オブジェクトは、この手法を使用するHttpSessionには保存されません。これにより、ユーザーごとに必要なセッション・メモリーが最小になり、サーバーがクラスタ構成の場合のセッション・レプリケーションに関係するネットワーク・トラフィックがなくなります。

信頼性の向上のため、複数のアプリケーション・サーバーがあり、オプションのADF Business Componentsフェイルオーバー・サポートを有効にしている場合は、以降のエンド・ユーザー・リクエストは、サーバー・ファームまたはクラスタ内の任意のサーバーで処理できます。クライアント・ブラウザに保存されるADFセッションCookieは、どのサーバーがリクエストを処理するのかに関係なく、必要な場合にデータベース利用のXMLスナップショットから保留中アプリケーション状態を再能動化するために十分なものです。

28.2.2 受動化と能動化が行われるタイミング

アプリケーション・モジュール状態の自動的な受動化と能動化がいつ行われるのかをわかりやすく説明するため、次のような簡単なケースについて考えます。

  1. HTTPリクエストの開始時に、アプリケーション・モジュール・データ・コントロールは、プールからアプリケーション・モジュール・インスタンスをチェックアウトすることで、beginRequestイベントを処理します。

    アプリケーション・モジュール・プールは、参照されないインスタンスを返します。参照されないアプリケーション・モジュールとは、その時点では他のユーザー・セッションに対する保留中の状態を管理していないモジュールです。

  2. リクエストの終了時に、アプリケーション・モジュール・データ・コントロールは、アプリケーション・モジュール・インスタンスを管理対象状態モードでプールに戻すことで、endRequestイベントを処理します。

    アプリケーション・モジュール・インスタンスは、この時点では、それを使用していたデータ・コントロールによって参照されています。また、アプリケーション・モジュール・インスタンスは、データ・コントロールによって行われ、メモリーに格納された、保留中のトランザクション状態(つまり、エンティティ・オブジェクトおよびビュー・オブジェクトのキャッシュ、行われてまだコミットされていない変更、およびカーソル状態)をまだ含んでいるオブジェクトです。この後で説明するように、このデータ・コントロールに専用のものではなく、参照されているだけです。

  3. 以降のリクエストでは、SessionCookieによって識別される同じデータ・コントロールが、アプリケーション・モジュール・インスタンスを再びチェックアウトします。

    プールが使用するユーザー・アフィニティについてステートレスなアルゴリズムのため、状態がまだメモリー内に存在しているまったく同じアプリケーション・モジュール・インスタンスがプールから返されると思われる場合があります。

ときには、サイトに同時にアクセスするユーザーが非常に多いため、アプリケーション・モジュール・インスタンスを異なるユーザー・セッションが順番に再利用する必要があります。この場合、アプリケーション・プールは、次に示すように、現在参照されているアプリケーション・モジュール・インスタンスを別のセッションによる使用のためにリサイクルする必要があります。

  1. ユーザーAのセッションのアプリケーション・モジュール・データ・コントロールは、リクエストの終了時に、アプリケーション・モジュール・インスタンスをアプリケーション・プールにチェックインします。このインスタンスの名前をAM1とします。

  2. ユーザーZの新しいセッションのアプリケーション・モジュール・データ・コントロールは、初めてプールにアプリケーション・モジュール・インスタンスを要求しますが、使用可能な参照されていないインスタンスはありません。このとき、アプリケーション・モジュール・プールは次のようにします。

    • インスタンスAM1の状態をデータベースに受動化します。

    • 別のセッションが使用するための準備として、AM1の状態をリセットします。

    • AM1インスタンスをユーザーZのデータ・コントロールに返します。

  3. その後のリクエストで、ユーザーAのセッションのアプリケーション・モジュール・データ・コントロールが、プールにアプリケーション・モジュール・インスタンスを要求します。このとき、アプリケーション・モジュール・プールは次のようにします。

    • 参照されていないインスタンスを取得します。

      これは、前述の手順2と同じ手順に従って取得されたインスタンスAM1である場合も、または途中で参照されなくなった場合は別のインスタンスAM2である場合もあります。

    • ユーザーAに対する適切な保留中状態をデータベースから能動化します。

    • アプリケーション・モジュール・インスタンスをユーザーAのデータ・コントロールに返します。

受動化、能動化およびリサイクルのプロセスにより、データ・コントロールによって参照される状態をリクエスト間で保持できます。データ・コントロールごとに専用のアプリケーション・モジュール・インスタンスは必要ありません。前述のシナリオのブラウザ・ユーザーはどちらも、複数のHTTPリクエストに渡ってアプリケーション・トランザクションを継続しますが、エンド・ユーザーには、背景で受動化と能動化が行われていることはわかりません。保留中の変更が継続して表示されるだけです。プロセスでは、エンド・ユーザーが作業の論理的な単位をコミットできる状態になるまで、保留中の変更を基礎のアプリケーション・データベースの表に保存する必要はありません。

アプリケーション・モジュール・プールは、アプリケーション・モジュール・インスタンスを、その保留中状態を管理している現在のデータ・コントロールにできるかぎり結び付けておこうとします。これは、ユーザー・セッション・アフィニティの維持と呼ばれます。データ・コントロールが、各リクエストでまったく同じアプリケーション・モジュール・インスタンスを使用し続けた場合、保留中状態を永続化されたスナップショットから再能動化する際のオーバーヘッドがなくなるので、最善のパフォーマンスが得られます。

28.2.3 オプションのフェイルオーバー・モードを有効にしたときの受動化の変化

構成エディタの「プーリングおよびスケーラビリティ」タブを使用してアプリケーション・モジュールの構成で設定可能なjbo.dofailoverという名前のパラメータがあります。このパラメータは、受動化が発生するタイミングと頻度を制御します。フェイルオーバー機能を無効にすると(デフォルトの設定)、アプリケーション・モジュールの保留状態は、必要なときにのみ受動化されます。これは、プールが現在参照されているアプリケーション・モジュール・インスタンスを別のデータ・コントロールに引き渡す必要があると決定する直前に発生します。

これに対し、フェイルオーバー機能が有効になっていると、アプリケーション・モジュールの保留状態は、アプリケーション・モジュール・プールにチェックインして戻されるたびに受動化されます。これにより、アプリケーション・サーバーの障害に対する最もペシミスティックな保護が提供されます。アプリケーション・モジュール・インスタンスの状態は常に保存され、任意のアプリケーション・モジュール・インスタンスがいつでも能動化できます。もちろん、この機能には、リクエストごとの積極的な受動化によるオーバーヘッドの増加という代償が伴います。


注意:

JDeveloper環境内でフェイルオーバー・サポートを使用するアプリケーションを実行またはデバッグするときは、埋込みOC4Jサーバーを頻繁に起動および停止しています。ADFのフェイルオーバー・メカニズムでは、ユーザーが埋込みサーバーを停止してアプリケーション・サーバーの障害をシミュレートしているのか、それとも初期状態のサーバー・インスタンスで何かを最初から再テストするために停止しているのかを認識する手段がありません。後者の場合は、埋込みサーバーでアプリケーションを再起動する前に、ブラウザを終了することをお薦めします。これにより、アプリケーションでフェイルオーバー・メカニズムの正しい機能に関する部分をテストするつもりがない場合に、このメカニズムが動作することによって発生する混乱を避けることができます。

28.3 状態管理解放レベルの制御

データ・コントロールは、現在のHTTPリクエストの処理が完了したことを示すendRequest通知を処理するとき、アプリケーション・モジュール・インスタンスをアプリケーション・モジュール・プールにチェックインして戻すことで解放します。アプリケーション・モジュール・プールは、インスタンスを管理し、インスタンスをプールに戻すときに使用される解放レベルに基づいて状態管理タスクを実行します(または実行しません)。

ADFは、以降の項で説明する解放レベルをサポートします。

28.3.1 サポートされる解放レベル

管理レベル

これはデフォルトのレベルです。アプリケーション・モジュールの状態には関連性があり、複数のHTTPリクエストに渡ってこのデータ・コントロールの状態を保持する必要があることを意味します。管理レベルは、次のリクエストに対してこのデータ・コントロールが物理的に同じアプリケーション・モジュール・インスタンスを受け取ることは保証しませんが、同じ状態のアプリケーション・モジュールが提供されて、毎回論理的に同じアプリケーション・モジュールであることは保証します。フレームワークは、同じデータ・コントロールに対しては、アプリケーション・モジュールの同じインスタンスを提供できるように最善の努力をします。これはパフォーマンスの向上を目的として行われることで、同じアプリケーション・モジュールであれば、前回のリクエストで同じデータ・コントロールにサービスを提供した後の状態が保持されているため、前回の状態を能動化する必要がありません。ただし、すべてのリクエストでデータ・コントロールが同じインスタンスを受け取る保証はなく、以前にデータ・コントロールにサービスを提供したアプリケーション・モジュールがビジー状態または利用できない場合は、別のアプリケーション・モジュールがそのデータ・コントロールの状態を能動化します。このため、次のリクエストまでの間、コントローラ・レイヤーのコードで、アプリケーション・モジュール・オブジェクト、ビュー・オブジェクト、またはビュー行に対する参照をキャッシュすることは有効ではありません。

このモードは、JDeveloperの以前のリリースではステートフル解放モードと呼ばれていました。


注意:

jbo.ampool.doampooling構成プロパティがfalseの場合は(これは、構成エディタの「プーリングおよびスケーラビリティ」タブで「アプリケーション・モジュール・プーリングの有効化」オプションの選択を解除することに相当します)、事実上プールはありません。この場合、リクエストの最後でアプリケーション・モジュール・インスタンスが解放されると、ただちに削除されます。同じユーザー・セッションによって行われる以降のリクエストでは、各ユーザー・リクエストを処理するために新しいアプリケーション・モジュール・インスタンスを作成する必要があり、保留状態は受動化ストアから再能動化する必要があります。このプロパティをfalseに設定すると、再能動化が行われるときにシステムに予想外の負荷がかかるために発生するアプリケーション・ロジックの問題を発見するときに役立ちます。ただし、通常は、このオプションをfalseに設定して本番システムを実行することはありません。

非管理レベル

このモードは、このデータ・コントロールに関連付けられた状態を現在のHTTPリクエストの終了後に維持する必要がないことを意味します。状態管理に関連するオーバーヘッドがないため、これはパフォーマンスに関して最も効率のよいレベルです。ただし、状態を管理する必要のないアプリケーション、または状態をそれ以上維持する必要がない場合のみに使用を限定する必要があります(典型的な例は、ログアウト・ページでHTTPリクエストを処理した後のアプリケーション・モジュールの解放です)。

このモードは、JDeveloperの以前のリリースではステートレス解放モードと呼ばれていました。

予約レベル

このレベルは、各データ・コントロールが、最初のリクエスト、およびそれ以降にこのデータ・コントロールと関連付けられているHttpSessionから受信するすべてのリクエストの間、専用のアプリケーション・モジュールを割り当てられることを保証します。このデータ・コントロールは、常にアプリケーション・モジュールの同じ物理インスタンスを受け取ります。このモードは、下位互換性および非常にまれで特殊なユースケースのために用意されています。通常は、このモードを使用しないことを強くお薦めします。データ・コントロールとアプリケーション・モジュールの相互関係が1対1になり、アプリケーションのスケーラビリティと信頼性が急激に低下するため、通常はこのモードを使用しないようにします。

なんらかの理由でアプリケーション・モジュールが失われると、データ・コントロールはプールからかわりに他のアプリケーション・モジュールを受け取ることができません。したがってHttpSessionも失われるために、信頼性が損われます。管理レベルではこのようなことはありません。


注意:

予約レベルは、アプリケーションが各リクエストで同じアプリケーション・モジュールを使用することが絶対的に必要であることを意味するため、予約解放レベルで解放されたアプリケーション・モジュールについては、フェイルオーバー・オプションは無視されます。

28.3.2 実行時の解放レベルの設定

デフォルトの管理状態解放レベルを使用しない場合は、プログラムで目的のレベルを設定できます。後の項で説明するAPIを使用してください。

非管理レベル

非管理レベルを使用してアプリケーション・モジュールを解放するようデータ・コントロールを設定するには、DCDataControlクラスでresetState()メソッドを呼び出します(oracle.adf.model.bindingパッケージにあります)。

このメソッドは、リクエスト中のいつでも呼び出すことができます。リクエストの終了時にプールに解放されるとき、アプリケーション・モジュールは状態を受動化しません。このメソッドは現在のリクエストの現在のアプリケーション・モジュール・インスタンスにのみ適用されることに注意してください。その後、アプリケーション・モジュールは非管理レベルでプールに解放され、参照されなくなり、リセットされます。そのアプリケーション・モジュールが次にクライアントによって使用される場合は、デフォルトにより再び管理レベルで使用されます。


注意:

ユーザーが作業の論理単位を終了したことを通知する場合は、プログラムでアプリケーション・モジュールを非管理レベルにして解放できます。後で説明するように、HTTPSessionがタイムアウトしたときはこれが自動的に行われます。

予約レベル

予約レベルを使用してアプリケーション・モジュールを解放するようデータ・コントロールを設定するには、DCJboDataControlクラスのsetReleaseLevel()メソッドを呼び出し(oracle.adf.model.bc4jパッケージにあります)、整数定数ApplicationModule.RELEASE_LEVEL_RESERVEDを渡します。

アプリケーション・モジュールの解放レベルが予約に変更されると、明示的に変更されるまで、それ以降のすべてのリクエストに対して予約レベルのままになります。

管理レベル

アプリケーション・モジュールを予約レベルに設定した場合、DCJboDataControlクラスのsetReleaseLevel()メソッドを呼び出し、整数定数ApplicationModule.RELEASE_LEVEL_MANAGEDを渡すことで、管理レベルに戻すことができます。

この後の項では、これらの解放モードAPIを使用する4種類のコンテキストを示します。

28.3.2.1 JSFバッキングBeanでの解放レベルの設定

例28-1では、JSFバッキングBeanのアクション・メソッドからUserModuleDataControlという名前のデータ・コントロールでresetState()メソッドを呼び出しています。

例28-1 JSFバッキングBeanのアクション・メソッドでのデータ・コントロールのresetState()の呼出し

package devguide.advanced.releasestateless.controller.backing;
import devguide.advanced.releasestateless.controller.JSFUtils;
import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCDataControl;
/**
 * JSF Backing bean for the "Example.jspx" page
 */
public class Example {
  /**
   * In an action method, call resetState() on the data control to cause
   * it to release to the pool with the "unmanaged" release level.
   * In other words, as a stateless application module.
   */
  public String commandButton_action() {
    // Add event code here...
    getDataControl("UserModuleDataControl").resetState();
    return null;
  }
  private DCDataControl getDataControl(String name) {
    BindingContext bc =
      (BindingContext)JSFUtils.resolveExpression("#{data}");
    return bc.findDataControl(name);
  }
}

28.3.2.2 ADF PagePhaseListenerでの解放レベルの設定

例28-2では、カスタムADFページ・フェーズ・リスナー・クラスを使用して、ADFライフサイクルのafter-prepareRenderフェーズからUserModuleDataControlという名前のデータ・コントロールでresetState()メソッドを呼び出しています。ページのページ定義のControllerClass属性にこのクラスの完全修飾名を設定することで、このカスタム・クラスを特定のページと関連付けます。

例28-2 カスタムPagePhaseListenerでのデータ・コントロールのresetState()の呼出し

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.v2.lifecycle.Lifecycle;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPagePhaseListener
       implements PagePhaseListener {
  /**
   * In the "after" phase of the final "prepareRender" ADF Lifecycle
   * phase, call resetState() on the data control to cause it to release
   * to the pool with the "unmanaged" release level. In other words,
   * as a stateless application module.
   *
   * @param event ADF page phase event
   */
  public void afterPhase(PagePhaseEvent event) {
    if (event.getPhaseId() == Lifecycle.PREPARE_RENDER_ID) {
      getDataControl("UserModuleDataControl", event).resetState();
    }
  }
  // Required to implement the PagePhaseListener interface
  public void beforePhase(PagePhaseEvent event) {}
  private DCDataControl getDataControl(String name,
                                       PagePhaseEvent event) {
    return event.getLifecycleContext()
                .getBindingContext()
                .findDataControl(name);
  }
}

28.3.2.3 ADF PageControllerでの解放レベルの設定

例28-3では、ADFページ・コントローラ・クラスのオーバーライドされたprepareRender()メソッドからUserModuleDataControlという名前のデータ・コントロールでresetState()メソッドを呼び出しています。ページのページ定義のControllerClass属性にこのクラスの完全修飾名を設定することで、このカスタム・クラスを特定のページと関連付けます。


注意:

カスタムPagePhaseListenerクラスまたはカスタムPageControllerクラスを使用して、基本的に同じ種類のページ固有ライフサイクル・カスタマイズ・タスクを実行できます。重要な違いは、PagePhaseListenerインタフェースは任意のクラスに実装できますが、カスタムPageControlleroracle.adf.controller.v2.lifecycleパッケージのPageControllerクラスを拡張する必要があります。

例28-3 カスタムADF PageControllerでのデータ・コントロールのresetState()の呼出し

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.v2.context.LifecycleContext;
import oracle.adf.controller.v2.lifecycle.PageController;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPageController extends PageController {
  /**
   * After calling the super in the final prepareRender() phase
   * of the ADF Lifecycle, call resetState() on the data control
   * to cause it to release to the pool with the "unmanaged"
   * release level. In other words, as a stateless application module.
   *
   * @param lcCtx ADF lifecycle context
   */
  public void prepareRender(LifecycleContext lcCtx) {
    super.prepareRender(lcCtx);
    getDataControl("UserModuleDataControl", lcCtx).resetState();
  }
  private DCDataControl getDataControl(String name,
                                       LifecycleContext lcCtx) {
    return lcCtx.getBindingContext().findDataControl(name);
  }
}

28.3.2.4 カスタムADF PageLifecycleでの解放レベルの設定

すべてのリクエストを完全にステートレスな方法で処理するADFアプリケーションを作成する場合は、例28-4で示すように、グローバル・カスタムPageLifecycleクラスを使用します。グローバルな方法でカスタム・ライフサイクルを使用するためのアプリケーションの構成方法の詳細は、10.5.4.1項「ADFページ・ライフサイクルのグローバルなカスタマイズ」を参照してください。

例28-4 カスタムADF PageLifecycleでのデータ・コントロールのresetState()の呼出し

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.faces.lifecycle.FacesPageLifecycle;
import oracle.adf.controller.v2.context.LifecycleContext;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPageLifecycle extends FacesPageLifecycle {
  /**
   * After calling the super in the final prepareRender() phase
   * of the ADF Lifecycle, call resetState() on the data control
   * to cause it to release to the pool with the "unmanaged"
   * release level. In other words, as a stateless application module.
   *
   * @param lcCtx ADF lifecycle context
   */
  public void prepareRender(LifecycleContext lcCtx) {
    super.prepareRender(lcCtx);
    getDataControl("UserModuleDataControl", lcCtx).resetState();
  }
  private DCDataControl getDataControl(String name,
                                       LifecycleContext lcCtx) {
    return lcCtx.getBindingContext().findDataControl(name);
  }
}

28.4 保存される状態とクリーンアップされるタイミング

受動化によって保存される情報は、トランザクション状態と非トランザクション状態の2つの部分に分かれます。トランザクション状態は、エンティティ・オブジェクト・データに対して行われた、データベースへの保存が意図されている更新のセットであり、エンティティ・オブジェクトに対して直接実行されるか、ビュー・オブジェクトの行を介してエンティティに実行されます。非トランザクション状態は、現在の行インデックス、WHERE句、ORDERBY句など、ビュー・オブジェクトの実行時設定で構成されます。

28.4.1 保存される状態

アプリケーション・モジュールの受動化スナップショットの一部として保存される情報には、次のものが含まれます。

トランザクション状態
  • このユーザー・セッションに対するルート・アプリケーション・モジュールのエンティティ・キャッシュにおける新しいエンティティ、変更されたエンティティおよび削除されたエンティティ(変更の場合は新旧の値を含みます)

非トランザクション状態
  • アクティブ・ビュー・オブジェクトごとに次のもの(静的および動的に作成されたもの両方):

    • 各行セットの現在の行インジケータ(通常は1)

    • 新しい行とその位置(新しい行は、更新された行とは別に扱われます。VOにおけるそのインデックスも追跡されます)

    • ViewCriteriaおよびビュー基準行などの関連するすべてのパラメータ

    • 行セットが実行されたかどうかを示すフラグ

    • 範囲の開始と範囲のサイズ

    • アクセス・モード

    • フェッチ・モードとフェッチ・サイズ

    • すべてのビュー・オブジェクト・レベルのカスタム・データ

    • 動的に作成された場合、またはビュー定義から変更された場合は、SELECT、FROM、WHERE、ORDER BYの各句


注意:

ADF Business Componentsのランタイム診断を有効にしている場合は、各XML状態スナップショットの内容。診断を有効にする方法の詳細は、5.5.3.2項「ADF Business Componentsデバッグ診断の有効化」を参照してください。

28.4.2 状態が保存される場所

デフォルトでは、受動化スナップショットはデータベースに保存されますが、かわりにファイル・システムを使用するよう構成できます。

28.4.2.1 データベース利用の受動化の動作方法

受動化されたXMLスナップショットは、jbo.server.internal_connectionプロパティで指定されている接続を使用して、PS_TXNという名前の表のBLOB列に書き込まれます。受動化レコードが保存されるたびに、受動化レコードには、PS_TXN_SEQ順序から取得された順序番号に基づいて、一意の受動化スナップショットIDが割り当てられます。ADFバインディング・コンテキストのアプリケーション・モジュール・データ・コントロールによって保持されるADFセッションCookieは、かわりに作成された最新の受動化スナップショットIDと、使用された前回のIDを記憶しています。

28.4.2.2 状態管理表が存在するスキーマの制御

ADFランタイムは、PS_TXN表およびPS_TXN_SEQ順序の作成に使用する必要のあるデータベース接続/スキーマを制御するjbo.server.internal_connectionという名前の構成プロパティを認識します。この構成パラメータの値が明示的に設定されていない場合、状態管理機能は、現在のアプリケーション・データベース接続の資格証明を使用して、一時的な表を作成します。

一時的な情報を別に保持するため、状態管理機能は、データベース接続プールから別の接続インスタンスを使用しますが、データベース資格証明は現在のユーザーと同じです。フレームワークは、一時的な表を作成し、存在しない場合には順序を作成することがあるため、jbo.server.internal_connectionの値を設定しないということは、現在のデータベース・ユーザーが、CREATE TABLECREATE INDEX、およびCREATE SEQUENCEの各権限を持つ必要があることを意味します。これは望ましくない場合が多いため、jbo.server.internal_connectionプロパティには常に適切な値を指定し、表とスキーマが作成される状態管理スキーマに対して資格証明を提供することをお薦めします。構成のjbo.server.internal_connectionプロパティに対する有効な値は、次のとおりです。

  • 次のような、完全修飾されたJDBC接続URL:

    jdbc:oracle:thin:someuser/somepassword@host:port:SID

  • 次のような、JDBCデータ・ソース名:

    java:/comp/env/jdbc/YourJ2EEDataSourceName

28.4.2.3 受動化ストアの種類の構成

受動化された情報は、複数の場所に格納できます。格納場所は、プログラムで制御することも、またはアプリケーション・モジュール構成でオプションを構成して制御することもできます。選択肢は、データベースまたはローカル・ファイル・システムに格納されるファイルです。

  • ファイル

    ファイルにアクセスする方がデータベースにアクセスするより速いため、これは使用できる最も速い選択肢である場合があります。中間層全体(1つまたは複数のOracle Application ServerのインストールおよびそのすべてのOC4Jインスタンス)が同じマシンにインストールされているか、共通の共有ファイル・システムにアクセスできる場合に、受動化された情報にすべてからアクセス可能であれば、これは適切な選択です。通常、この選択肢は、使用されているOracle Application Serverが1台である小さい中間層に適しています。つまり、Oracle Application Serverが1台で、そのすべてのコンポーネントが1台の物理マシンにインストールされているような小型の中間層には、非常にふさわしい選択です。永続スナップショット・ファイルの場所と名前は、jbo.tmpdirプロパティ(指定されている場合)によって決まります。この指定は、構成プロパティに関する通常のADFプロパティ優先順位の規則に従います。他になにも指定されていない場合、場所はuser.dirによって決まります(指定されている場合)。これはデフォルトのプロパティであり、OS固有です。

  • データベース

    これはデフォルトの選択肢です。ファイルへの受動化より少し遅くなる場合がありますが、信頼性ははるかに優れています。ファイルへの受動化でよくある問題は、リモートでインストールされているOracle Application Serverインスタンスがアクセスできないことです。この場合、クラスタ環境では、1つのノードがダウンすると、他のノードは受動化された情報にアクセスできず、フェイルオーバーが機能しません。可能性のあるもう1つの問題は、リモート・ノードがファイルにアクセスできたとしても、ローカル・ノードとリモート・ノードでアクセス時間が大きく異なり、パフォーマンスが一定でなくなります。データベース・アクセスの場合、時間はすべてのノードでほぼ同じです。

設計時に選択した値を設定するには、jbo.passivationstoreプロパティにdatabaseまたはfileを設定します。値nullは、接続の種類に固有のデフォルトを使用することを示します。OracleまたはDB2の場合はデータベースによる受動化を使用し、それ以外はファイル・シリアライズを使用します。

保管方法をプログラムで設定するには、oracle.jbo.ApplicationModuleインタフェースのsetStoreForPassiveState()メソッドを使用します。指定できるパラメータ値は次のとおりです。

  • PASSIVATE_TO_DATABASE

  • PASSIVATE_TO_FILE

28.4.3 状態がクリーンアップされるタイミング

通常の環境では、ADF状態管理機能は、受動化スナップショット・レコードを自動的にクリーンアップします。

28.4.3.1 次のスナップショットが取得されたときの前のスナップショットの削除

前述のとおり、セッションCookieのかわりに受動化レコードがデータベースに保存されたとき、この受動化レコードには新しい一意のスナップショットIDが設定されます。同じトランザクションの一部として、その同じセッションCookieによって使用されていた前のスナップショットIDを持つ受動化レコードは削除されます。これにより、サーバー障害がなければ、受動化スナップショット・レコードはアクティブなエンド・ユーザー・セッションごとに1つのみ存在します。

28.4.3.2 非管理解放で削除される受動化スナップショット

セッションCookieに関係する受動化スナップショット・レコードは、アプリケーション・モジュールが非管理状態レベルでプールにチェックインされると削除されます。これは次の場合に発生します。

  • コードがアプリケーション・モジュール・データ・コントロールでresetState()を明示的に呼び出す場合

  • たとえば明示的なログアウト機能の実装の一部などとして、コードがHttpSessionを明示的に無効化する場合

  • アイドル時間に対するセッション・タイムアウトのしきい値を超え、フェイルオーバー・モードが無効(デフォルト)になっているため、HttpSessionがタイムアウトする場合

いずれの場合も、アプリケーション・モジュール・プールは、セッションCookieによって参照されていたアプリケーション・モジュールも参照されていない状態にリセットします。基礎となるデータベース表には変更が保存されていないため、保留中のセッション状態スナップショットが削除されると、その時点までにユーザー・セッションが完了した未終了作業のトレースは残りません。

28.4.3.3 フェイルオーバー・モードでの受動化スナップショットの維持

フェイルオーバー・モードが有効になっていると、セッションが非アクティブなためにHttpSessionがタイムアウトした場合、ユーザーがブラウザに戻ったときに作業を継続できるよう、受動化スナップショットが維持されます。

アクションが中断された後、エンド・ユーザーがブラウザに戻ってアプリケーションの使用を続けると、なにも変化がなかったかのように作業は継続されます。フェイルオーバー・モードでは、ADFランタイムは、フェイルオーバー・モードでアプリケーションを実行しているクライアントごとに、ADFランタイムが最新の受動化スナップショットIDを追跡するために使用する、追加のブラウザCookieを保存します。これにより、ユーザーの次のリクエストが新しいHttpSessionのコンテキストで処理されても(別のアプリケーション・サーバー・インスタンスであっても)、ユーザーはこのことに気付きません。追加のブラウザCookieは、リクエストを処理する前のユーザーの最新の保留中状態スナップショットで、使用可能なアプリケーション・モジュール・インスタンスを再能動化するために使用されます。


注意:

アプリケーション・モジュールが予約レベルで解放された場合は、HttpSessionはタイムアウトし、ユーザーは認証プロセスを実行する必要があり、保存されていない変更はすべて失われます。

28.4.4 HttpSessionのタイムアウトの方法

HTTPはステートレス・プロトコルであるため、クライアントがブラウザを閉じたり週末で不在になったことの暗黙の通知を、サーバーが受け取ることはありません。このため、J2EEに準拠するサーバーは、ユーザーがリクエスを停止したときにHTTPセッションに結び付けられているリソースを解放できるよう、標準的で構成可能なセッション・タイムアウトのメカニズムを提供します。プログラムでタイムアウトを強制することもできます。

28.4.4.1 ユーザーの非アクティブ状態による暗黙のタイムアウトの構成

web.xmlファイルのsession-timeoutタグを使用して、セッションのタイムアウトのしきい値を構成できます。デフォルト値は35分です。HttpSessionがタイムアウトすると、BindingContextはスコープ外となり、それとともに、アプリケーション・モジュールを参照していたすべてのデータ・コントロールは、管理状態レベルでプールに解放されます。アプリケーション・モジュール・プールは、参照されていたアプリケーション・モジュールをリセットし、インスタンスを再び参照されていない状態にします。

28.4.4.2 明示的なHttpSessionタイムアウトのコーディング

セッション・タイムアウトの期限が切れる前にユーザーのセッションを終了するには、ユーザーがログアウトのボタンやリンクをクリックした際に、バッキングBeanからHttpSessionオブジェクトのinvalidate()メソッドを呼び出します。これにより、セッションがタイムアウトした場合と同じ方法でHttpSessionがクリーンアップされます。JSFとADFを使用して、セッションを無効化した後は、単に転送を行うのではなく、表示する次のページへのリダイレクトを行う必要があります。例28-5では、SRDemoアプリケーションのSRLogout.javaバッキングBeanがこのタスクを実行するために使用するコードを示します。

例28-5 プログラムによる終了とリダイレクト

// In SRLogout.java, backing bean for the logout.jspx page
public String logoutButton_action() throws IOException{
  ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
  HttpServletResponse response = (HttpServletResponse)ectx.getResponse();
  HttpSession session = (HttpSession)ectx.getSession(false);
  session.invalidate();
  response.sendRedirect("SRWelcome.jspx");
  return null;
}

暗黙のタイムアウトと同様に、HTTPセッションをこの方法でクリーンアップすると、参照されていたアプリケーション・モジュールは非参照としてマークされます。

28.4.5 一時的な記憶域表のクリーンアップ

JDeveloperでは、状態管理表を定期的にクリーンアップするための手段として、/BC4J/binディレクトリのbc4jcleanup.sqlスクリプトが提供されています。サーバーが開発時や障害時などに異常な方法でシャットダウンすると、永続的スナップショット・レコードが時間とともに蓄積します。SQL*Plusでスクリプトを実行すると、BC4J_CLEANUP PL/SQLパッケージが作成されます。このパッケージには、2つの関連するプロシージャがあります。

  • PROCEDURE Session_State( olderThan DATE )

    このプロシージャは、特定の日付より古いセッションのアプリケーション・モジュール・セッション状態記憶域をクリーンアップします。

  • PROCEDURE Session_State( olderThan_minutes INTEGER )

    このプロシージャは、特定の分数より古いセッションのアプリケーション・モジュール・セッション状態記憶域をクリーンアップします。

このパッケージの適切なプロシージャの呼出しをデータベース・ジョブとして発行することで、ADFの一時的永続記憶域の定期的なクリーンアップをスケジュールできます。

例28-6で示すような匿名PL/SQLブロックを使用することで、bc4j_cleanup.session_state()の実行を、翌日の午前2時に開始して以降は毎日実行し、状態が1日(1440分)を超えているセッションをクリーンアップするようスケジュールできます。

例28-6 状態管理表の定期的クリーンアップのスケジュール

SET SERVEROUTPUT ON
DECLARE
  jobId    BINARY_INTEGER;
  firstRun DATE;
BEGIN
  -- Start the job tomorrow at 2am
  firstRun := TO_DATE(TO_CHAR(SYSDATE+1,'DD-MON-YYYY')||' 02:00',
              'DD-MON-YYYY HH24:MI');
   -- Submit the job, indicating it should repeat once a day
  dbms_job.submit(job       => jobId,
                  -- Run the BC4J Cleanup for Session State
                  -- to cleanup sessions older than 1 day (1440 minutes)
                  what      => 'bc4j_cleanup.session_state(1440);',
                  next_date => firstRun,
                  -- When completed, automatically reschedule
                  -- for 1 day later
                  interval  => 'SYSDATE + 1'
                 );
  dbms_output.put_line('Successfully submitted job. Job Id is '||jobId);
END;
.
/

28.5 カスタム・ユーザー固有情報の管理

メンバー変数の形式、またはoracle.jbo.Sessionユーザー・データ・ハッシュテーブルに格納されるカスタム情報の形式で、カスタム・ユーザー定義情報をアプリケーション・モジュールに追加することは、ごく一般的な方法です。ADF状態管理機能では、このカスタム情報を受動化スナップショットにも保存するためのメカニズムが提供されています。ApplicationModuleImplクラスの次の2つのメソッドをオーバーライドすることで、カスタム情報の書込みと読取りを行うことができます。

protected void passivateState(Document doc, Element parent)
public void activateState(Element elem)

この2つのメソッドをオーバーライドして、カスタム・アプリケーション・モジュール状態を受動化/能動化サイクルに組み込む方法を、次の簡単な例を使用して説明します。アプリケーション・モジュールに含まれるjbo.counterという名前のカスタム・パラメータを、アプリケーション・モジュール状態の受動化と能動化の過程で保持するものとします。各アプリケーション・モジュールにはoracle.jbo.Sessionというオブジェクトが関連付けられており、アプリケーション・モジュール固有のセッション・レベル状態を格納します。セッションにはユーザー・データ・ハッシュテーブルがあり、一時的な情報を格納できます。ユーザー固有のデータをアプリケーション・モジュールの受動化と能動化の過程で保持するには、このカスタム値をアプリケーション・モジュール状態の受動化スナップショットに保存してリストアするコードを記述する必要があります。例28-7は、この処理を行うために、カスタム・アプリケーション・モジュール・クラスでオーバーライドしたpassivateState()メソッドとactivateState()メソッドに記述するコードを示しています。

オーバーライドされたpassivateState()メソッドは、次の手順を実行します。

  1. 保存する値を取得します。

  2. 値を収めるXML要素を作成します。

  3. 値を表すためのXMLテキスト・ノードを作成します。

  4. テキスト・ノードを要素の子として追加します。

  5. 渡される親要素に要素を追加します。


注意:

XMLドキュメントでノードを操作するために使用するAPIは、org.w3c.domパッケージのDocument Object Model(DOM)インタフェースで提供されています。これらはJava API for XML Processing(JAXP)の一部です。詳細は、このパッケージに含まれるNodeElementTextDocumentNodeListの各インタフェースのJavaDocを参照してください。

オーバーライドされたactivateState()メソッドは、次の手順で逆の処理を実行します。

  1. jbo.counter要素の要素を検索します。

  2. 見つかった場合は、ノード・リストで見つかったノードをループします。

  3. jbo.counter要素の最初の子ノードを取得します。

    これはDOM Textノードで、その値は前述のpassivateState()メソッドが呼び出されたときに保存された、jbo.counter属性の値を表す文字列です。

  4. カウンタの値に、スナップショットから能動化された値を設定します。

例28-7 状態スナップショットXMLドキュメントでのカスタム情報の受動化と能動化

/**
 * Overridden framework method to passivate custom XML elements
 * into the pending state snapshot document
 */
public void passivateState(Document doc, Element parent) {
  // 1. Retrieve the value of the value to save
  int counterValue = getCounterValue();
  // 2. Create an XML element to contain the value
  Node node = doc.createElement(COUNTER);
  // 3. Create an XML text node to represent the value
  Node cNode = doc.createTextNode(Integer.toString(counterValue));
  // 4. Append the text node as a child of the element
  node.appendChild(cNode);
  // 5. Append the element to the parent element passed in
  parent.appendChild(node);
}
/**
 * Overridden framework method to activate  custom XML elements
 * into the pending state snapshot document
 */
public void activateState(Element elem) {
  super.activateState(elem);
  if (elem != null) {
    // 1. Search the element for any <jbo.counter> elements
    NodeList nl = elem.getElementsByTagName(COUNTER);
    if (nl != null) {
      // 2. If any found, loop over the nodes found
      for (int i=0, length = nl.getLength(); i < length; i++) {
        // 3. Get first child node of the <jbo.counter> element
        Node child = nl.item(i).getFirstChild();
        if (child != null) {
          // 4. Set the counter value to the activated value
          setCounterValue(new Integer(child.getNodeValue()).intValue()+1);
          break;
        }
      }
    }
  }
}
/*
 * Helper Methods
 */
private int getCounterValue() {
  String counterValue = (String)getSession().getUserData().get(COUNTER);
  return counterValue == null ? 0 : Integer.parseInt(counterValue);
}
private void setCounterValue(int i) {
  getSession().getUserData().put(COUNTER,Integer.toString(i));
}
private static final String COUNTER = "jbo.counter";

注意:

ViewObjectImplクラスとEntityObjectImplクラスでも、似たメソッドを使用して、これらのオブジェクトのカスタム状態を受動化スナップショットに保存できます。

28.6 一時ビュー・オブジェクトの状態の管理

ビュー・オブジェクト・エディタの「チューニング」ページの「受動状態」チェック・ボックスを使用して、各ビュー・オブジェクトを宣言的に受動化可能または不可能にすることができます。ビュー・オブジェクトが受動化可能ではない場合は、ビュー・オブジェクトに関する情報はアプリケーション・モジュールの受動化スナップショットに書き込まれません。

受動化と能動化に関して、一時ビュー・オブジェクトとSQL計算ビュー・オブジェクトはどちらも、同じように扱われます。

一時ビュー・オブジェクトの属性は、デフォルトでは受動化されません。その性質上、一時ビュー・オブジェクトの属性は読取り専用として意図されていることが普通であり、非常に簡単に再作成できます。そのため、通常は、その値をXMLスナップショットの一部として受動化しても意味がありません。ただし、ビュー・オブジェクト・エディタの「属性」ページで、任意の一時属性に対する「受動化」チェック・ボックスを選択することで、宣言的に受動化可能として構成できます。

デフォルトでは、すべてのビュー・オブジェクトが受動化可能に設定され、すべての一時属性が受動化不可能に設定されています。つまり、一時ビュー・オブジェクト(一時属性のみを含むビュー・オブジェクト)は受動化可能に設定されていますが、現在行およびその他の非トランザクション状態に関連する情報のみを受動化します。

トランザクション機能はエンティティ・オブジェクト・レベルで管理されることが普通であるため、一時ビュー・オブジェクト属性の受動化はリソースやパフォーマンスの点で効率が悪いことに注意してください。一時ビュー・オブジェクトはエンティティ・オブジェクトに基づいていないため、すべての更新は、エンティティ・キャッシュではなく、ビュー・オブジェクト行キャッシュで管理されます。したがって、一時ビュー・オブジェクトまたは一時ビュー・オブジェクト属性を受動化するには、特別なランタイム処理が必要になります。

通常、受動化では変更された値のみを保存しますが、一時ビュー・オブジェクトの受動化では行全体を保存する必要があります。行には、受動化として設定されたビュー・オブジェクト属性のみが含まれます。

28.7 中間層セーブポイントに対する状態管理の使用

データベース・サーバーのセーブポイント機能にはなじみがあると思われます。これは、トランザクション全体をロールバックするのではなく、トランザクション内の特定のポイントまでロールバックできる機能です。アプリケーション・モジュールも同じ機能を提供しますが、中間層に実装されています。oracle.jbo.ApplicationModuleインタフェースの3つのメソッドを使用して、この機能を利用できます。次の3つのメソッドです。

public String passivateStateForUndo(String id,byte[] clientData,int flags)
public byte[] activateStateForUndo(String id,int flags)
public boolean isValidIdForUndo(String id)

これらのメソッドを使用すると、名前付きスナップショットのスタックを作成し、名前を指定してスタックから保留中トランザクションの状態をリストアできます。これらのスナップショットは、トランザクションの期間(コミットまたはロールバックのイベントまで)を超えては存在しないことに注意してください。この機能を使用すると、変更を元に戻したりやりなおしたりといったアプリケーションの複雑な機能を開発できます。この機能を有効に利用するもう1つの用途は、ブラウザの「戻る」ボタンと「進む」ボタンをアプリケーション固有の方法で動作するようにすることです。それ以外では、これらのメソッドを単純に使用するだけでも、十分に便利な場合があります。

28.8 アプリケーション・モジュールの能動化が安全であることの確認テスト

保留状態を受動化スナップショットから能動化したときにアプリケーション・モジュールが機能することを明確にテストしていない場合、システムに高い負荷がかかることによってシステムのこの面が初めてテストされたときに、本番環境で予期せず不愉快な経験をする可能性があります。

28.8.1 jbo.ampool.doampooling構成パラメータ

jbo.ampool.doampooling構成プロパティは、構成エディタの「プーリングおよびスケーラビリティ」タブの「アプリケーション・モジュール・プーリングを使用可能にする」オプションに対応しています。デフォルトでは、このチェック・ボックスは選択されていて、アプリケーション・モジュール・プーリングは有効になっています。アプリケーションを本番環境で実行するときは、ほとんどの場合、jbo.ampool.doampoolingをデフォルト設定のtrueにしておきます。ただし、テストでは、このプロパティをfalseに設定することが重要になります。このプロパティがfalseの場合、実質的にアプリケーション・プールはありません。リクエストの最後でアプリケーション・モジュール・インスタンスが解放されると、ただちに削除されます。同じユーザー・セッションによって行われる以降のリクエストでは、リクエストを処理するために新しいアプリケーション・モジュール・インスタンスを作成する必要があり、アプリケーション・モジュールの保留状態を受動化ストアから再能動化する必要があります。

28.8.2 能動化をテストするためのアプリケーション・モジュール・プーリングの無効化

全体的なテスト計画の一部として、jbo.ampool.doampooling構成パラメータをfalseに設定した状態でアプリケーション・モジュールをテストする方法を採用することをお薦めします。このように設定すると、アプリケーション・モジュール・プーリングは完全に無効になり、ページ・リクエストがあるたびに、システムは受動化スナップショットからアプリケーション・モジュールの保留状態を能動化することを余儀なくされます。これは、カスタム・アプリケーション・コードで行われている想定のために本番環境で発生する可能性のある問題を検出する優れた方法です。

たとえば、受動化されていると確信している一時ビュー・オブジェクト属性がある場合、この手法を使用すると、意図したとおりに動作しているかどうかをテストできます。さらに、次のものを導入している状況を考えてみます。

  • アプリケーション・モジュール、ビュー・オブジェクトまたはエンティティ・オブジェクトのプライベート・メンバー・フィールド

  • Sessionユーザー・データ・ハッシュテーブルのカスタム・ユーザー・セッション状態

カスタム・コードでは、このカスタム状態がHTTPリクエスト間で維持されているものとされている場合があります。JDeveloperの埋込みOC4Jサーバーを使用して1人のユーザーでテストしている場合、または少数のユーザーでテストしている場合は、問題なく動いているように見えます。これは、ADFアプリケーション・モジュール・プールのアフィニティによるステートレスの最適化によるものです。システムの負荷が許せば、プールは後のリクエストでもユーザーに同じアプリケーション・モジュール・インスタンスを返し続けます。しかし、負荷が大きい、現実の使用においては、この最適化を実現できない場合があり、使用できる任意のアプリケーション・モジュール・インスタンスを取得して、受動化スナップショットから保留状態を再能動化することが必要になります。カスタム・コンポーネントの状態を受動化スナップショットに保存してそこからリロードするためのpassivateState()activateState()のオーバーライド(28.5項「カスタム・ユーザー固有情報の管理」の説明を参照)が正しく行われていないと、この再能動化ステップの後でカスタム状態が失われます(つまり、nullになるか、デフォルト値に戻ります)。jbo.ampool.doampoolingをfalseに設定してテストすることで、コードに存在するこの種の状況を素早く検出できます。

28.9 保留データベース状態に関する注意事項

これまで見てきたように、ADFの状態管理メカニズムは、受動化と能動化を利用して、アプリケーション・モジュール・インスタンスの状態を管理します。この機能の安定した実装は、すべての保留中の変更が中間層のアプリケーション・モジュール・トランザクションによって管理される場合にのみ可能です。最もスケーラブルな戦略は、保留中の変更を中間層のオブジェクトに維持し、HTTPリクエストの間で保留データベース状態が存在するような操作を実行しないようにすることです。これにより、アプリケーション・モジュール・プールが提供するパフォーマンスの最適化を最大限に利用し、アプリケーションにとって最も安定したランタイム動作を実現できます。

28.9.1 Webアプリケーションに要求されるオプティミスティック・ロックの使用

Webアプリケーションにはオプティミスティック・ロックを使用することをお薦めします。デフォルトであるペシミスティック・ロックは、行レベル・ロックの形でデータベースに保留トランザクション状態を作り出すため、Webアプリケーションでは使用しないようにする必要があります。ペシミスティック・ロックを設定しても状態管理は動作しますが、ロック・モードは予想したほどのパフォーマンスになりません。裏側では、アプリケーション・モジュールがリサイクルされるたびに、JDBC接続でロールバックが発行されます。これにより、ペシミスティック・ロックが作成していたすべてのロックが解放されます。

オプティミスティック・ロックを使用するように構成を変更するには、構成エディタの「プロパティ」タブを開き、jbo.locking.modeの値をオプティミスティックに設定します。

28.9.2 現在のリクエストの間のPostChangesのみの使用

9.2.2項「コミット処理と検証の理解」で見たように、postChanges()メソッドを使用する場合は慎重に検討する必要があります。状態管理、アプリケーション・プーリング、およびデータベース接続プーリングを検討するときは特にそうです。postChanges()メソッドはコミット処理ライフサイクルの一部としてのみ使用し、それによって作成される保留データベース状態が同じリクエストの間にコミットまたはロールバックされるようにします。

28.9.3 リクエスト間の保留データベース状態で必要な予約レベル

なんらかの理由で、postChanges()メソッドを呼び出して、またはPL/SQLのストアド・プロシージャを呼び出して、リクエストの間にデータベースにトランザクション状態を作成する必要があり、同じリクエストの終わりまでにコミットまたはロールバックを発行できない場合は、後のリクエストでコミットまたはロールバックするまでの間に、そのリクエストから予約レベルでアプリケーション・モジュール・インスタンスを解放する必要があります。データベースでのトランザクション状態の作成から最後のコミットまたはロールバックの実行までを可能なかぎり短くして、予約レベルを長時間使用する必要がないようにすることをお薦めします。これは、予約レベルがアプリケーションのスケーラビリティと信頼性に悪影響を及ぼすためです。

アプリケーション・モジュールを予約レベルで解放すると、予約レベルを明示的に管理レベルまたは非管理レベルに変更して戻すまで、以降のすべてのリクエストは予約レベルのままになります。したがって、コミットまたはロールバックを発行したら、責任を持って予約レベルを管理レベルに戻す必要があります。

28.9.4 接続プーリングによる保留データベース状態の妨害

構成エディタの「プーリングおよびスケーラビリティ」タブで「解放時にアプリケーション・モジュールを切断(接続プーリングを使用)」プロパティを設定すると、jbo.doconnectionpooling構成パラメータがtrueに設定されます。

この接続プーリング・オプションを有効にした場合(一般に、データベース接続の共通プールを複数のアプリケーション・モジュール・プールで共有するため)、アプリケーション・モジュールをアプリケーション・モジュール・プールに解放すると、そのJDBC接続が解放されてデータベース接続プールに戻され、その接続でROLLBACKが発行されます。その結果、ポストされていてコミットされていなかったすべての変更が失われます。次のリクエストで、アプリケーション・モジュールが使用されると、プールからJDBC接続を受け取りますが、前回使用していたものとは異なるJDBC接続インスタンスである可能性があります。これにより、データベースにポストされただけで、前のリクエストの間にコミットされていなかった変更は、存在しなくなります。