e-learning、オラクル研修、LMS(学習管理システム)のiStudy

e-learning、オラクル研修、LMS(学習管理システム)のiStudy

第90回 「DBMS_PIPEを使った非同期メッセージ通知(2)」

2014.03.20

こんにちは。インストラクターの蓑島です。

今回も前回に引き続き、「DBMS_PIPEを使った非同期メッセージ通知」について解説します。

前回はDBMS_PIPEパッケージを使って複数のセッションの間でメッセージを非同期に送受信できることを確認しました。 今回はそれを使った簡単な応用例を行ってみたいと思います。いわば非同期通知の実験のようなものです。たいへん簡単な内容ですが、DBMS_PIPEパッケージの機能を理解するヒントになると思います。

ストーリーはこうです。

まず、全ユーザで共通して使用するパイプの名前を決めておきます。仮にそのパイプの名前を「COMMON」とします。
そして説明を簡単にするために、ここでは全ユーザといっても前回作成したTEST01ユーザとTEST02ユーザに限定します。
(前回解説したように、ユーザには、DBMS_PIPEパッケージのEXECUTE権限が必要です)

最初にTEST01ユーザは自分のセッション名を「COMMON」パイプに送信して格納します。
ここでセッション名とはセッションを識別する一意な名前なので、一意でさえあれば何でも構いません。通常はDBMS_PIPEパッケージのUNIQUE_SESSION_NAMEファンクションによって取得されるシステム生成名を用います。セッション毎に必ず一意な名前になることが保証されている文字列で、"ORA$PIPE$xxxx"といった書式で生成されます。

次にTEST01ユーザはこのセッション名を名前とするパイプからメッセージを受信してそれを画面に表示する処理を行います。
しかし、そのようなセッション名を名前とするパイプがこの時点でまだ存在していないので、TEST01ユーザは待機することになります。つまり誰かがそのセッション名のパイプにメッセージを送信してくれるまで、TEST01ユーザは待機するわけです。

TEST01ユーザが待機している間に、TEST02ユーザがCOMMONパイプからセッション名を受信します。このセッション名は上の処理でTEST01ユーザがCOMMONパイプに送信したものです。TEST02ユーザはセッション名を取得したらそのセッション名のパイプに何らかのメッセージを送信します。

それにより、そのセッション名のパイプが作成されメッセージが格納されるので、待機していたTEST01ユーザの処理が再開して、そのセッション名のパイプからメッセージを取得し画面表示します。

以上が今回のストーリです。

簡単に言えば、TEST01ユーザは誰かが自分宛てにメッセージを送ってくれるまで待機するわけです。
そして誰かが(今回はTEST02ユーザが)、メッセージを送ってくれれば、TEST01ユーザの待機は解消され、そのメッセージを画面表示する、ということです。

それでは早速行ってみます。以下の例をご覧ください。

まずTEST01ユーザのセッションで以下のPL/SQLプログラムを実行します。
画面表示も有効にしておきます。
SQL> SHOW USER
ユーザーは"TEST01"です。
SQL> SET SERVEROUTPUT ON --画面表示有効

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
DECLARE
     PIPE_NAME  VARCHAR2(30);
     STATUS     NUMBER;
     V_MESSAGE  VARCHAR2(32767);
     END_OF_MESSAGE   EXCEPTION;
     PRAGMA EXCEPTION_INIT(END_OF_MESSAGE,-6556);
BEGIN
      /************************************************************************/
     /* セッション名を取得し、PIPE_NAME変数に格納                             */
     /*************************************************************************/
     PIPE_NAME := DBMS_PIPE.UNIQUE_SESSION_NAME;
--
     /************************************************************************/
     /* PIPE_NAME変数の値をCOMMONパイプに送信                                */
     /************************************************************************/
     DBMS_PIPE.PACK_MESSAGE(PIPE_NAME);
     STATUS := DBMS_PIPE.SEND_MESSAGE( 'COMMON' );
--
     /************************************************************************/
     /*  セッション名(PIPE_NAME)と同じ名前のパイプからメッセージを受信する   */
     /*  メッセージがなければ届くまで待機する                                */
     /************************************************************************/
     STATUS := DBMS_PIPE.RECEIVE_MESSAGE(PIPE_NAME);
--
     /************************************************************************/
     /*  メッセージを受信したら画面表示する **                               */
     /************************************************************************/
     LOOP
        DBMS_PIPE.UNPACK_MESSAGE(V_MESSAGE);
        DBMS_OUTPUT.PUT_LINE(V_MESSAGE);
     END LOOP;
EXCEPTION
     /***********************************************************************/
     /*  すべてのメッセージを取り出し終わったときの処理                     */
     /***********************************************************************/
     WHEN  END_OF_MESSAGE THEN   -- メッセージ取り出しの終了
           DBMS_OUTPUT.PUT_LINE( '以上' );
END ;
/
 
<<<この処理は待機します>>>

それでは解説します。
まず、先ほど説明したように、このPL/SQLブロックを実際に実行すると待機します。
では処理内容の解説です。
コメントを書いていますが、11行目でセッション名をPIPE_NAMEという変数に取得します。
そして、この値を16~17行目でCOMMONパイプに送信します。

次に23行目でセッション名(PIPE_NAME)のパイプからメッセージを受信しようとしていますが、ここで待機します。つまりこの時点ではそのような名前のパイプは存在しないからです。
後にTEST02ユーザのセッションがCOMMONパイプからそのセッション名を取り出し、その名前のパイプにメッセージを送信するので、それまで上記の処理は待機するわけです。
パイプにメッセージが送信されれば待機が解消され、29~30行目でそのパイプから取り出したメッセージを画面表示します。
この間の処理はLOOP処理なので、繰り返され、やがてパイプが空になった状態でさらにメッセージを取り出そうとすると、「ORA-06556: パイプが空です。UNPACK_MESSAGEリクエストを実行できません。」のエラーとなります。(前回解説)
しかし上記のPL/SQLブロック内でORA-06556エラーに対して「END_OF_MESSAGE」という名前のユーザ定義の例外を宣言している(5~6行目)ので、そのエラーが発生しても、結局36行目のEND_OF_MESSAGE例外ハンドラを実行して正常に終了するわけです。

ではこの処理が待機している間に、別画面を開き、TEST02ユーザで接続してください。そして以下のようにPL/SQLプログラムを実行します。画面出力も有効としてください。

<<別画面のTEST02のセッション>>
SQL> CONNECT TEST02/TEST02
SQL> SHOW USER
ユーザーは"TEST02"です。
SQL> SET SERVEROUTPUT ON -- 画面出力を有効

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DECLARE
   PIPE_NAME   VARCHAR2(30);
   STATUS      NUMBER;
BEGIN
   /************************************************************************/
   /* COMMONパイプからメッセージを取得(TEST01がセットしたセッション名)    */
   /************************************************************************/
   STATUS := DBMS_PIPE.RECEIVE_MESSAGE( 'COMMON' );
   DBMS_PIPE.UNPACK_MESSAGE(PIPE_NAME);
--
   /************************************************************************/
   /* 取得したセッション名(PIPE_NAME)を画面表示する                        */
   /************************************************************************/
   DBMS_OUTPUT.PUT_LINE( 'PIPE_NAME=' || PIPE_NAME);
--
   /*************************************************************************/
   /* 取得したセッション名(PIPE_NAME)をパイプ名として2件のメッセージを送信 */
   /*************************************************************************/
   DBMS_PIPE.PACK_MESSAGE( 'こんにちは。私はTEST02です' );
   DBMS_PIPE.PACK_MESSAGE( 'よろしくお願いします' );
   STATUS := DBMS_PIPE.SEND_MESSAGE(PIPE_NAME);
END ;
/
PIPE_NAME=ORA$PIPE$009100040001
 
PL/SQLプロシージャが正常に完了しました。

これもコメントがあるのでわかりやすいと思います。
8~9行目でCOMMONパイプからPIPE_NAME変数に取得したメッセージがTEST01ユーザのセッションでセットしたセッション名です。
参考までに14行目で画面表示しています。その値は「ORA$PIPE$xxxxxxx」といったシステム生成名で、セッションを識別できる一意な名前です。

次にこのセッション名をパイプ名にして、2件のメッセージ(「こんにちは。私はTEST02です」、「よろしくお願いします」)を送信します。(19~21行目)

この処理により、先ほどまで待機していたTEST01の処理が再開され、以下のメッセージを表示して終了します。

<<待機していたTEST01のセッション画面 待機が解除されメッセージが表示される>>

1
2
3
4
5
6
    ~略~
こんにちは。私はTEST02です
よろしくお願いします
以上
 
PL/SQLプロシージャが正常に完了しました。

このようにTEST02セッションで送った2件のメッセージが待機していたTEST01セッションに届き、待機が解除されそのメッセージを表示した様子が確認できます。
いかがですか?2枚の画面を使いながら実際に行ってみるとよく理解できると思います。
それでは今回はここまでにいたします。また次回、ご期待ください。

先頭へ戻る