掲載元
Oracle Magazine
2005年5月/6月

テクノロジー: PL/SQL


パッケージの選択

Steven Feuerstein 著 Oracle ACE Director

PL/SQLを使用する場合と使用しない場合を把握する

Oracle PL/SQL言語は、簡潔で非常に読みやすいプログラミング・ツールであるため、楽しんで使えます。また、この言語は、基盤となるOracleデータベースで処理を実行するという、特定の領域にレーザー・ビームのように焦点を合わせています。

PL/SQLのもっとも重要で役立つ要素の1つが、パッケージです。PL/SQLの初期には、多くの開発者がパッケージにそれほど詳しくありませんでした。開発者はプロシージャやファンクション、トリガーの使用方法は把握していましたが、パッケージには別のレベルの複雑さと抽象化が伴いました。

現在では、パッケージはPL/SQLプログラミングのメインストリームとなっています。PL/SQLアプリケーションの基本構成要素として広く受け入れられています。それにもかかわらず、パッケージの適用や使用上の懸念についてPL/SQLプログラマーから絶えず問合せを受けています。

この記事では、PL/SQLパッケージの基本概念とおもな利点について説明し、それほどよく知られていない微妙なパッケージ動作をいくつか紹介します。最後に、パッケージの利用を避けた方が妥当な場合について説明します。

パッケージの概念

まず、PL/SQLのパッケージ構造の基盤となっている、もっとも重要な構成要素と概念についていくつか説明します。

仕様部と本体: パッケージは、パッケージ仕様部とパッケージ本体という2つの異なるコード・チャンクで構成されています。

パッケージ仕様部には、パッケージの外から参照できる、パッケージ内で共通使用可能なすべての要素の定義が含まれます。パッケージ仕様部には、パッケージ内の使用可能なものが示されますが、パッケージのプログラムの実装に関する情報は含まれません(注釈コメントを追加している場合を除く)。仕様部が適切に設計されていれば、開発者は仕様部から、パッケージの使用に必要なすべての情報を把握できます。

パッケージ本体には、パッケージ仕様部で定義された要素を実装するために必要なすべてのコードが含まれます。また、仕様部には示されない、したがってパッケージの外からは参照できない、プライベート要素を本体に含めることもできます。パッケージ本体は、スタンドアロン・プログラムの宣言セクションに似ています。パッケージ本体には、変数の宣言とすべてのパッケージ・プログラムの定義の両方が含まれます。また、パッケージ本体に実行セクションを含めることもできます。この実行セクションは、パッケージを初期化するために1回だけ実行されるため、初期化セクションと呼ばれます。

アプリケーションで長期的に生じる変更の量を考えると、実装(本体)をインタフェース(仕様部)から分離しておくことが重要です。アプリケーションを利用するユーザーは考えを変えることが多く、また、オラクルはPL/SQLとデータベースを常に改良しています。したがって、コード改良を行う時は、このような要因を頭に置いておく必要があります。

パブリックとプライベート: パブリック・コードはパッケージ仕様部に定義され、パッケージのEXECUTE権限を持つスキーマで利用できます(パッケージを所有するスキーマを含む)。 それに対して、プライベート・コードはパッケージ内に定義され、パッケージ内からのみ参照できます。パッケージを使用する外部プログラムは、プライベート・コードを参照することも使用することもできません。

パッケージを作成するときに、どのパッケージ要素をパブリックにし、どのパッケージ要素をプライベートにするかを判断します。また、他のスキーマや開発者からパッケージ本体の詳細を完全に非表示にすることもできます。このように、パッケージを使用することでプログラムの実装詳細は非表示になります。これは、プラットフォームの依存関係、頻繁に変更が生じるデータ構造、一時的な回避策といった、アプリケーションのもっとも変化しやすい部分を分離する必要がある場合に重要です。

初期化: 初期化は、プログラマーにとって新しい概念ではありません。ただし、パッケージのコンテキストでは、初期化には固有の意味があります。1つの変数の値を初期化するのではなく、アプリケーションでの必要に応じて単純にも複雑にもなるコードをパッケージ全体で初期化します。Oracle Databaseでは、パッケージの初期化はセッションあたり1回のみです。パッケージのデータ構造を初期化するコードを自身で記述すると、コードが扱いにくくなったり、必要以上に頻繁にコードが実行されたりする可能性があります。

セッション永続性: データベース・プログラマーとして、永続性の概念を把握しておく必要があります。結局のところ、データベースは永続性につきます。たとえば、月曜日にデータベースに行を挿入し、その週の残りはバハマへ行き、翌週の月曜日に業務に戻ったとしても、挿入した行は引き続きデータベース内に配置されています。別の種類の永続性として、PL/SQLパッケージがサポートしているセッション永続性があります。この場合、Oracleデータベースに接続(セッションを確立)して、パッケージ・レベルの変数(パッケージ内の、プログラムの外部で宣言された変数。)に値を割り当てるプログラムを実行すると、この変数はそのセッションの期間だけ永続化するように設定されて、割当てを実行したプログラムが終了した場合でも変数にこの値が保持されます。

パッケージのおもな利点

パッケージのもっとも重要な利点は、前のセクションで説明している概念から直接導き出されます。

関連する項目の収集: パッケージのもっとも優れた点の1つが、関連する項目を配置するコンテナを提供することです。さまざまな住宅ローン計算を実行する10~20ものスタンドアロン・プログラムを用意するのではなく、すべてをまとめてmortgage_calcパッケージに配置できます。

このように論理的にグループ化すると、チーム・メンバーは、実行したいコードを簡単に見つけることができます。また、別のプログラマーが他の住宅ローン計算を実装するときに、このプログラマーはこの計算をどこに配置すれば良いかを正確に把握できます。これらの計算ルーチンの多くが、共通の内部機能を使用すると考えられます。すべてのプログラマーが、パッケージ本体にプライベートに定義された、プログラム内のこの共通コードを特定できるため、既存のコードの再利用が促進されます。

かなり複雑なアプリケーションには、数百もの個別のプログラム・ユニットが含まれます。これらのユニットがスタンドアロン・プログラムとしてすべて実装されている場合は、使い慣れた統合開発環境(IDE)のオブジェクト・ブラウザのコンテンツ(USER_OBJECTSデータ・ディクショナリ・ビューのコンテンツ)に圧倒されて途方に暮れることになります。パッケージは、対象となるプログラム・ユニットの数を減らしてコード・ベースを管理しやすくするのに役立ちます。

実装変更の煩わしさの軽減: パッケージのパブリック・インタフェース(仕様部)をパッケージの実装(本体)から分離するもっとも重要な利点の1つが、パッケージを使用しているアプリケーション・コードに影響を及ぼすことなく、実装、つまりパッケージ本体を変更できることです。実際に、パッケージ仕様部が変更または再コンパイルされない限り、アプリケーション・コードの再コンパイルも不要です。そのパッケージに依存するコードは引き続き有効です。

このため、数十や数百ものプログラムに影響が及ぶのを心配することなく、コードの可読性とパフォーマンスを向上するためのPL/SQLの新しいオプションを非常に柔軟に試すことができます。

初期化が失敗した場合

前述のとおり、パッケージに必要なセットアップ・ロジックを実行するために、パッケージの初期化セクションはセッションあたり1回のみ実行されます。そのため、初期化が失敗した場合はどうなるのか、という疑問が当然生じます。例をあげて説明します。

リスト1の非常にシンプルなパッケージについて検討します。valerrパッケージの仕様部には、g_privateの値を取得する1つのファンクションが含まれています。変数g_privateはパッケージ・レベルで本体に定義されており、値abcが割り当てられています。

コード・リスト1: valerr パッケージ

CREATE OR REPLACE PACKAGE valerr
IS
   FUNCTION private_variable RETURN VARCHAR2;
END valerr;
/
CREATE OR REPLACE PACKAGE BODY valerr
IS
   g_private VARCHAR2(1) := 'abc';

   FUNCTION private_variable RETURN VARCHAR2 
   IS
   BEGIN
      RETURN g_private;
   END private_variable;
BEGIN
   DBMS_OUTPUT.PUT_LINE ('Before I show you v...');

EXCEPTION
  WHEN OTHERS 
  THEN
    DBMS_OUTPUT.PUT_LINE ('Trapped the error!');
	 
END valerr;
/


valerrパッケージには、メッセージを表示する初期化セクションも含まれています。さらに、パッケージには、別のメッセージを表示する例外セクションも含まれています。

パッケージの名前から想像されるように、g_privateにデフォルト値を割り当てようとすると、Oracle DatabaseによってVALUE_ERROR例外、ORA-06502が発生します。変数には1文字しか保持できませんが、3文字割り当てようとしたためです。しかし、例外が発生した後で、この例外はどうなるのでしょうか。

このプログラムをパッケージのコンパイル直後に実行すると、次のように表示されます。

SQL> exec DBMS_OUTPUT.PUT_LINE 
(valerr.private_variable)

BEGIN DBMS_OUTPUT.PUT_LINE 
(valerr.private_variable); 
END;

*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or 
value error: character string 
buffer too small
ORA-06512: at "SCOTT.VALERR", line 3
ORA-06512: at line 1


一見すると、かなり分かりづらいです。パッケージに、"すべてのエラー"を捕捉する例外セクション(WHEN OTHERSを使用)が含まれているため、メッセージ"Trapped the error!"が表示されるはずですが、表示されず、エラーは処理されていません。

この動作の理由は、パッケージ本体に含まれている例外セクションでは、パッケージの実行部または初期化セクションで生じたエラーしか処理できないためです。パッケージ変数に値を割り当てようとしてエラーが生じたため、初期化セクションは実行されていません。そのため、例外セクションは実行されず、エラーは処理されません。

さらに興味深いことには、このプログラムを再度実行してみると、エラーは生じません。代わりに、ファンクションが実行されて、g_privateの値(NULL)が返されます。

SQL> BEGIN
  2    DBMS_OUTPUT.PUT_LINE 
  3   ('Value='||valerr.private_variable);
  4   END;

SQL> /

Value=


パッケージの初期化失敗に伴う問題のもっとも重要な点は、パッケージを適切に初期化できない場合でも、Oracleではパッケージは"初期化済み"としてマークされることです。

エラーは繰り返されず、1回しか生じないため、これは気付くのが非常に難しい状況です。実際、アプリケーションを引き続き実行でき、明らかな問題が確認されない場合があります。エラーを1つの値に分離でき、パッケージ内の残りのコードはすべて適切に実行されるためです。

ただし、パッケージを強制的に再初期化し、エラーをもう1回生じさせることができます。Oracle Databaseに再接続するか、パッケージを再コンパイルすることです。

一般に、この種のシナリオ(エラーは1回生じるが、セッション内で(即座に)繰り返されるわけではない)に遭遇した場合は、最近変更したパッケージを思い返して、特にパッケージ仕様部またはパッケージ本体の宣言セクションを注目する必要があります。

可能であれば、パッケージ・データのデフォルト値の割当てを別の方法で実装することを検討すべきです。宣言セクションで値を割り当てるのではなく、パッケージの設定または初期化ロジックがすべて含まれた個別の初期化プロシージャを作成します。作成したプロシージャに例外ハンドラを配置します。パッケージの初期化セクションで初期化プロシージャをコールします。このアプローチをリスト2に示します。

コード・リスト2:パッケージ本体の初期化プロシージャの使用

CREATE OR REPLACE PACKAGE BODY valerr
IS
   g_private   VARCHAR2 (1);

   FUNCTION private_variable
      RETURN VARCHAR2
   IS
   BEGIN
      RETURN g_private;
   END private_variable;

   PROCEDURE initialize
   IS
   BEGIN
      DBMS_OUTPUT.put_line ('Before I show you v...');
      g_private := 'abc';
   EXCEPTION
      WHEN OTHERS
      THEN
         DBMS_OUTPUT.put_line ('Trapped the error!');
   END initialize;
BEGIN
   initialize;
END valerr;
/


初期化プログラムを配置し、コンパイルの直後にprivate_variableファンクションをコールすると(初期化を確実に実行するため)、大きく異なる動作が得られます。

SQL> @valerr.pks

SQL> exec DBMS_OUTPUT.PUT_LINE 
(valerr.private_variable)

Before I show you v...
Trapped the error!


今回は初期化セクションが実行され、エラーが捕捉されました。

結論:デフォルト値の割当てはすべて、パッケージ実行の初期化フェーズに移します。さらに、すべての初期化アクションを1つのプロシージャにカプセル化して、初期化セクションでコールします。

パッケージを使用する場合

パッケージの優れた機能と利点すべてをするために、パッケージをPL/SQL開発のデフォルトのプログラム・ユニットとして使用することをお勧めします。つまり、スタンドアロンのプロシージャとファンクションは使用しないようにします。変更する特別な理由がなければ(このようなシナリオについては、次のセクションで説明します)、まずパッケージで開始してパッケージを常に使用します。

関連する機能は、1つのパッケージ名の下にまとめます。セッション固有の永続的なデータをパッケージ本体に定義し、プログラム間で共有します。明確で一貫性のある、使いやすいインタフェースをパッケージ仕様部に定義し、同僚のプログラマーが基本部分を処理せずに済むようにします。

パッケージを使用しない場合

パッケージの利点と機能すべてを考慮すると、パッケージにはいくつかの側面のために、使用をちゅうちょする場合があります。また、まれですが、これらの側面のために、パッケージの使用について異論が生じる場合もあります。これらの側面について、詳しく説明します。

ポテト・チップ・メーカーで、よく使用されるユーティリティのパッケージchip_utilがあるとします。このパッケージには、特に、ポテト・チップ・ブランドのマーケティング・キャッチフレーズ("Everybody wants more!")を返すcompany_taglineファンクションが含まれています。このキャッチフレーズはこのメーカーの電子メールの最後、記事内、テレビなどで使用されます。システム内の数百ものプログラムによって呼び出され、変更されることはありません(マーケティングに多額のコストをかけているメーカーではありません)。リスト3に、chip_utilパッケージがどのように表示されるかを示します。

コード・リスト3: chip_utilパッケージ

CREATE OR REPLACE PACKAGE chip_util
IS
   FUNCTION company_tagline RETURN VARCHAR2;
   
   FUNCTION max_chip_diameter (brand_in IN VARCHAR2) RETURN NUMBER;
   
   FUNCTION to_metric (weight_in in NUMBER) RETURN NUMBER;
   
   ...
   
   FUNCTION nutrition_label_template RETURN VARCHAR2; 
END chip_util;
/

CREATE OR REPLACE PACKAGE BODY chip_util
IS
   FUNCTION company_tagline RETURN VARCHAR2
   IS
   BEGIN
      RETURN 'Everybody wants more!';
   END company_tagline;   
   
   ... all the rest of the programs ...
   
END chip_util;
/


chip_utilパッケージは新しいユーティリティで常に更新されるため、chip_utilパッケージの仕様部の再コンパイルが必要です。この再コンパイルによって、chip_util.company_taglineをコールするすべてのプログラムは、プログラム自体に変更はなくても、無効化と再コンパイルが強制的に実行されます。

このシナリオでは、次に示すように、パッケージからcompany_taglineファンクションを取り除いて、代わりに以下のように、このファンクションをスタンドアロンのファンクションとして定義するのが適切です。

CREATE OR REPLACE FUNCTION 
company_tagline RETURN VARCHAR2
IS
BEGIN
  RETURN 'Everybody wants more!'
END company_tagline;
/

 

次のステップ


Feuersteinによるその他の情報
oracle.com/technetwork/issue-archive/index-092362.html
oracle.com/technetwork/articles
oreillynet.com/cs/catalog/view/au/344

 ダウンロード Oracle Database 10g

このファンクションを使用するプログラムは、company_taglineファンクション自体が変更された場合のみ無効化されますが、そのようなことは発生しません。比較的安全なchip_util.company_taglineのグローバル検索と、company_taglineでの置換によって、スタンドアロンのファンクションを使用するようにメーカーのアプリケーションを変換できます。

より一般的には、パッケージ・コードの再コンパイルの必要性を最小限に抑えるよう、次のガイドラインを検討します。

  • まれにしか変更されない機能を、アプリケーション内の頻繁に変更される領域から分離します。これにより、スタンドアロン・プログラムの数がわずかになるか、または"静的"コードのパッケージが得られます。

  • コード・ベースにプログラム依存関係のボトルネックが生じないようにします。アプリケーションで広く参照されるパッケージを作成する場合は、これらのプログラムを再コンパイルせずに済むように、できる限りのことを行います。たとえば、パッケージ仕様部で定数を宣言するのではなく、パッケージ本体内から定数値を返すファンクションを定義します。このようにすることで、仕様部を再コンパイルせずに、いつでも値を変更できます。

再コンパイルの問題以外で、アプリケーションにオブジェクト指向構造の機能が必要な場合は、パッケージをバイパスすることを選択できます。このような状況では、Oracle8で初めて導入され、Oracle9iで継承サポートによって大幅に拡張された、オラクルのオブジェクト・タイプを使用するのが適切です。

パッケージの幅広い賢明な使用

間違いなく、パッケージはOracle PL/SQLでのアプリケーション開発の基盤として役立ちます。コードを論理的に体系化し、セッション実行中は永続化されるデータを利用し、基本機能への無駄のないインタフェースを提供するのに役立ちます。

ただし、パッケージ初期化に伴う複雑さには注意してください。また、パッケージがどれほど優れたものであっても、スタンドアロンのプロシージャとファンクションを使用するのが妥当な場合があることを覚えておいてください。


Steven Feuerstein's (steven@stevenfeuerstein.com)の略歴とコラム目録.


ご意見ご感想をお寄せください