OracleのPL/SQLで無理やりに継承を使ってみる。簡単な例でログ出力の仕組みでも作ってみた。
PL/SQLで継承を使うにはTYPEを作成してオブジェクト?を作成する。
--ログ出力用基本オブジェクト
CREATE OR REPLACE TYPE logger_typ AS OBJECT
(
MY_TYPE varchar2(20),
MEMBER PROCEDURE write( MSG IN logmsg_typ )
) NOT final;
/
--基本形 (DBMS_OUTPUT)
CREATE OR REPLACE TYPE BODY logger_typ AS
MEMBER PROCEDURE write( MSG IN logmsg_typ ) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(MSG.LOG_DATE || ' ' ||
MSG.LOG_LEVEL || ' ' ||
MSG.LOG_MSG || ' ' ||
MSG.LOG_HOST || ' ' ||
MSG.LOG_OSUSER || ' ' ||
MSG.LOG_PRG || ' ' ||
MSG.LOG_SUPPLE );
END;
END;
/
ログ出力用のオブジェクトなので、もっとも基本の形をDBMS_OUTPUTを使用したものにした。TYPEの中でwriteプロシジャを持たせている。
writeプロシジャへの引数としては出力するメッセージを格納したTYPEを渡している。
CREATE OR REPLACE TYPE logmsg_typ AS OBJECT
(
LOG_DATE TIMESTAMP,
LOG_LEVEL varchar2(3),
LOG_MSG varchar2(1000),
LOG_HOST varchar2(30),
LOG_OSUSER varchar2(30),
LOG_PRG varchar2(50),
LOG_SUPPLE varchar2(2000)
) NOT final;
/
さて、DBMS_OUTPUT以外にテーブルへも格納したくなったので、基本形のオブジェクトを継承してwriteプロシジャをオーバーライドする。
-- logger_table_typ
-- テーブル出力型 (自律型トランザクションを使用)
CREATE OR REPLACE TYPE logger_table_typ UNDER logger_typ
(
OVERRIDING MEMBER PROCEDURE write( MSG IN logmsg_typ )
) NOT final;
/
CREATE OR REPLACE TYPE BODY logger_table_typ AS
OVERRIDING MEMBER PROCEDURE write( MSG IN logmsg_typ ) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO APL_LOG_TABLE (
L_DATE,
L_LEVEL,
L_MSG,
L_HOST,
L_OSUSER,
L_PRG,
L_SUPPLE
) values (
MSG.LOG_DATE,
MSG.LOG_LEVEL,
MSG.LOG_MSG,
MSG.LOG_HOST,
MSG.LOG_OSUSER,
MSG.LOG_PRG,
MSG.LOG_SUPPLE
);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
END;
使う側としては同じように呼び出しをするが、生成するオブジェクトをlogger_typにするかlogger_table_typにするかで出力先が画面出力かテーブル出力か切り替わる。
ちなみに、テーブル出力はログ記録を確実に残したいので自律型トランザクションを宣言して、呼び出し側のrollbackに関係なく記録されるようにするのは常識だ。
こんな感じで、ファイル出力型もあっていいかもしれない。これはこれで継承してwriteプロシジャの中身を変えてしまえばいい。
しかし、単純にこれだけでは使う方も面倒だ。例えば複数の記録媒体へログを記録したいときなどプログラムが複数のオブジェクトを直接操作することになる。そういうことを避けるためpackageを用意する。
CREATE OR REPLACE PACKAGE logger IS
/*===========================================================
* plsqlログ出力用パッケージ
*==========================================================*/
--デフォルトのログ出力先
DEFAULT_LOGGER varchar2(50) := 'DBMS_OUTPUT';
--ログ出力オブジェクトの追加
--引数なしで呼ばれる場合はDEFAULT_LOGGERがセットされる。
PROCEDURE SET_LOGGER;
PROCEDURE SET_LOGGER( logger_type IN varchar2 );
--ログ出力オブジェクトの削除
PROCEDURE DEL_LOGGER( logger_type IN varchar2 );
--ログオブジェクトの数を取得
FUNCTION GET_LOGGER_COUNT RETURN NUMBER;
--ログ出力用
PROCEDURE write( msg IN varchar2 );
PROCEDURE write( log_level IN varchar2,
msg IN varchar2 );
PROCEDURE write( err IN excep_typ );
END logger;
/
まずはシンプルに、このくらいの機能で始めてみる。(こえは仕様部)
SET_LOGGERは指定されたログ記録オブジェクトを用意するプロシジャ。これは複数のオブジェクトを保持できるように配列にして保持する。
--ログ出力オブジェクト格納エリア(複数のオブジェクトを格納可能とする)
TYPE ArrayLoggerTyp IS TABLE OF logger_typ;
ArrayLogger ArrayLoggerTyp;
ただし外部から直接操作させないために、packageヘッダー部ではなくbodyの方へ書く。
SET_LOGGERはこんな感じ。
----------------
-- ログ出力オブジェクトの生成(指定あり)
----------------
PROCEDURE SET_LOGGER( logger_type IN varchar2 ) IS
my_logger logger_typ := NULL;
BEGIN
IF ArrayLogger.COUNT > 0 THEN
--------------------
-- 生成されるloggerが既に登録済みかチェック
--------------------
FOR i IN ArrayLogger.FIRST .. ArrayLogger.LAST
LOOP
IF ( ArrayLogger(i).MY_TYPE = logger_type ) THEN
RETURN;
END IF;
END LOOP;
END IF;
CASE upper(logger_type)
WHEN 'DBMS_OUTPUT' THEN
ArrayLogger.EXTEND;
ArrayLogger(ArrayLogger.LAST) := logger_typ('DBMS_OUTPUT');
WHEN 'TABLE' THEN
ArrayLogger.EXTEND;
ArrayLogger(ArrayLogger.LAST) := logger_table_typ('TABLE');
WHEN 'FILE' THEN
NULL;
ELSE
NULL;
END CASE;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
少し美しくない部分もあるが、まずはお試しということで良とする。オブジェクトのコンストラクタでは引数でどのタイプのログ出力オブジェクトかを表す文字列を渡している。これは同じタイプのオブジェクトを複数登録された場合にログ出力内容が重複してしまうのを防ぐためで、SET_LOGGERの最初に登録済みのオブジェクトと重複していないかを必ずチェックする。
DEL_LOGGERは指定されたログ記録オブジェクトを削除する。削除された時から対象のログ記録はされなくなる。
GET_LOGGER_COUNTはログ記録オブジェクトの数を知るため。(通常はあまり使用しないだろう)
writeプロシジャはいろいろな引数でログを記録するためのプロシジャ。単純に文字列を渡された場合や、ログレベルと文字列を渡された場合、例外が発生したときに例外オブジェクトを渡され記録する場合など。
ちなみに、例外オブジェクトというのも自前で作成する。これは例外処理用のpackageを別途用意しており、そこでオブジェクトが生成される。
テスト用のコードを書いてみる。
DECLARE
BEGIN
-- デフォルトログ出力はDBMS_OUTPUTを使った画面出力
logger.write('This is logger test!!');
logger.write('E','This is logger ERROR test!!');
DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);
-- 重複したログ出力オブジェクトを指定しても問題ないか?
logger.SET_LOGGER('DBMS_OUTPUT');
logger.write('This is logger 2 test!!');
logger.write('E','This is logger ERROR 2 test!!');
DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);
-- テーブル出力ログオブジェクトを追加
-- この時ログ出力オブジェクトは2種類。DBMS_OUTPUTとTABLE
-- 1回のwriteで両方に出力される。
logger.SET_LOGGER('TABLE');
logger.write('This is logger table test!!');
logger.write('E','This is logger ERROR table test!!');
DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);
-- DBMS_OUTPUTログオブジェクトを削除
-- この時点からログ出力はTABLEのみになる。
logger.DEL_LOGGER('DBMS_OUTPUT');
logger.write('This is logger table only test!!');
logger.write('E','This is logger ERROR table only test!!');
DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);
END;
お試しの感じでPL/SQLの継承機能を使ってみたが、やはり本格的なオブジェクト指向言語に比べものにはならない。そもそもそう言う場合はjavaを使えと言うことかもしれないが・・・