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

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

第121回「実用WEBアプリ 文書管理システム(2)ディレクトリ登録」

2015.03.19

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

前回から、新シリーズ「実用WEBアプリ」を開始しました。その第一弾として文書管理システムです。
前回で概要を説明していますが、このシステムはデータベースに文書を登録して、ブラウザでそれを表示する単純なシステムです。そして文書の数が増えても管理しやすいように、ディレクトリを登録(作成)できます。もちろん、ディレクトリは階層的に作成できますので、必要に応じた整理分類ができます。

それらの文書やディレクトリは以下の表に格納します。表の名前はDOCS表でしたね。

SQL> SHOW USER ユーザーは"SCOTT"です。

1
2
3
4
5
6
7
8
9
10
11
SQL> DESC DOCS
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER           --番号(主キー)
 TITLE                                     NOT NULL VARCHAR2(300)    --タイトル
 KBN                                       NOT NULL NUMBER           --区分1:文書 2:ディレクトリ
 HONBUN                                             CLOB             --本文(文書のときのみセット)
 OYA_DIR                                            NUMBER           --親ディレクトリ
 SEQ                                                NUMBER           --ディレクトリ内の順序
 INSERT_DATE                               NOT NULL DATE             --登録日
 UPDATE_DATE                               NOT NULL DATE             --更新日

DOCS表には、文書(KBN=1)だけでなく、ディレクトリ(KBN=2)も登録します。また文書もディレクトリも親ディレクトリ(OYA_DIR)を持つことができます。つまり、ディレクトリによる階層的な管理ができます。

まず最初にすべきことは、ディレクトリ登録です。最低でも一つのディレクトリがあり、その下に文書が登録可能となります。ということで、今回はディレクトリを作成(登録)するプロシージャを作成します。
名前を「DOC_DIR_MAKE」(フォーム)および「DOC_DIR_MAKE_EXE」(実行処理)にします。なお、この文書管理システムで作成するプロシージャは「DOC_xxxxx」といった名前の形式で作成するつもりです。そして、すべてのプロシージャが完成した段階で、プロシージャ名から、「DOC_」という文字をとって、その代わり「DOC」という名前のパッケージにまとめる予定です。すなわち、「DOC_DIR_MAKE」というプロシージャはパッケージ化したあと、「DOC.DIR_MAKE」という名前で使用可能になります。

では早速、ディレクトリ登録フォーム画面「DOC_DIR_MAKE」プロシージャを確認します。以下のような内容で作成しました。
(注意)以下のソースコードはブラウザ表示のために、山括弧(「<」と「>」)を全角にしています。
コピーして実行する方は、必ず、すべての山括弧(「<」と「>」)を半角に変換してください。

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
CREATE OR REPLACE PROCEDURE DOC_DIR_MAKE
IS
 V_ERRMSG  VARCHAR2(500);
BEGIN
/*****************************************/
/* ディレクトリ登録のためのフォーム画面  */
/*****************************************/
  HTP.P('<HTML>');
  HTP.P('<HEAD><TITLE>ディレクトリ登録</TITLE></HEAD>');
  HTP.P('<BODY>');
  HTP.P('<H1>ディレクトリ登録</H1>');
  HTP.P('<HR>');    -- 横線
  HTP.P('<FORM ACTION="doc_dir_make_exe" METHOD="POST">');
  HTP.P('<TABLE BORDER>');
  HTP.P('<TR><TD BGCOLOR="LIGHTYELLOW">親ディレクトリのID</TD><TD>' ||
  '<INPUT TYPE="TEXT" NAME="P_OYA_ID" SIZE="8"> (省略:最上位ディレクトリ)</TD></TR>');
  HTP.P('<TR><TD BGCOLOR="LIGHTYELLOW">表示順番</TD><TD>' ||
  '<INPUT TYPE="TEXT" NAME="P_SEQ" SIZE="8"> (同じディレクトリ内の表示順)</TD></TR>');
  HTP.P('<TR><TD BGCOLOR="LIGHTYELLOW">タイトル</TD><TD>' ||
       '<INPUT TYPE="TEXT" NAME="P_TITLE" SIZE="60"></TD></TR>');
  HTP.P('</TABLE>');
  HTP.P('<INPUT TYPE="SUBMIT" VALUE="送信">');
  HTP.P('</FORM>');
  HTP.P('</BODY>');
  HTP.P('</HTML>');
EXCEPTION
  WHEN  OTHERS  THEN
     V_ERRMSG := SQLERRM;
     HTP.P(V_ERRMSG);
END  DOC_DIR_MAKE;
/
 
プロシージャが作成されました。

このフォーム画面をブラウザに表示してみましょう。

バックナンバー 第103回「WEBアプリ作成(1) (Oracle DBとPL/SQLだけで、即、WEBアプリ)」と同等の設定ができている方は以下のURLで表示できます。

http://localhost:8080/dad/doc_dir_make

ログインを求められたら、SCOTTユーザでログインしてください。


ご覧のようにこのフォームには、項目が3つあります。
まず、「親ディレクトリのID」ですが、上記ソースコードに記述があるように、パラメータ名は「P_OYA_ID」(ソースコード 16行目)です。同様に「表示順番」のパラメータ名は「P_SEQ」(18行目)、「タイトル」のパラメータ名は「P_TITLE」(17行目)です。送信ボタンをクリックすると、これらのパラメータが「doc_dir_make_exe」プロシージャ(ソースコード 13行目 FORMタグのACTION属性)に送信されますが、このプロシージャは現時点ではまだ存在しませんので、送信してもエラーとなります。

その「DOC_DIR_MAKE_EXE」プロシージャですが、以下のように作成しました。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
CREATE OR REPLACE PROCEDURE DOC_DIR_MAKE_EXE
(  P_OYA_ID  IN VARCHAR2,
   P_SEQ     IN VARCHAR2,
   P_TITLE   IN VARCHAR2)
IS
 V_ID        NUMBER;
 V_OYA_ID    NUMBER;
 V_SEQ       NUMBER;
 V_ERRMSG  VARCHAR2(500);  --エラーメッセージ用変数
 ERROR01     EXCEPTION;
BEGIN
/*****************************************/
/* ディレクトリ登録の実行処理            */
/*****************************************/
--
/***********************************/
/*親ディレクトリ(親ID)のチェック   */
/***********************************/
  V_OYA_ID := TO_NUMBER(P_OYA_ID);   -- 親IDが数字かどうかのチェック
  IF V_OYA_ID IS NULL  THEN
     NULL;      -- 親IDはセットされていなくてもOK(この場合、最上位ディレクトリ)
  ELSE  -- 親IDがセットされている場合はそれが存在し、ディレクトリ(KBN=2)であること
    DECLARE  -- ネストブロック
       V_KBN  NUMBER;
    BEGIN
       SELECT  KBN INTO V_KBN FROM DOCS WHERE ID = V_OYA_ID;
       IF V_KBN = 2 THEN
          NULL;     -- ディレクトリであればOK
       ELSE         -- ディレクトリではない(つまり文書)のときはエラー
          V_ERRMSG := '親ディレクトリのIDは、ディレクトリでなく文書です';
          RAISE  ERROR01;  -- エラー処理へ
       END IF;
    EXCEPTION
       WHEN NO_DATA_FOUND THEN   -- 存在しない場合もエラー
          V_ERRMSG := '指定された親ディレクトリのIDは存在しません';
          RAISE  ERROR01;   -- エラー処理へ
    END;
  END IF;
/**********************************/
/* 表示順(P_SEQ)のチェック        */
/**********************************/
   V_SEQ  := TO_NUMBER(P_SEQ);   --P_SEQが数字であることのチェック
   IF V_SEQ IS NULL THEN
      NULL;                      -- 表示順はNULLでもOK
   END IF;
/**********************************/
/* タイトル(P_TITLE)のチェック    */
/**********************************/
   IF P_TITLE IS NULL THEN
      V_ERRMSG := 'タイトルがセットされていません';
      RAISE ERROR01;    -- エラー処理へ
   END IF;
/*********************************************************************/
/* もろもろのチェックがOKであれば次にデータ登録処理                  */
/*********************************************************************/
-- 主キーを生成する(※簡単ですが、実際にはお勧めできない方法です。要解説確認)
   SELECT NVL(MAX(ID),0) + 1 INTO V_ID FROM DOCS;
-- DOCS表にINSERTする
   INSERT INTO DOCS(ID, TITLE, KBN, OYA_DIR, SEQ, INSERT_DATE, UPDATE_DATE)
   VALUES (V_ID, P_TITLE, 2, V_OYA_ID, V_SEQ, SYSDATE, SYSDATE);
-- 1行操作されたことの確認
   IF SQL%ROWCOUNT = 1 THEN
      COMMIT;
      HTP.P('ディレクトリが登録されました ID = ' || TO_CHAR(V_ID));
   ELSE
      ROLLBACK;
      HTP.P('ディレクトリ登録処理は異常です。処理を取り消しました');
   END IF;
EXCEPTION
  WHEN  VALUE_ERROR THEN
        HTP.P('親ディレクトリ、表示順番に数値でない値がセットされています');
  WHEN  ERROR01 THEN
      HTP.P(V_ERRMSG);
  WHEN  OTHERS  THEN
     V_ERRMSG := SQLERRM;
     HTP.P(V_ERRMSG);
END  DOC_DIR_MAKE_EXE;
/
 
プロシージャが作成されました。

解説します。まずこのプロシージャはフォーム画面から、「P_OYA_ID」、「P_SEQ」「P_TITLE」という3つのリクエストパラメータを受信しますので、これらのリクエストパラメータはプロシージャのパラメータとして定義される必要があります。(2~4行目)

これらのパラメータの値はフォーム画面で手入力されるものなので、値が適切かどうか、きちんとチェックをする必要があります。

最初に親ディレクトリのID(以下、親ID)のチェックです(19~38行目)。
19行目でTO_NUMBER関数を使って、親IDをNUMBER型に変換し変数に取得します。この変数は、後にデータ登録処理で使用しますが、同時にこのNUMBER型への変換処理は数字として適切な値であるかどうかのチェックにもなります。
もし、数字に変換できないような値であればTO_NUMBER関数がエラー(VALUE_ERROR)になります。
その結果、例外処理部のVALUE_ERROR例外ハンドラ(70~71行目)に飛び、数値ではない値がセットされていることをユーザに通知することができます。
また、23~37行目にかけてネストブロックとなっていますが、この意図は26行目のSELECT INTO文が0件でエラーのとき、いきなり例外処理部(69行目以下)に飛ぶのではなく、いったんネストブロックの例外処理部(33~36行目)で、エラーメッセージ(35行目)を用意してからエラー処理へ飛ばすという意図です。
これにより、親IDが存在しない場合(SELECT INTO文が0件の場合)、「指定された親ディレクトリのIDは存在しません」というエラーメッセージをユーザに返すことができます。
またSELECT INTO文が1件取得できた場合でも、その行がKBN=2でなければ、ディレクトリではないので、これも同様の方法でエラーメッセージを用意して、エラー処理へ飛ばします。(30~31行目)

親ディレクトリのチェックがOKだった場合は、次に表示順のチェックを行います。親IDと同じように、TO_NUMBER関数でNUMBER型に変換するとともに、そのこと自体が数字であることのチェックになります。(42行目)
なお、DOCS表の中で、表示順(SEQ列)はNOT NULLの制約はないので、NULL値もOKとしてあります。(43~45行目)

次にタイトルのチェックですが、もしNULLであれば、エラーとします(49~52行目)。

こうした諸々のチェックがOKであれば、次にそれらのパラメータ値を使ったデータ登録処理となります。
最初に主キーの値を生成する必要があります。主キーの値を生成するために、ここではID列の最大値(MAX)の値に1を加算した値を生成しています(57行目 なお、NVL関数はDOCS表が空の場合の対応)。
しかし、この方法は複数のユーザが同時にアクセスする場合、タイミングによっては同じ値を生成します。それを防ぐためには表全体に排他的なロックをかける必要があります。そうすると、他のユーザの更新処理と競合しますので、複数ユーザで利用する場合は適切ではありません。そのような場合は順序オブジェクトを使ったり、あるいは、発番値を管理する発番表を使ったプログラム的な発番処理がおすすめです。
バックナンバー 第19回第20回にプログラム的な発番処理について解説してありますので、興味のある方はご覧ください。
なお、今回の発番方法、つまり最大値MAX(ID)に1を加算した値を使う方法ですが、複数ユーザでの同時処理がなければほとんど問題ないといえます。なぜならID列は主キーであり、必ず索引がありますので、表の行数が多くても、競合がなければ最大値の取得自体は索引を通してほとんど瞬時に行える処理だからです。

このように主キーも含めてINSERTに必要な値が用意できたら、データ登録、すなわちINSERT処理(59~60行目)となります。
その後、念のために処理行数(SQL%ROWCOUNT 62行目)が1行であることを確認し、確定(COMMIT)した後、ディレクトリが登録された旨のメッセージを表示します(62~64行目)。もし処理行数が1行でなければ、なにか想定外の異常な状況なので、処理を取り消し(ROLLBACK)、その旨のメッセージを表示します(65~67行目)。

以上がディレクトリ登録処理の内容です。

これでプロシージャがそろったので、早速、ディレクトリを登録してみましょう。
先ほどのフォーム画面に以下のように入力して送信ボタンをクリックします。



ID=1 でディレクトリが登録されました。



このディレクトリの下にさらに子供のディレクトリを作成してみます。親ディレクトリのIDを1にします。



ID=2 でディレクトリが登録されました。



DOCS表を問いあわすと、ID=1とID=2の行があり、ともにKBN=2なのでディレクトリとして登録されたことがわかります。
また、ID=2の行のOYA_DIR列は「1」なので、ID=2の行の親ディレクトリはID=1のディレクトリであることがわかります。



ここでは触れませんが、当然チェックでNGの内容であれば、それに応じたエラーメッセージが通知されることも確認する必要があります。

このようにディレクトリとはいっても、普段皆さんがパソコンの中に作成するディレクトリ(フォルダ)のことではなく、単なる情報としての(いわば仮想的な)ディレクトリなのですが、きちんとデータベースに登録されたことがわかりますね。

これでディレクトリが作成できるようになったので、このディレクトリの下に文章を作成することが可能になりました。
ということで、次回は文章を作成(登録)するプロシージャを作成しましょう。

それでは次回、ご期待ください。

先頭へ戻る