タイトル: パッケージ
SEOタイトル: Oracle PL/SQL パッケージ 完全ガイド(仕様部 / 本体 / グローバル変数 / 利点と設計指針)
| この記事の要点 |
|
パッケージとは
パッケージ (PACKAGE) は Oracle 独自の PL/SQL オブジェクトで、関連するプロシージャ・ファンクション・変数・型・カーソル・例外を 1 つの単位にまとめたものです。Java のクラスや C# のクラス、モジュールに近い概念。
業務システムでは「顧客処理パッケージ」「受注処理パッケージ」のように業務単位でまとめるのが定石で、肥大化したスタンドアロンプロシージャを整理する強力な手段になります。
パッケージの構成 — 2 段構成
| 区分 | 役割 |
|---|---|
| パッケージ仕様部 (PACKAGE) | 外部に公開するインターフェース。関数シグネチャ、定数、型を宣言 |
| パッケージ本体 (PACKAGE BODY) | 仕様部で宣言したプロシージャ / ファンクションの実装と、プライベートメンバー |
基本構文 — 仕様部
CREATE OR REPLACE PACKAGE pkg_customer AS
-- 公開定数
c_default_status CONSTANT VARCHAR2(10) := 'ACTIVE';
-- 公開型
TYPE customer_rec IS RECORD (
customer_id NUMBER,
customer_name VARCHAR2(100),
email VARCHAR2(255)
);
-- 公開プロシージャ / ファンクション
PROCEDURE register_customer(
p_name IN VARCHAR2,
p_email IN VARCHAR2,
p_id OUT NUMBER
);
FUNCTION find_customer(p_id IN NUMBER) RETURN customer_rec;
FUNCTION count_active_customers RETURN NUMBER;
END pkg_customer;
/
基本構文 — 本体 (PACKAGE BODY)
CREATE OR REPLACE PACKAGE BODY pkg_customer AS
-- プライベート変数 (仕様部に宣言なし -> 外部から見えない)
g_last_inserted_id NUMBER;
-- プライベートプロシージャ (内部ヘルパー)
PROCEDURE log_action(p_action VARCHAR2) IS
BEGIN
INSERT INTO action_log(action, log_time)
VALUES (p_action, SYSDATE);
END log_action;
-- 公開プロシージャの実装
PROCEDURE register_customer(
p_name IN VARCHAR2,
p_email IN VARCHAR2,
p_id OUT NUMBER
) IS
BEGIN
INSERT INTO customers(name, email, status)
VALUES (p_name, p_email, c_default_status)
RETURNING customer_id INTO p_id;
g_last_inserted_id := p_id;
log_action('REGISTER: ' || p_name);
END register_customer;
FUNCTION find_customer(p_id IN NUMBER) RETURN customer_rec IS
v_rec customer_rec;
BEGIN
SELECT customer_id, name, email
INTO v_rec
FROM customers
WHERE customer_id = p_id;
RETURN v_rec;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END find_customer;
FUNCTION count_active_customers RETURN NUMBER IS
v_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_count
FROM customers WHERE status = c_default_status;
RETURN v_count;
END count_active_customers;
-- パッケージ初期化ブロック (セッション内で初回参照時に 1 度だけ実行)
BEGIN
g_last_inserted_id := 0;
log_action('PACKAGE INITIALIZED');
END pkg_customer;
/
パッケージの呼び出し
-- 名前空間付きで呼び出し
DECLARE
v_id NUMBER;
v_cnt NUMBER;
v_rec pkg_customer.customer_rec;
BEGIN
pkg_customer.register_customer('山田太郎', 'yamada@example.com', v_id);
DBMS_OUTPUT.PUT_LINE('登録 ID: ' || v_id);
v_cnt := pkg_customer.count_active_customers;
DBMS_OUTPUT.PUT_LINE('アクティブ件数: ' || v_cnt);
v_rec := pkg_customer.find_customer(v_id);
DBMS_OUTPUT.PUT_LINE('名前: ' || v_rec.customer_name);
END;
/
パッケージのメリット
| メリット | 詳細 |
|---|---|
| カプセル化 | 本体の実装変更が仕様部に影響しないなら、呼び出し側の再コンパイル不要 |
| 名前空間 | pkg_customer.find と pkg_order.find のように同名関数を共存させられる |
| セッション変数 | パッケージのグローバル変数はセッション内で値を保持 (擬似的なクラス変数) |
| パフォーマンス | 初回参照時にパッケージ全体がメモリにロードされる — 関連処理の連続実行が高速 |
| オーバーロード | 同名で引数の異なる関数を複数定義可 |
| 権限管理が楽 | パッケージ単位で EXECUTE 権限を付与 |
パッケージのプライベートとパブリック
| 宣言場所 | 公開範囲 |
|---|---|
| 仕様部に宣言 + 本体に実装 | パブリック (外部から呼べる) |
| 本体のみに宣言 + 実装 | プライベート (パッケージ内部のみ) |
パッケージ初期化ブロック
パッケージ本体の末尾に BEGIN ... END; を書くと、そのパッケージがセッション内で初めて参照されたときに 1 回だけ実行されます。設定読み込みや初期データ取得に便利。
CREATE OR REPLACE PACKAGE BODY pkg_config AS
g_app_name VARCHAR2(100);
g_environment VARCHAR2(20);
FUNCTION app_name RETURN VARCHAR2 IS
BEGIN RETURN g_app_name; END;
FUNCTION environment RETURN VARCHAR2 IS
BEGIN RETURN g_environment; END;
BEGIN
-- セッション開始後、最初に pkg_config が参照されたタイミングで実行
SELECT name, env INTO g_app_name, g_environment
FROM app_config WHERE active = 'Y' AND ROWNUM = 1;
END pkg_config;
/
オーバーロード
CREATE OR REPLACE PACKAGE pkg_util AS
FUNCTION fmt(p_num IN NUMBER) RETURN VARCHAR2;
FUNCTION fmt(p_date IN DATE) RETURN VARCHAR2;
FUNCTION fmt(p_text IN VARCHAR2) RETURN VARCHAR2;
END pkg_util;
/
CREATE OR REPLACE PACKAGE BODY pkg_util AS
FUNCTION fmt(p_num IN NUMBER) RETURN VARCHAR2 IS
BEGIN RETURN TO_CHAR(p_num, 'FM999,999,990.00'); END;
FUNCTION fmt(p_date IN DATE) RETURN VARCHAR2 IS
BEGIN RETURN TO_CHAR(p_date, 'YYYY-MM-DD HH24:MI:SS'); END;
FUNCTION fmt(p_text IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN RETURN '[' || p_text || ']'; END;
END pkg_util;
/
パッケージの管理
-- パッケージ一覧
SELECT object_name, status
FROM user_objects
WHERE object_type IN ('PACKAGE','PACKAGE BODY');
-- 仕様部 / 本体のソース取得
SELECT text
FROM user_source
WHERE name = 'PKG_CUSTOMER' AND type = 'PACKAGE'
ORDER BY line;
-- パッケージ削除
DROP PACKAGE pkg_customer; -- 仕様部 + 本体 ともに削除
DROP PACKAGE BODY pkg_customer; -- 本体のみ
設計のコツ
- 業務単位でパッケージを切る (顧客 / 受注 / 在庫など)
- 仕様部は安定させる — 本体だけ変更すれば呼び出し側の再コンパイル不要
- セッション変数は濫用しない — 接続プール環境ではセッション再利用で状態が漏れる
- プライベートヘルパーは積極的に活用 — 仕様部を肥大化させない
- 定数 (CONSTANT) を仕様部に集約してマジックナンバーを排除
FAQ
Q: パッケージとプロシージャ単独はどちらを使うべき?
A: パッケージ推奨。名前空間、初期化、グローバル変数、依存関係解決の安定性などメリットが大きい。スタンドアロンプロシージャは依存先の変更でステータスが INVALID になりやすい。
Q: パッケージのグローバル変数はトランザクション単位?
A: いいえ、セッション単位で保持される。COMMIT / ROLLBACK では消えない。DBMS_SESSION.RESET_PACKAGE で明示的にリセット可。
Q: 本体だけ変更したのに呼び出し側がエラーになる
A: 仕様部のタイムスタンプが変わると呼び出し側が INVALID になる仕様。ALTER ... COMPILE で再コンパイルするか、シグネチャ依存性を使う設定にしておく。
関連
- マテリアライズド・ビュー — Oracle のもうひとつの強力な機能
- プロシージャ / ファンクション — 単体の PL/SQL オブジェクト
- カーソル / 例外処理 — PL/SQL の基本構文