マッパークラス作成

1. マッパークラスとは

マッパークラスは、SQLマッパーを定義する時に指定しますが、どのマッパークラスを指定するかによりデータベースアクセス時の振る舞いが変わってきます。
Webtribeではいくつかのマッパークラスを標準で提供していますが、次のように、標準のマッパークラスでは実現できない処理を行う必要がある場合には、要件に応じたマッパークラスを作成し、プロジェクトに登録して使うことができます。

マッパークラスは Java のクラスとして実装し、実行時にDBServerから呼び出されます。
マッパークラスを作成するためには、JDBC のコーディングを行います。
そのため、このドキュメントでは Webtribe に対する知識の他に、次のような前提知識が必要となります。

 

2. マッパークラス作成に必要なもの

マッパークラスを作成するために必要なものは、次の通りです。

 

3. マッパークラスでのコーディング

3-1. もっともシンプルなマッパークラスのコーディング(更新処理)

ここでは例として、テーブルから全レコードを削除するマッパークラスのコーディングについて説明します。
全レコードを削除するだけですので、入力アイテム、出力アイテムとも使いません。(絞込み条件や処理結果のデータが発生しないためです。)
マッパークラスに定義された内容を実行するだけの処理となります。
SQLマッパーに定義された SQL が

DELETE FROM WORKTABLE
といった SQL になる場合、この SQL には入力パラメータ、出力パラメータのいずれも必要ありません。そこで、入力アイテム、出力アイテムの処理は行っていません。
補足補足
ひとつのマッパークラスで、入力アイテムがある場合も、ない場合も、それぞれの場合に応じた処理を行えるような記述をすることは可能ですが、ここではまずマッパークラスとして、最低限どのような記述が必要かということをご理解いただくために、入力アイテムをチェックする、といった処理は行っていません。

上記 SQL を実行するために必要なマッパークラスの処理は次のようになります。
 1: package samples;
 2: import java.sql.SQLException;
 3: import java.sql.Statement;
 4: import jp.ne.mki.wedge.run.db.dc.SqlDataControl;
 5: import jp.ne.mki.wedge.run.interfaces.DataInterface;
 6: import jp.ne.mki.wedge.run.interfaces.DcRequest;
 7:
 8: public class SimpleDeleteDc extends SqlDataControl {
 9:
10:     protected DataInterface[] executeSql(DcRequest req) throws SQLException {
11:         int updatedRecordCount = 0;
12:         Statement stmt = null;
13:
14:         try {
15:             stmt = req.createStatement();
16:             updatedRecordCount = stmt.executeUpdate(req.getSql());
17:             req.setUpdateErrorLine(0);
18:         } catch (SQLException ex) {
19:             req.setUpdateErrorLine(1);
20:             throw ex;
21:         } finally {
22:             req.setUpdateTargetCount(0);
23:             req.setDbAccessCount(1);
24:             req.setDbUpdatedCount(updatedRecordCount);
25:
26:             req.closeDbObject(stmt);
27:         }
28:
29:         return null;
30:     }
31: }

3-2. 出力アイテムがないマッパークラスのコーディング(更新処理)

次に入力アイテムを処理するマッパークラスのコーディングについて説明します。
ここでは例として、次のような

UPDATE WORKTABLE SET NAME=? WHERE ID=?
といった、指定された条件に該当するレコードに対してだけ、値を更新する SQL を実行します。
上記の SQL を処理するために、先ほどのコーディングと比べて、入力アイテムの処理が追加されています。
 1: package samples;
 2: import java.io.IOException;
 3: import java.sql.PreparedStatement;
 4: import java.sql.SQLException;
 5: import java.text.ParseException;
 6: import jp.ne.mki.wedge.run.db.dc.SqlDataControl;
 7: import jp.ne.mki.wedge.run.interfaces.DBDataConvertInterface;
 8: import jp.ne.mki.wedge.run.interfaces.DataInterface;
 9: import jp.ne.mki.wedge.run.interfaces.DcRequest;
10:
11: public class SimpleUpdateDc extends SqlDataControl {
12:
13:     protected DataInterface[] executeSql(DcRequest req) throws SQLException, ParseException, IOException {
14:         PreparedStatement stmt = null;
15:         int colCount = req.getColumns();
16:         int rowCount = req.getRows();
17:         int updatedCount = 0;
18:         int updatedRecordCount = 0;
19:         int errorRowNo = 0;
20:
21:         try {
22:             DataInterface[] inItemArray = req.getInputRecordArray();
23:             DBDataConvertInterface[] dbCvIn = req.getInDbCvClassArray();
24:             stmt = req.prepareStatement();
25:             // stmt = req.prepareStatement(req.getSql());
26:
27:             for (int ii = 0; ii < rowCount; ii++) {
28:                 errorRowNo++;
29:
30:                 for (int jj = 0; jj < colCount; jj++) {
31:                     String data = inItemArray[jj].getString(ii);
32:                     if (dbCvIn[jj] instanceof DBDataConvert) {
33:                         ((DBDataConvert) dbCvIn[jj]).setObject(stmt, jj + 1, data);
34:                     } else {
35:                         dbCvIn[jj].setData(stmt, jj + 1, data);
36:                     }
37:                 }
38:
39:                 int count = stmt.executeUpdate();
40:                 if (count > 0) {
41:                     updatedCount++;
42:                     updatedRecordCount += count;
43:                 }
44:             }
45:             req.setUpdateErrorLine(0);
46:         } catch (SQLException ex) {
47:             req.setUpdateErrorLine(errorRowNo);
48:             throw ex;
49:         } finally {
50:             req.setUpdateTargetCount(rowCount);
51:             req.setDbAccessCount(updatedCount);
52:             req.setDbUpdatedCount(updatedRecordCount);
53:
54:             req.closeDbObject(stmt);
55:         }
56:
57:         return null;
58:     }
59: }

3-3. 入力アイテムがないマッパークラスのコーディング

次に、全件検索の SQL を例にとり、出力アイテムを使ったマッパークラスのコーディングについて説明します。
ここでは例として次のような SQL を実行します。

SELECT ID, NAME FROM WORKTABLE
ID, NAME という 2つのカラムが検索対象となっていますので、この 2つのカラムを出力アイテムにセットするためのコーディングが追加されています。
 1: package samples;
 2: import java.io.IOException;
 3: import java.sql.ResultSet;
 4: import java.sql.SQLException;
 5: import java.sql.Statement;
 6: import jp.ne.mki.wedge.run.db.dc.SqlDataControl;
 7: import jp.ne.mki.wedge.run.interfaces.DBDataConvertInterface;
 8: import jp.ne.mki.wedge.run.interfaces.DataInterface;
 9: import jp.ne.mki.wedge.run.interfaces.DcRequest;
10:
11: public class SimpleQueryAllRecordsDC extends SqlDataControl {
12:
13:     protected DataInterface[] executeSql(DcRequest req) throws SQLException, IOException {
14:         DataInterface[] outItemArray = null;
15:         int readRowCount = 0;
16:         Statement stmt = null;
17:         ResultSet rs = null;
18:
19:         try {
20:             stmt = req.createStatement();
21:             rs = stmt.executeQuery(req.getSql());
22:             int outColumnCount = rs.getMetaData().getColumnCount();
23:             outItemArray = createOutputDataArray(outColumnCount);
24:             DBDataConvertInterface[] dbCvOut = req.getOutDbCvClassArray();
25:             while (rs.next()) {
26:                 for (int ii = 0; ii < outColumnCount; ii++) {
27:                     String data;
28:                     if (dbCvOut[ii] instanceof DBDataConvert) {
29:                         data = ((DBDataConvert) dbCvOut[ii]).getObject(rs, ii + 1);
30:                     } else {
31:                         data = dbCvOut[ii].getData(rs, ii + 1);
32:                     }
33:                     outItemArray[ii].addString(data);
34:                 }
35:                 readRowCount++;
36:             }
37:         } finally {
38:             req.setDbAccessCount(readRowCount);
39:             req.closeDbObject(rs);
40:             req.closeDbObject(stmt);
41:         }
42:
43:         return outItemArray;
44:     }
45: }

3-4. 入力アイテム、出力アイテムを使ったマッパークラスのコーディング

次に、絞込み条件付き検索の SQL を例にとり、入力アイテム出力アイテムを使ったマッパークラスのコーディングについて説明します。
ここでは例として次のような SQL を実行します。

SELECT NAME FROM WORKTABLE WHERE ID=?
ここでは ID カラムに検索条件を設定し、NAME カラムのデータを検索しますので、マッパークラスは、入力アイテムをセットし、出力アイテムを受け取る処理を行います。
条件指定項目の行数だけ検索処理を繰り返し、検索結果の行数だけデータを取得し、検索結果の行に含まれる項目数だけ出力アイテムに値をセットしますので、下記例では 3重の入れ子になったループを行っています。
 1: package samples;
 2: import java.io.IOException;
 3: import java.sql.PreparedStatement;
 4: import java.sql.ResultSet;
 5: import java.sql.SQLException;
 6: import java.text.ParseException;
 7: import jp.ne.mki.wedge.run.db.dc.SqlDataControl;
 8: import jp.ne.mki.wedge.run.interfaces.DBDataConvertInterface;
 9: import jp.ne.mki.wedge.run.interfaces.DataInterface;
10: import jp.ne.mki.wedge.run.interfaces.DcRequest;
11:
12: public class SimpleQueryDC extends SqlDataControl {
13:
14:     protected DataInterface[] executeSql(DcRequest req) throws SQLException, ParseException, IOException {
15:
16:         int rowCount = req.getRows();
17:         DataInterface[] outItemArray = null;
18:         int readRowCount = 0;
19:
20:         DataInterface[] inItemArray = req.getInputRecordArray();
21:         PreparedStatement stmt = null;
22:
23:         try {
24:             int colCount = req.getColumns();
25:             DBDataConvertInterface[] dbCvIn = req.getInDbCvClassArray();
26:             DBDataConvertInterface[] dbCvOut = req.getOutDbCvClassArray();
27:             stmt = req.prepareStatement();
28:
29:             for (int ii = 0; ii < rowCount; ii++) {
30:                 for (int jj = 0; jj < colCount; jj++) {
31:                     String data = inItemArray[jj].getString(ii);
32:                     if (dbCvIn[jj] instanceof DBDataConvert) {
33:                         ((DBDataConvert) dbCvIn[jj]).setObject(stmt, jj + 1, data);
34:                     } else {
35:                         dbCvIn[jj].setData(stmt, jj + 1, data);
36:                     }
37:                 }
38:
39:                 ResultSet rs = null;
40:
41:                 try {
42:                     rs = stmt.executeQuery();
43:                     int outColumnCount = rs.getMetaData().getColumnCount();
44:                     if (outItemArray == null) {
45:                         outItemArray = createOutputDataArray(outColumnCount);
46:                     }
47:
48:                     while (rs.next()) {
49:                         for (int jj = 0; jj < outColumnCount; jj++) {
50:                             String data;
51:                             if (dbCvOut[jj] instanceof DBDataConvert) {
52:                                 data = ((DBDataConvert) dbCvOut[jj]).getObject(rs, jj + 1);
53:                             } else {
54:                                 data = dbCvOut[jj].getData(rs, jj + 1);
55:                             }
56:                             outItemArray[jj].addString(data);
57:                         }
58:                         readRowCount++;
59:                     }
60:                 } finally {
61:                     req.closeDbObject(rs);
62:                 }
63:             }
64:         } finally {
65:             req.setDbAccessCount(readRowCount);
66:             req.closeDbObject(stmt);
67:         }
68:
69:         return outItemArray;
70:     }
71: }

3-5. PL/SQLと配列のデータを受け渡しするマッパークラスのコーディング

ここでは表タイプを使って、PL/SQL に配列を渡し、結果も配列で受け取るためのサンプルについて説明します。

注意注意
下記サンプルは Oracle 8i を動作を確認しています。
基本的には Oracle 9i でも動作しますが、このサンプルは Oracle に依存したコーディングをしているため、Oracle のバージョンや JDBC ドライバのバージョンによってはうまく動作しないケースも考えられます。
また、下記コードを実行する場合は、あらかじめDBServerの CLASSPATH に Oracle 社から提供される NLS 対応 JDBC ドライバを追加しておく必要があります。
参考例Example...
  • setenv.bat
    set RUNJDBC=c:\jdbc\nls_charset12.zip;c:\jdbc\classes12.zip
  • setenv.sh
    RUNJDBC=/opt/jdbc/nls_charset12.zip:/opt/jdbc/classes12.zip
  • Common.ini
    RUNJDBC=c:\jdbc\nls_charset12.zip;c:\jdbc\classes12.zip


このサンプルでは、PL/SQL と配列を受け渡しするために、次のような作業を行い、
  1. 表タイプを作成
  2. 表タイプを使ったパッケージを作成
  3. 表タイプを使ったパッケージ本体を作成
作成したパッケージに記述されたプロシージャを、Java から呼び出す処理を行っています。
Java からプロシージャを呼び出す時に、あらかじめ作成しておいた表タイプを指定します。
 1: package samples;
 2: import java.sql.Array;
 3: import java.sql.CallableStatement;
 4: import java.sql.SQLException;
 5: import java.sql.Types;
 6:
 7: import oracle.sql.ARRAY;
 8: import oracle.sql.ArrayDescriptor;
 9: import jp.ne.mki.wedge.run.db.dc.SqlDataControl;
10: import jp.ne.mki.wedge.run.interfaces.DataInterface;
11: import jp.ne.mki.wedge.run.interfaces.DcRequest;
12:
13: public class SimpleSpDC extends SqlDataControl {
14:     private final static String TABLE_TYPE_NAME = "STRINGLIST";
15:     private final static String SQL = "{call array.ReverseStringList(?,?)}";
16:
17:     protected DataInterface[] executeSql(DcRequest req) throws SQLException {
18:         CallableStatement stmt = null;
19:         DataInterface[] inItemArray = req.getInputRecordArray();
20:         String[] inStringArray = null;
21:         DataInterface[] outItemArray = null;
22:
23:         try {
24:             int itemSize = inItemArray[0].getSize();
25:             inStringArray = new String[itemSize];
26:             for (int ii = 0; ii < itemSize; ii++) {
27:                 String data = inItemArray[0].getString(ii);
28:                 inStringArray[ii] = data;
29:             }
30:
31:             stmt = req.prepareCall(SQL);
32:             ArrayDescriptor desc = ArrayDescriptor.createDescriptor(TABLE_TYPE_NAME, stmt.getConnection());
33:             ARRAY arrayIn = new ARRAY(desc, stmt.getConnection(), inStringArray);
34:             stmt.setArray(1, arrayIn);
35:             stmt.registerOutParameter(2, Types.ARRAY, TABLE_TYPE_NAME);
36:             stmt.execute();
37:
38:             Array arrayOut = stmt.getArray(2);
39:             String[] stringArray = (String[]) arrayOut.getArray();
40:             outItemArray = createOutputDataArray(1);
41:             int outSize = stringArray.length;
42:             DataInterface di = outItemArray[0];
43:             for (int ii = 0; ii < outSize; ii++) {
44:                 di.addString(stringArray[ii]);
45:             }
46:         } finally {
47:             req.closeDbObject(stmt);
48:         }
49:
50:         return outItemArray;
51:     }
52: }

 

4. マッパークラスを使うには

実装したマッパークラスを使うためには、いくつかの設定が必要となります。
ここでは、SimpleQueryDC を使う場合の手順について説明します。

4-1. マッパークラス作成手順

まずマッパークラスのソースをコンパイルして class ファイルを生成します。
コンパイルを行うには、IDE(統合開発環境)を使う方法と、JDK を使う方法がありますが、IDE の場合は、IDE のツールによって設定が異なりますので、ここでは JDK を使ってコンパイルを行う方法について説明します。
コンパイルを行うには、CLASSPATH に wedge-common-1.4.0.jar, wedge-common-server-1.4.0.jar, wedge-optional-1.4.0.jar, wedge-run-common-1.4.0.jar, wedge-run-data-1.4.0.jar, wedge-run-server-1.4.0.jar を設定します。

参考例Example...
[Java 6 を使って SimpleQueryDC.java をコンパイルする場合]
コマンドプロンプトを開いて、次のコマンドを実行します。
set JAVA_HOME=c:\jdk1.7.0_13
set PATH=%JAVA_HOME%\bin;%PATH%
set CLASSPATH=c:\Tomcat6.0\webapps\webtribe\WEB-INF\lib\wedge-common-1.4.0.jar;\WEB-INF\lib\wedge-common-server-1.4.0.jar;\WEB-INF\lib\wedge-optional-1.4.0.jar;\WEB-INF\lib\wedge-run-common-1.4.0.jar;\WEB-INF\lib\wedge-run-data-1.4.0.jar;\WEB-INF\lib\wedge-run-server-1.4.0.jar



javac SimpleQueryDC.java
コンパイルが通ると、SimpleQueryDC.class というファイルが作成されます。

4-2. マッパークラス登録

  1. ToolClientを起動し、Operation Management Console(POMC)のマッパークラスタブを開きます。


    ●図1. [マッパークラス]
    images/dcbase.png
  2. 検索DC を選択します。


    ●図2. [検索SQLマッパー]
    images/querydc.png
  3. 次の内容で、作成したマッパークラスを登録し、保存します。
    論理名 シンプルクエリDC
    物理名 SimpleQueryDC
    クラス名 samples.SimpleQueryDC



    ●図3. [マッパークラス登録]
    images/registdcbase.png

4-3. マッパークラスを使った SQLマッパー を作成

  1. Data Management ConsoleのSQLマッパータブを開き、「ワークテーブル」の「QUERY」を選択します。


    ●図4. [DMC登録]
    images/dmcdc1.png
  2. 「SELECT NAME FROM WORKTABLE WHERE ID=?」という SQL を実行しますので、下記のようなSQLマッパーを登録します。
    補足補足
    事前に ID, NAME カラムをデータ型タブに、WORKTABLE の構造をレコードタブとテーブルタブに登録しておく必要があります。
    今回使った WORKTABLE は次のように定義しました。
    create table worktable (
        id      number(7) not null,
        name    varchar2(16),
        version varchar2(16)
    );
    
    alter table worktable add constraint pk_worktable primary key (id);
    
    insert into worktable values(1,'Sparkler','1.1.4');
    insert into worktable values(2,'Pumpkin','1.1.5');
    insert into worktable values(3,'Abigail','1.1.6');
    insert into worktable values(4,'Brutus','1.1.7');
    insert into worktable values(5,'Chelsea','1.1.8');
    insert into worktable values(6,'Playground','1.2');
    insert into worktable values(7,'Cricket','1.2.2');
    insert into worktable values(8,'Kestrel','1.3');
    insert into worktable values(9,'Ladybird','1.3.1');
    insert into worktable values(10,'Merlin','1.4.0');
    insert into worktable values(11,'Hopper','1.6.0');
    insert into worktable values(12,'Mantis','1.4.2');
    insert into worktable values(13,'Tiger','1.5.0');
    insert into worktable values(14,'DragonFly','1.5.1');
    insert into worktable values(15,'Mustang','1.6.0');
    commit;
    



    ●図5. [定義入力タブ]
    images/dmcdc2.png


    ●図6. [入出力データセットタブ]
    images/dmcdc3.png
  3. 作成したSQLマッパーを使ったトランザクションもServer Application Management Consoleに登録しておきます。

4-4. マッパークラスのクラスファイルをコピー

コンパイルしたマッパークラスのクラスファイル(SimpleQueryDC.class)をコピーします。

参考例Example...
SimpleQueryDC.class を WEB-INF/classes/samples にコピー
Windows の場合
md c:\Tomcat6.0\webapps\webtribe\WEB-INF\classes
md c:\Tomcat6.0\webapps\webtribe\WEB-INF\classes\samples
copy SimpleQueryDC.class c:\Tomcat6.0\webapps\webtribe\WEB-INF\classes\samples
Linux の場合
mkdir -p /opt/jakarta-tomcat-6.0/webapps/webtribe/WEB-INF/classes/samples
cp SimpleQueryDC.class /opt/jakarta-tomcat-6.0/webapps/webtribe/WEB-INF/classes/samples

4-5. クラスパスに追加

classes ディレクトリを CLASSPATH に追加します。

4-6. DBServer 再起動

DBServerを再起動し、CLASSPATH の設定を有効にします。

4-7. トレースログを確認

トランザクションテスト等で作成したSQLマッパーを実行し、動作を確認します。
トレースログで、作成したSQLマッパーが呼び出されているかどうか確認することができます。


●図7. [トランザクションテスト]
images/trantest.png

[トレースログ]

[2004/05/10 19:49:51]
*DC Class
 [samples.SimpleQueryDC]
*SQL
 [SELECT NAME FROM  WORKTABLE WHERE  ID = ? ]
*Input DB convert class
 []
*Input data
[12]
*Output DB convert class
 []
*Output data
 [Mantis]