ヘッダーをスキップ

Oracle Applications開発者ガイド
リリース12
E06048-01
目次へ
目次
前のページへ
前へ
次のページへ
次へ

Oracle ApplicationsでのPL/SQL使用

ApplicationsでのPL/SQL使用の概要

Oracle Applicationsで構築するアプリケーションの一部としてPL/SQLプロシージャを使用できます。コーディング標準に従うことによって、アプリケーションおよびOracle Applicationsとシームレスに統合されるプロシージャを作成できます。

PL/SQLの使用目的は次のとおりです。

SQLへのOracleの手続き型言語拡張であるPL/SQLを使用して、Oracleのツールで作成したカスタム・フォームおよびレポートへのプロシージャ拡張を開発できます。

たとえば、Oracle Applicationsの標準に従ったフォームを開発するには、フォーム・コードをPL/SQLビジネス・ルール・プロシージャ、項目ハンドラ、イベント・ハンドラおよび表ハンドラに編成します。非常に小さなPL/SQLコードをフォーム・トリガーに直接に入れるのは、それらのトリガーが論理モデルを表していないためです。それらのトリガーは、プロシージャ・コードをコールするためにOracle Formsが提供する、単なるイベント・ポイントです。パッケージされたPL/SQLプロシージャでコードを活用し、さらにトリガーからそれらのプロシージャをコールする場合には、開発と保守が容易なモジュール化フォーム・コードを作成します。

フォーム・コードのモジュール化に役立つPL/SQLプロシージャを作成することが可能です。たとえば、項目ハンドラ、イベント・ハンドラまたはビジネス・ルール・プロシージャは、実際にはより小さな複数のプロシージャから構成されます。これらのより小さなプロシージャを論理的なパッケージにグループ化すると、その目的が明確になります。(これらのより小さなプロシージャには特別な名称はありません。単にPL/SQLプロシージャと呼ばれます。)

また、PL/SQLを使用して、コンカレント・プログラム、あるいはコンカレント・プログラムからコールされるストアド・プロシージャを開発できます。一般に、Oracle Applicationsの過去のリリースで即時コンカレント・プログラムとして開発したコンカレント・プログラムなら、PL/SQLコンカレント・プログラムとして開発することが可能です。または、コンカレント・プログラムの本体をC言語で開発することは可能ですが、コンカレント・プログラムにより発行されたSQL文はすべてPL/SQLストアド・プロシージャにカプセル化されます。

関連項目: PL/SQLストアド・プロシージャ

定義

この章で使用される2つの用語の定義を次に示します。

サーバー側

「サーバー側」は、(データベース・サーバー上の)Oracleデータベースに格納されているPL/SQLプロシージャを記述するのに使用する用語です。データベースに格納されているプロシージャおよびファンクションはストアド・プロシージャおよびファンクションとも呼ばれ、データベース・サーバー側プロシージャとも呼ばれることがあります。

クライアント側

「クライアント側」は、Oracle Forms、Oracle Reportsおよびライブラリなど、データベースのクライアントであるプログラムで実行されるPL/SQLプロシージャを記述するのに使用する用語です。

このマニュアルの「クライアント側」という用語は、通常は(フォームが存在する)フォーム・サーバーを指します。このマニュアルの「クライアント側」は、Webブラウザを実行中のパーソナル・コンピュータまたはその他のデスクトップ・マシンを通常意味する「デスクトップ・クライアント」は指していません。

一般的なPL/SQLコーディング標準

準拠する必要がある一般的な標準を次に示します。

常にパッケージを使用

PL/SQLプロシージャは、パッケージ内で常に定義されている必要があります。フォームまたはその他の論理的にグループ化されたコードのブロックごとにパッケージを作成します。

パッケージ・サイズ

クライアント側の(Oracle Forms)PL/SQLプログラム・ユニットのソース・コードおよびコンパイル済コードは、あわせて64Kより小さくする必要があります。(プログラム・ユニットは、パッケージ仕様または本体またはスタンドアローン・プロシージャ)です。)つまり、プログラム・ユニットのソース・コードは10Kを超えることができないことを意味します。

パッケージが10Kの制限を超える場合、1つ以上の「プライベート・パッケージ」にプライベート変数およびプロシージャを置くことによってパッケージのサイズを減らすことができます。標準では、プライベート・パッケージにある変数およびプロシージャにアクセスするのは、元のパッケージのみです。個別のプロシージャがサイズ制限を超える場合は、コードを2つ以上のプロシージャに分割する必要があります。

Oracle Forms PL/SQLプロシージャが64Kの制限を超える場合は、生成時にOracle Formsがエラーを示します。

サーバー側のパッケージおよびプロシージャにはサイズ制限はありませんが、Oracle Formsがサーバー側のパッケージまたはプロシージャを参照する際に、サイズ制限のあるローカル・スタブが作成されます。パッケージ・スタブのサイズは、パッケージにあるプロシージャの数およびそれぞれのプロシージャが持つ引数の数とタイプに依存します。10Kの制限を超えないようにするためには、パッケージ内のプロシージャ数が25より小さくなるようにしてください。

既存パッケージへの新規プロシージャの追加

既存のパッケージ(データベースまたはOracle Formsライブラリのいずれかに格納)に新規プロシージャまたはファンクションを追加する場合は、通常、パッケージ(およびパッケージ仕様)の最後に追加する必要があります。パッケージ仕様およびパッケージの途中に新規プロシージャを追加するには、パッケージを参照するすべてのフォームを再生成する必要があり、そうしない場合にはそれらのフォームにはORA-4062エラーが発生します。

クライアント側PL/SQLパッケージでのフィールド名の使用

必ずブロック名を含む完全なフィールド名を指定してください(つまり、FIELD_NAMEではなくBLOCK.FIELD_NAMEとする)。フィールド名のみを指定すると、Oracle Formsはフィールドを見つけるためにフォーム内の各ブロックに対してフィールドの全リストをスキャンし、その名前が曖昧で潜在的にフォームのパフォーマンスを低下させないかどうかをチェックする必要があります。ブロック名を含めると、Oracle Formsはそのブロック内のフィールドのみを検索し、一致するものを見つけると停止します。その上、ブロックをさらに追加したりすると、フィールド名を明確に指定したため既存のコードが機能し続けます。

プロシージャ・パラメータでのフィールド名

フィールド名をプロシージャに渡し、IN OUTまたはOUTパラメータを使用するかわりにCOPYを使用してフィールド値を更新します。この方法により、プロシージャで実際に修正したかどうかに関係なくフィールドが変更済とマークされることがなくなります。OUTと宣言されたパラメータはすべて、プロシージャが正常に終了したときに書き出されます。

たとえば、test(my_var VARCHAR2 IN OUT)としてプロシージャを宣言しそれをtest(:block.field)としてコールするのでなく、プロシージャをtest(my_var VARCHAR2 IN)と宣言しそれをtest('block.field')としてコールします。

読みやすさを向上するためにパラメータ・リストが長い場合にはパラメータ名および値を「=>」で明示的に関連付けて、パラメータによって「切断」されることのないようにします。

DEFAULTの使用

パラメータのデフォルト値の宣言時には「:=」ではなくDEFAULTを使用します。値をデフォルト設定中のため、DEFAULTがより正確となります。コールするプロシージャは値を上書きできます。

逆に、不変の定数に対して値を宣言する際にはDEFAULTではなく「:=」を使用します。値をデフォルト設定ではなく割当中のため、「:=」を使用するとより正確になります。値は上書きできません。

オブジェクトIDの使用

SET_<OBJECT>_PROPERTYビルトイン(またはOracle Application Object Libraryと同等のもの)を使用してオブジェクトの複数のプロパティを変更するコードには、オブジェクトIDを使用する必要があります。まず適切なFIND_<OBJECT>ビルトインを使用してIDを取得してから、そのIDをSET_<OBJECT>_PROPERTYビルトインに渡します。

また、フォームの実行中に一度のみ取り出せばよいように、パッケージ・グローバルにIDを格納することも考慮する必要があります。

NULLと同値の処理

PL/SQLでNULL値を処理する際は注意を要します。たとえば、a := NULLかつb := NULLである場合、式(a = b)はFALSEと同値となります。片方の項がNULLである「=」式では、その式全体の値はFALSEとなります。

この理由から、値がNULLに等しいかどうかをチェックするには、かわりに演算子「is」を使用する必要があります。片方がNULLに等しい可能性のある2つの値を比較する場合は、次のように式を作成する必要があります:((a = b) or ((a is null) and (b is null))

グローバル変数

Oracle Forms DeveloperとPL/SQLは、様々なタイプのグローバル変数をサポートしています。

これらの変数タイプの詳細説明は『Oracle Forms Reference Manual』を参照してください。次の表は各タイプの変数の特性をリストしており、作成するコードに最も適切なタイプの選択が可能です。

動作 Oracle Formsグローバル PL/SQLパッケージ・グローバル Oracle Formsパラメータ
設計時に作成可能   Y Y
実行時に作成可能 Y    
すべてのフォームからアクセス可能 Y    
添付ライブラリからアクセス可能 Y (1) Y
特定のデータ型をサポート (2) Y Y
宣言デフォルトあり     Y
間接的に参照可能 Y   Y
コマンド行にて指定可能     Y
メモリーを回復するには削除する必要あり Y    
すべてのOracle Formsコードで使用可能 Y   Y

(1)フォームで定義されたパッケージ変数は添付ライブラリからは参照できません。添付ライブラリで定義された変数はフォームから参照できます。(Oracle Formsグローバルは添付ライブラリから参照できます)

(2) 常にCHAR(255)になります。

データベース・サーバー側対クライアント側

パフォーマンスは、どのようなアプリケーションにおいても重要な側面です。典型的なクライアント/サーバー環境ではネットワークのラウンド・トリップは非常にコストがかかるものであるため、ラウンド・トリップの回数を最小化することが良好なパフォーマンスを確保する上でのかぎとなります。

PL/SQLプロシージャをサーバー側に置くかクライアント側に置くかは、ネットワークのラウンド・トリップの数が結果的に最も少なくなるように決定する必要があります。次のとおり、いくつかのガイドラインがあります。

プロシージャがサーバーからコールされる場合、プロシージャはサーバーに置く必要があります。プロシージャがクライアントとサーバーの両方からコールされる場合、プロシージャが非常に複雑で両方で保守するコストが大きくなりすぎないかぎり、両方の場所で定義する必要があります。両方で定義しない場合は、プロシージャはサーバーに置きます。

PL/SQLコードの書式

この項では、PL/SQLコードの書式についての推奨事項を説明します。

例外処理

例外処理では次に示すヒントを参照してください。

Oracle Forms PL/SQLでのエラー

Oracle Forms PL/SQLで失敗が発生し、後続処理を停止する場合は、FND_MESSAGEを使用してエラー・メッセージを表示してから、RAISE FORM_TRIGGER_FAILUREで処理を停止します。

IF (error_condition) THEN

   fnd_message.set_name(appl_short_name,

        message_name);

   fnd_message.error;

   RAISE FORM_TRIGGER_FAILURE;

END IF;

RAISE FORM_TRIGGER_FAILUREはメッセージ等を表示することなく処理を停止することに注意してください。エラー通知がないため、メッセージを表示する場合は例外を実行する前に自分でFND_MESSAGEを使用して表示する必要があります。

関連項目: PL/SQLプロシージャのメッセージ・ディクショナリAPI

ストアド・プロシージャでのエラー

ストアド・プロシージャで失敗が発生し、後続処理を停止する場合は、パッケージ・プロシージャFND_MESSAGE.SET_NAMEを使用してメッセージを設定し、APP_EXCEPTION.RAISE_EXCEPTIONで処理を停止します。

IF (error_condition) THEN

  fnd_message.set_name(appl_short_name,

        message_name);

  APP_EXCEPTION.RAISE_EXCEPTION;

END IF;

フォームでのプロシージャのコールは、このストアド・プロシージャ・エラーを処理するためには何もする必要はありません。フォームのON-ERRORトリガーのコードは、自動的にストアド・プロシージャを検出し、メッセージを取り出して表示します。

重要: パフォーマンス上の理由から、サーバー側パッケージは、no_rowsなど、予期しないすべてのリターンに対してreturn_codeを返すことが必要です。例外処理で処理する必要があるのは、予期しない例外のみです。

関連項目: PL/SQLプロシージャのメッセージ・ディクショナリAPITEMPLATEフォームでの特殊トリガーおよびAPP_EXCEPTION: 例外処理API

FORM_SUCCESS、FORM_FAILUREおよびFORM_FATALのテスト

FORM_SUCCESS、FORM_FAILUREまたはFORM_FATALをテストする際は、その値が、ビルトインの結果として起動される別のトリガーのビルトインによって変更される場合があることに注意してください。たとえば、次のコードを考えてみます。

GO_ITEM('emp.empno');

IF FORM_FAILURE THEN

  RAISE FORM_TRIGGER_FAILURE;

END IF;

GO_ITEMによって、WHEN-NEW-ITEM-INSTANCEなど、その他のトリガーが起動されます。GO_ITEMが失敗しても、起動する最後のトリガーが成功する、つまりFORM_FAILUREがFALSEとなる可能性があります。この問題を回避する例を次に示します。

GO_ITEM('EMP.EMPNO');

IF :SYSTEM.CURSOR_ITEM != 'EMP.EMPNO' THEN

  -- No need to show an error, because Oracle Forms

  -- must have already reported an error due to

  -- some other condition that caused the GO_ITEM

  -- to fail.

  RAISE FORM_TRIGGER_FAILURE;

END IF;

各ビルトインの失敗を検出するその他の技法は、『Oracle Forms Reference Manual』を参照してください。

RAISE_APPLICATION_ERRORの回避

RAISE_APPLICATION_ERRORは使用しないでください。これは、サーバー側の例外を処理するスキームと衝突します。

関連項目: PL/SQLプロシージャのメッセージ・ディクショナリAPI

SQLコーディングのガイドライン

コードを作成するすべてのSQLで次のガイドラインに従ってください。

フォームでのトリガー

フォームでのトリガーでは次のとおりの一般的ルールに従います。

実行スタイル

ブロックまたはフィールド・レベルのすべてのトリガーの実行スタイルは、「上書き」または「前」のいずれかである必要があります。トリガーのフォーム・レベルのバージョンも通常は開始する必要があるため、一般にはスタイルに「前」を使用します。例外は、フォーム・レベルのPOST-QUERYトリガーでフレックスフィールド・コールがあるものの、ブロック・レベルPOST-QUERYでブロックの問合せステータスをリセットしている場合です。その場合には、ブロック・レベルPOST-QUERYは、実行スタイルに「後」を使用する必要があります。

関連項目: TEMPLATEフォームでの特殊トリガー

KEY-トリガーのプロパティ

すべてのKEYに対して「キーの表示」プロパティはTRUEに設定 - 無効にするコード(「キーの表示」をFALSEに設定)を除いたコードがトリガーされます。「キー説明の表示」プロパティは常にNULLに設定します。

動的問合せのみのモードでのWHEN-CREATE-RECORD

WHEN-CREATE-RECORDトリガーは、ブロックが挿入を許していない場合でも開始します。このトリガーにロジックがあり、ブロックが挿入可能なFALSEを動的に持つ場合には、ブロックが挿入を許容するかどうかをチェックする必要がある場合があります。

IF GET_ITEM_PROPERTY('<BLOCK>', INSERT_ALLOWED) = FALSE THEN

null;

ELSE

<your logic here>;

END IF;

リソース

パーソナル・コンピュータ上では、同時に利用可能なリアルのウィジェットの数に制限があります(テキスト項目と表示項目は、Oracle Formsが作成するものであるので、リアルのWindowsウィジェットではありません)。

フォームにあるすべてのチェック・ボックス、リスト・項目およびオブジェクト・グループは、これらのリソースを消費します。リアルのウィジェットが非表示のキャンバス上にある場合、それが消費するリソースは解放されます。画面に表示されていないキャンバスを明示的に非表示にすることによって、リソースを解放できます。また、Oracle Forms DesignerにおいてFALSEの表示プロパティで設定されたキャンバスは、キャンバスを訪れるかキャンバスがプログラムから表示されないかぎり、それ自身またはそのウィジェットのためのリソースを消費しません。

Oracle Formsはスタートアップ時に最初の入力可能項目にナビゲートし、First Navigation Blockに対してキャンバスとそのすべてのウィジェットを作成することに注意してください。

リソース可用性のチェック

なんらかのアクションを実行する前にMS Windowsリソースの可用性をチェックするには、次のユーティリティを使用します。

if get_application_property(USER_INTERFACE) =

                            'MSWINDOWS' then

   if (FND_UTILITIES.RESOURCES_LOW) then

     FND_MESSAGE.SET_NAME('FND', 'RESOURCES_LOW');

      if (FND_MESSAGE.QUESTION('Do Not Open', 'Open',

                               '', 1) =1) then

        raise FORM_TRIGGER_FAILURE;

     end if;

   end if;

end if;

Oracle Formsビルトインの置換

これらの標準は、一定のビルトインを完全に避けたり、その場所に「ラッパー」ルーチンをコールすることを必要とします。多くのビルトインに、複数の起動方法があります。ビルトインを直接コールして、標準フォーム動作を提供できます。ビルトインによっては、APP_STANDARD.EVENTをコールして起動する標準のOracle Applications動作があります。

これらのビルトインの多くに、キーおよびそれに関連付けられたKEY-トリガーがあります。利用対象のKEY-トリガーに追加ロジックがある場合には、DO_KEYビルトインを使用してトリガーを起動できます。これは、関連付けられたキーをユーザーが押した場合と同じ結果となります。

DO_KEYビルトインは定期的に使用する必要があります。KEY-トリガーをバイパスする理由があるとすれば、開始する追加のコードを開始する必要がある場合です。

CALL_FORMは使用しない

次に示すOracle Formsビルトインは使用しないでください。

変数 説明
CALL_FORM このビルトインは、Oracle Applicationsルーチンで使用されるOPEN_FORMとは互換性がありません。
フォームをプログラムからオープンする必要があるときは、CALL_FORMまたはOPEN_FORMのいずれかではなくFND_FUNCTION.EXECUTEを必ず使用します。FND_FUNCTION.EXECUTEを使用すると、Oracle Applicationsセキュリティをバイパスすることなくフォームをオープンすることが可能になり、フォームの正しいディレクトリ・パスの検索も可能になります。
関連項目: PL/SQLプロシージャの機能セキュリティAPI

APPCORE置換を伴うOracle Formsビルトイン

これらのOracle Formsビルトインには、追加機能を提供する同等のAPPCOREルーチンがあります。

変数 説明
EXIT_FORM EXIT_FORM Oracle Applicationsフォームには、特別の終了処理があります。EXIT_FORMを直接コールしないでください。必ずdo_key('EXIT_FORM')をコールしてください。
Oracle Applicationsを完全に終了するには、まず次をコールします。
 copy('Y','GLOBAL.APPCORE_EXIT_FLAG'); 

続いて次をコールします。
 do_key('exit_form');  
SET_ITEM_ PROPERTY APP_ITEM_PROPERTY.SET_ PROPERTYおよびAPP_ITEM_PROPERTY.SET_VISUAL_ATTRIBUTEで置換します。これらのAPPCOREルーチンは、Oracle Applicationsの標準的な方法でプロパティを設定し、伝播の動作を変更します。プロパティによっては、Oracle Formsシステム固有のSET_ITEM_PROPERTYを使用するものもあります。APP_ITEM_PROPERTY.SET_PROPERTYがカバーしているプロパティの網羅的なリストは、そのルーチンのドキュメントを参照ください。
関連項目: APP_ITEM_PROPERTY: 個別プロパティ・ユーティリティ
GET_ITEM_ PROPERTY Oracle Applicationsに固有のプロパティを取得する際は、APP_ITEM_PROPERTY.GET_PROPERTYを使用します。その他のプロパティの設定または取得にはOracle Formsビルトインを使用します。
OPEN_FORM FND_FUNCTION.EXECUTEを使用します。このルーチンは、機能セキュリティのために必要です。
OPEN_FORMとFND_ FUNCTION.EXECUTEの両方とも、POST-RECORDおよびPOST-BLOCKトリガーを開始させます。
CLEAR_FORM do_key('clear_form')を使用します。無効なレコードがある場合には、このルーチンが例外FORM_TRIGGER_FAILUREを発生させます。
「do_key」抜きでこのビルトインを使用することによって、トリガーの実行から生じる追加機能を避けることが可能になります。
COMMIT do_key('commit_form')を使用します。無効なレコードがある場合には、このルーチンが例外FORM_TRIGGER_FAILUREを発生させます。
「do_key」抜きでこのビルトインを使用することによって、トリガーの実行から生じる追加機能を避けることが可能になります。
EDIT_FIELD/ EDIT_ TEXTITEM do_key('edit_field')を使用します。現在の項目が日付である場合に、このルーチンがカレンダを起動します。
「do_key」抜きでこのビルトインを使用することによって、トリガーの実行から生じる追加機能を避けることが可能になります。
VALIDATE かわりにAPP_STANDARD.APP_VALIDATEを使用します。このルーチンはナビゲーションの失敗をもたらすすべての項目にナビゲートします。
「do_key」抜きでこのビルトインを使用することによって、トリガーの実行から生じる追加機能を避けることが可能になります。

警告: APP_STANDARD.APP_VALIDATEを使用するためには、ボタンのコーディング標準に準拠する必要があります。


関連項目: APP_STANDARDパッケージおよびボタン

項目、イベントおよび表ハンドラのコーディング

開発者は、項目を検証するために必要なすべてのコードを実行するため、または具体的な状況における正しい動作を確保するために、トリガーからハンドラをコールします。

ハンドラは、読込みおよび作業が容易になるように、コードを集中するのに使用されます。典型的なフォームは、ブロックごとのパッケージおよびフォームそのもののパッケージを持っています。これらのパッケージ内にプロシージャのコードを置き、関連付けられたトリガーからプロシージャ(ハンドラ)を呼びます。ハンドルが複数のブロック、またはフォーム・レベル・トリガーへの応答を伴う場合には、ハンドラをフォーム・パッケージに置きます。

項目ハンドラ、イベント・ハンドラ、表ハンドラなど様々な種類のコードに対して、様々な種類のプロシージャが存在します。ほとんどのコードはこれらのプロシージャに存在しており、プロシージャへのコール以外では、トリガーにあるコードは最小限にする必要があります。

項目ハンドラのコーディング

項目ハンドラは、特定の項目の検証に使用されるすべてのロジックを含んでいるプロシージャです。項目ハンドラ・パッケージには、ブロックまたはフォームにおいて項目を検証するためのすべてのプロシージャが含まれています。

このパッケージには通常はブロックやフォームに対応する名前が付けられており、プロシージャには個別の項目名に対応する名前が付けられています。たとえばEMPブロックは、EMPNO、ENAME、JOBという項目を含みますが、対応するEMPパッケージのプロシージャにもEMPNO、ENAME、JOBの名前がそれぞれ付けられており、特定の項目に関連付けられたコードを容易に検出できます。

項目ハンドラは常にEVENTと命名された1つのパラメータを取り、タイプVARCHAR2は通常は項目ハンドラをコールするトリガーの名前となります。

項目ハンドラの共通EVENT引数

共通のイベント・ポイントと関連ロジックは次に示すとおりです。

変数 説明
PRE-RECORD 新しいレコードの項目属性をリセットします。通常は依存フィールドの有効と無効を切り替えるAPPCOREルーチンで使用されます。制限されたOracle Formsビルトイン・ルーチンを使用する必要があるか、またはナビゲーションやコミットを実行する必要がある場合は、WHEN-NEW-RECORD-INSTANCEが使用できます。
INIT 項目を初期化します。
VALIDATE 項目を検証し、動的な項目属性を設定します。

INITイベント

INITは「初期化(Initialize)」の短縮名であり、項目ハンドラが項目を初期化するディレクティブです。INITは、項目ハンドラにフォームの現在の状態を検査するように指示し、必要に応じてその項目のデフォルト値および動的属性をリセットします。このイベントはその他のハンドラによって渡され、多くのAPPCOREルーチンが待ち受けています。

最も一般的なケースは、別の項目に依存している項目の場合です。マスターの項目ハンドラのWHEN-VALIDATE-ITEMで、マスター項目が変更されるたびに、従属する項目ハンドラがINITイベントでコールされます。

マスター項目の条件が変更される場合、通常はイベントをその他の従属項目にカスケードする必要があります。

VALIDATEイベント

この疑似イベントは、項目を検証する必要のある、多くのAPPCOREルーチンで使用されます。WHEN-VALIDATE-ITEM、WHEN-CHECKBOX-CHANGED、WHEN-LIST-CHANGEDまたはWHEN-RADIO- CHANGED(このいずれかを使用することも可能)のかわりに、このイベントを使用します。VALIDATEイベントまたはトリガー名のいずれかを予期する、独自の項目ハンドラ・ルーチンを作成できます。

項目ハンドラの書式

典型的な項目ハンドラは、次のような内容です。

procedure ITEM_NAME(event VARCHAR2) IS

  IF (event = 'WHEN-VALIDATE-ITEM') THEN

      -- validate the item

  ELSIF (event = 'INIT') THEN

      -- initialize this dependent item

  ELSIF (event in ('PRE-RECORD', 'POST-QUERY')) THEN

      -- etc.

  ELSE fnd_message.debug('Invalid event passed to item_name: ' || EVENT);

  END IF;

END ITEM_NAME;

ヒント: 項目ハンドラを作成することがプロセスの全体ではないことに注意してください。また、プロシージャが処理するイベントごとにトリガーをコーディングし、項目ハンドラをコールする必要があります。コーディングした内容で動作しない場合、まずチェックするのは、新規の項目ハンドラをコールするトリガーをコーディングしたかどうかということです。

イベント・ハンドラのコーディング

イベント・ハンドラは、個別の項目の動作にでなくイベントに関してコードを集中化する方が容易である場合、複数項目に付随するロジックをカプセル化します。開発者は、どの場合に項目ハンドラのセットより、イベント・ハンドラが読み込みやすくなるかを決定します。

非常に複雑な項目間の動作がイベント・ハンドラに属し、一方、非常にシンプルな単位の項目の動作は項目ハンドラに属します。イベント・ハンドラから項目ハンドラをコールできます。

たとえば、POST-QUERYで多くの項目に値を移入するように、イベント・ハンドラのコードを記述することが可能です。項目ごとに項目ハンドラを作成するより、すべてのロジックを単一のイベント・ハンドラにカプセル化できます。

イベント・ハンドラはイベントを1つのみ処理するものであるため、EVENTパラメータは必要ありません。実際に、イベント・ハンドラはパラメータを一切受け付けません。

イベント・ハンドラは、トリガーから命名され、ダッシュをアンダースコアに置換します(たとえば、PRE-QUERYイベント・ハンドラはPRE_QUERYとなります)。

共通イベント・ハンドラ

変数 説明
PRE_QUERY 適切なレコードを検索するのに必要な値を項目に移入します。
POST_QUERY 基礎表でない項目に値を移入します。
WHEN_CREATE _RECORD デフォルト値を移入します(デフォルト値プロパティの使用が不十分な場合)
WHEN_ VALIDATE_ RECORD 複雑な項目間の関連を検証します

表ハンドラのコーディング

表ハンドラは、APIを表に与えるサーバー側またはクライアント側のパッケージです。表ハンドラは、レコードを挿入、更新、削除またはロックするため、あるいは別の表にあるレコードがこの表にあるレコードを参照しているかどうかをチェックするために使用されます。

Oracle Applicationsでのフォームのほとんどはビューに基づいているため、これらの表ハンドラはビューの下で表との相互作用を処理するのに必要となります。

警告: ブロックが複数表のビューに基づいている場合、ブロック「キー・モード」をデフォルト値の「一意キー」から「更新不可キー」に変更します。項目のプロパティ・シートで「主キー」をTRUEに設定して、主キー項目を指定します。

表ハンドラには、次のプロシージャのいくつかまたはすべてが含まれます。

変数 説明
CHECK_ UNIQUE 一意の列で値が重複していないかチェックします。
CHECK_ REFERENCES 参照整合性をチェックします。
INSERT_ROW 表に行を挿入します。
UPDATE_ROW 表にある行を更新します。
DELETE_ROW 表から行を削除します。
LOCK_ROW 表にある行をロックします。

INSERT_ROW、UPDATE_ROW、DELETE_ROWおよびLOCK_ROWは、通常、ON-INSERT、ON-UPDATE、ON-DELETEおよびON-LOCKトリガーにおいてデフォルトのOracle Formsトランザクション処理を置換するのに使用されます。

INSERT_ROW表ハンドラ・プロシージャにおいて、主キー列でNULLが許可されている場合には、SELECT ROWID文のWHERE句に「OR (primary_key IS NULL AND X_col IS NULL)」を必ず追加してください。

LOCK_ROW表ハンドラ・プロシージャで、列にNULLが許されていない場合は、IF文から「OR (RECINFO.col IS NULL AND X_col IS NULL」条件を削除してください。

また、Oracle Formsは問合せを受けたフィールドから後続ブランクを取り去るため、通常の行ロックは比較の前にデータベース値から後続ブランクは取り去られています。例でのLOCK_ROWストアド・プロシージャは後続ブランクを取り去らないため、この(まれな)場合の比較は常に失敗します。必要に応じRTRIMを使用して後続ブランクを取り去ることが可能です。

第2の表でのアクション

別の表でアクションを実行するには、アクションを直接に実行するのでなく、その表の適切なハンドラ・プロシージャをコールします。

たとえば、カスケードDELETEを実行するには、マスター表のDELETE_ROW表ハンドラにおいて直接DELETEを実行するかわりに、詳細表の(マスター主キーをパラメータとして受け取る)DELETE_ROWS表ハンドラをコールします。

クライアント側の表ハンドラの例

次に示すのは、EMP表に対してINSERT_ROW、UPDATE_ROW、DELETE_ROWおよびLOCK_ROWプロシージャを提供するクライアント側の表ハンドラの例です。フォームに直接にクライアント側の表ハンドラをコーディングします。

EMPブロックに対してコーディングするパッケージ仕様

PACKAGE EMP IS

  PROCEDURE Insert_Row;

  PROCEDURE Lock_Row;

  PROCEDURE Update_Row;

  PROCEDURE Delete_Row;

END EMP;

EMPブロックに対してコーディングするパッケージ本体

PACKAGE BODY EMP IS

 PROCEDURE Insert_Row IS

    CURSOR C IS SELECT rowid FROM EMP

                 WHERE empno = :EMP.Empno;

  BEGIN

    INSERT INTO EMP(

              empno,

              ename,

              job,

              mgr,

              hiredate,

              sal,

              comm,

              deptno

             ) VALUES (

              :EMP.Empno,

              :EMP.Ename,

              :EMP.Job,

              :EMP.Mgr,

              :EMP.Hiredate,

              :EMP.Sal,

              :EMP.Comm,

              :EMP.Deptno

             );

    OPEN C;

    FETCH C INTO :EMP.Row_Id;

    if (C%NOTFOUND) then

      CLOSE C;

      Raise NO_DATA_FOUND;

    end if;

    CLOSE C;

  END Insert_Row;

 PROCEDURE Lock_Row IS

    Counter NUMBER;

    CURSOR C IS

        SELECT empno,

              ename,

              job,

              mgr,

              hiredate,

              sal,

              comm,

              deptno

        FROM   EMP

        WHERE  rowid = :EMP.Row_Id

        FOR UPDATE of Empno NOWAIT;

    Recinfo C%ROWTYPE;

  BEGIN

    Counter := 0;

    LOOP

      BEGIN

        Counter := Counter + 1;

        OPEN C;

        FETCH C INTO Recinfo;

        if (C%NOTFOUND) then

          CLOSE C;

          FND_MESSAGE.Set_Name('FND',

                'FORM_RECORD_DELETED');

          FND_MESSAGE.Error;

          Raise FORM_TRIGGER_FAILURE;

        end if;

        CLOSE C;

        if (

               (Recinfo.empno =  :EMP.Empno)

           AND (   (Recinfo.ename =  :EMP.Ename)

                OR (    (Recinfo.ename IS NULL)

                    AND (:EMP.Ename IS NULL)))

           AND (   (Recinfo.job =  :EMP.Job)

                OR (    (Recinfo.job IS NULL)

                    AND (:EMP.Job IS NULL)))

           AND (   (Recinfo.mgr =  :EMP.Mgr)

                OR (    (Recinfo.mgr IS NULL)

                    AND (:EMP.Mgr IS NULL)))

           AND (   (Recinfo.hiredate =  :EMP.Hiredate)

                OR (    (Recinfo.hiredate IS NULL)

                    AND (:EMP.Hiredate IS NULL)))

           AND (   (Recinfo.sal =  :EMP.Sal)

                OR (    (Recinfo.sal IS NULL)

                    AND (:EMP.Sal IS NULL)))

           AND (   (Recinfo.comm =  :EMP.Comm)

                OR (    (Recinfo.comm IS NULL)

                    AND (:EMP.Comm IS NULL)))

           AND (Recinfo.deptno =  :EMP.Deptno)

          ) then

          return;

        else

          FND_MESSAGE.Set_Name('FND',

                     'FORM_RECORD_CHANGED');

          FND_MESSAGE.Error;

          Raise FORM_TRIGGER_FAILURE;

        end if;

      EXCEPTION

        When APP_EXCEPTIONS.RECORD_LOCK_EXCEPTION then

          IF (C% ISOPEN) THEN

             close C;

           END IF;

          APP_EXCEPTION.Record_Lock_Error(Counter);

      END;

    end LOOP;

  END Lock_Row;

 PROCEDURE Update_Row IS

  BEGIN

    UPDATE EMP

    SET

       empno                           =     :EMP.Empno,

       ename                           =     :EMP.Ename,

       job                             =     :EMP.Job,

       mgr                             =     :EMP.Mgr,

       hiredate                        =     :EMP.Hiredate,

       sal                             =     :EMP.Sal,

       comm                            =     :EMP.Comm,

       deptno                          =     :EMP.Deptno

    WHERE rowid = :EMP.Row_Id;

    if (SQL%NOTFOUND) then

      Raise NO_DATA_FOUND;

    end if;

  END Update_Row;

 PROCEDURE Delete_Row IS

  BEGIN

    DELETE FROM EMP

    WHERE rowid = :EMP.Row_Id;

    if (SQL%NOTFOUND) then

      Raise NO_DATA_FOUND;

    end if;

  END Delete_Row;



END EMP;

サーバー側の表ハンドラの例

次に示すのは、EMP表に対してINSERT_ROW、UPDATE_ROW、DELETE_ROWおよびLOCK_ROWプロシージャを提供するサーバー側の表ハンドラの例です。ハンドラは、フォームのパッケージおよびデータベースのサーバー側パッケージから構成されます。フォームのパッケージは、サーバー側パッケージをコールし、引数としてすべてのフィールド値を渡します。

EMPブロックに対しフォームにおいてコーディングするパッケージ仕様

PACKAGE EMP IS

  PROCEDURE Insert_Row;

  PROCEDURE Update_Row;

  PROCEDURE Lock_Row;

  PROCEDURE Delete_Row;

END EMP;

EMPブロックに対しフォームにおいてコーディングするパッケージ本体

PACKAGE BODY EMP IS

PROCEDURE Insert_Row IS

BEGIN

  EMP_PKG.Insert_Row(

      X_Rowid                => :EMP.Row_Id,

      X_Empno                => :EMP.Empno,

      X_Ename                => :EMP.Ename,

      X_Job                  => :EMP.Job,

      X_Mgr                  => :EMP.Mgr,

      X_Hiredate             => :EMP.Hiredate,

      X_Sal                  => :EMP.Sal,

      X_Comm                 => :EMP.Comm,

      X_Deptno               => :EMP.Deptno);

END Insert_Row;



PROCEDURE Update_Row IS

BEGIN

  EMP_PKG.Update_Row(

      X_Rowid                => :EMP.Row_Id,

 X_Empno                => :EMP.Empno,

      X_Ename                => :EMP.Ename,

      X_Job                  => :EMP.Job,

      X_Mgr                  => :EMP.Mgr,

      X_Hiredate             => :EMP.Hiredate,

      X_Sal                  => :EMP.Sal,

      X_Comm                 => :EMP.Comm,

      X_Deptno               => :EMP.Deptno);

END Update_Row;



PROCEDURE Delete_Row IS

BEGIN

  EMP_PKG.Delete_Row(:EMP.Row_Id);

END Delete_Row;



PROCEDURE Lock_Row IS

  Counter    Number;

BEGIN

  Counter := 0;

  LOOP

    BEGIN

      Counter := Counter + 1;

      EMP_PKG.Lock_Row(

          X_Rowid                => :EMP.Row_Id,

          X_Empno                => :EMP.Empno,

          X_Ename                => :EMP.Ename,

          X_Job                  => :EMP.Job,

          X_Mgr                  => :EMP.Mgr,

          X_Hiredate             => :EMP.Hiredate,

          X_Sal                  => :EMP.Sal,

          X_Comm                 => :EMP.Comm,

          X_Deptno               => :EMP.Deptno);

      return;

    EXCEPTION

      When APP_EXCEPTIONS.RECORD_LOCK_EXCEPTION then

        APP_EXCEPTION.Record_Lock_Error(Counter);

    END;

  end LOOP;

END Lock_Row;

END EMP;

サーバー側の表ハンドラに対するパッケージ仕様(SQLスクリプト)

SET VERIFY OFF

DEFINE PACKAGE_NAME="EMP_PKG"

WHENEVER SQLERROR EXIT FAILURE ROLLBACK;

CREATE or REPLACE PACKAGE &PACKAGE_NAME as



/* Put any header information (such as $Header$) here.

It must be written within the package definition so that the

 header information will be available in the package itself.

 This makes it easier to identify package versions during

 upgrades. */



 PROCEDURE Insert_Row(X_Rowid     IN OUT VARCHAR2,

                       X_Empno     NUMBER,

                       X_Ename     VARCHAR2,

                       X_Job       VARCHAR2,

                       X_Mgr       NUMBER,

                       X_Hiredate  DATE,

                       X_Sal       NUMBER,

                       X_Comm      NUMBER,

                       X_Deptno    NUMBER

                      );

 PROCEDURE Lock_Row(X_Rowid       VARCHAR2,

 X_Empno       NUMBER,

                     X_Ename       VARCHAR2,

                     X_Job         VARCHAR2,

                     X_Mgr         NUMBER,

                     X_Hiredate    DATE,

                     X_Sal         NUMBER,

                     X_Comm        NUMBER,

                     X_Deptno      NUMBER

                    );



 PROCEDURE Update_Row(X_Rowid     VARCHAR2,

 X_Empno     NUMBER,

                       X_Ename     VARCHAR2,

                       X_Job       VARCHAR2,

                       X_Mgr       NUMBER,

                       X_Hiredate  DATE,

                       X_Sal       NUMBER,

                       X_Comm      NUMBER,

                       X_Deptno    NUMBER

                      );



  PROCEDURE Delete_Row(X_Rowid VARCHAR2);

END &PACKAGE_NAME;

/

show errors package &PACKAGE_NAME

SELECT to_date('SQLERROR') FROM user_errors

WHERE  name = '&PACKAGE_NAME'

AND    type = 'PACKAGE'

/

commit;

exit;

サーバー側の表ハンドラに対するパッケージ本体(SQLスクリプト)

SET VERIFY OFF

DEFINE PACKAGE_NAME="EMP_PKG"

WHENEVER SQLERROR EXIT FAILURE ROLLBACK;

CREATE or REPLACE PACKAGE BODY &PACKAGE_NAME as



/* Put any header information (such as $Header$) here.

 It must be written within the package definition so the

 header information is available in the package itself.

 This makes it easier to identify package versions during

 upgrades. */

 PROCEDURE Insert_Row(X_Rowid     IN OUT VARCHAR2,

 X_Empno     NUMBER,

                       X_Ename     VARCHAR2,

                       X_Job       VARCHAR2,

                       X_Mgr       NUMBER,

                       X_Hiredate  DATE,

                       X_Sal       NUMBER,

                       X_Comm      NUMBER,

                       X_Deptno    NUMBER

  ) IS

    CURSOR C IS SELECT rowid FROM emp

                 WHERE empno = X_Empno;

 BEGIN

 INSERT INTO emp(

 empno,

              ename,

              job,

              mgr,

              hiredate,

              sal,

              comm,

              deptno

             ) VALUES (

              X_Empno,

              X_Ename,

              X_Job,

              X_Mgr,

              X_Hiredate,

              X_Sal,

              X_Comm,

              X_Deptno

             );

 OPEN C;

    FETCH C INTO X_Rowid;

    if (C%NOTFOUND) then

      CLOSE C;

      Raise NO_DATA_FOUND;

    end if;

    CLOSE C;

  END Insert_Row;

 PROCEDURE Lock_Row(X_Rowid        VARCHAR2,

                     X_Empno        NUMBER,

                     X_Ename        VARCHAR2,

                     X_Job          VARCHAR2,

                     X_Mgr          NUMBER,

                     X_Hiredate     DATE,

                     X_Sal          NUMBER,

                     X_Comm         NUMBER,

                     X_Deptno       NUMBER

  ) IS

    CURSOR C IS

        SELECT *

        FROM   emp

        WHERE  rowid = X_Rowid

        FOR UPDATE of Empno NOWAIT;

    Recinfo C%ROWTYPE;

  BEGIN

    OPEN C;

    FETCH C INTO Recinfo;

    if (C%NOTFOUND) then

      CLOSE C;

      FND_MESSAGE.Set_Name('FND', 'FORM_RECORD_DELETED');

      APP_EXCEPTION.Raise_Exception;

    end if;

    CLOSE C;

    if (

               (Recinfo.empno =  X_Empno)

           AND (   (Recinfo.ename =  X_Ename)

                OR (    (Recinfo.ename IS NULL)

                    AND (X_Ename IS NULL)))

           AND (   (Recinfo.job =  X_Job)

                OR (    (Recinfo.job IS NULL)

                    AND (X_Job IS NULL)))

           AND (   (Recinfo.mgr =  X_Mgr)

                OR (    (Recinfo.mgr IS NULL)

                    AND (X_Mgr IS NULL)))

           AND (   (Recinfo.hiredate =  X_Hiredate)

                OR (    (Recinfo.hiredate IS NULL)

                    AND (X_Hiredate IS NULL)))

           AND (   (Recinfo.sal =  X_Sal)

                OR (    (Recinfo.sal IS NULL)

                    AND (X_Sal IS NULL)))

           AND (   (Recinfo.comm =  X_Comm)

                OR (    (Recinfo.comm IS NULL)

                    AND (X_Comm IS NULL)))

           AND (Recinfo.deptno =  X_Deptno)

      ) then

      return;

    else

      FND_MESSAGE.Set_Name('FND', 'FORM_RECORD_CHANGED');

      APP_EXCEPTION.Raise_Exception;

    end if;

  END Lock_Row;

 PROCEDURE Update_Row(X_Rowid        VARCHAR2,

                       X_Empno        NUMBER,

                       X_Ename        VARCHAR2,

                       X_Job          VARCHAR2,

                       X_Mgr          NUMBER,

                       X_Hiredate     DATE,

                       X_Sal          NUMBER,

                       X_Comm         NUMBER,

                       X_Deptno       NUMBER

  ) IS

  BEGIN

    UPDATE emp

    SET

       empno                           =     X_Empno,

       ename                           =     X_Ename,

       job                             =     X_Job,

       mgr                             =     X_Mgr,

       hiredate                        =     X_Hiredate,

       sal                             =     X_Sal,

       comm                            =     X_Comm,

       deptno                          =     X_Deptno

    WHERE rowid = X_Rowid;

    if (SQL%NOTFOUND) then

      Raise NO_DATA_FOUND;

    end if;

  END Update_Row;

 PROCEDURE Delete_Row(X_Rowid VARCHAR2) IS

  BEGIN

    DELETE FROM emp

    WHERE rowid = X_Rowid;

    if (SQL%NOTFOUND) then

      Raise NO_DATA_FOUND;

    end if;

  END Delete_Row;

 END &PACKAGE_NAME;

/

show errors package body &PACKAGE_NAME

SELECT to_date('SQLERROR') FROM user_errors

WHERE  name = '&PACKAGE_NAME'

AND    type = 'PACKAGE BODY'

/

commit;

exit;