作成日: 2004/10/23

サーブレットとトランザクション

概要

サーブレットから JTA のトランザクションを使いたくて、色々探してみました。
機能的には JDBC のトランザクションで十分なのですが、 再利用したいデータアクセス関連のクラスが、EJB の CMP 上で使うのを前提に、 JNDI リソースからデータソースを取得して使い終わったらクローズするというつくりになっており、 これに、データソースを引き回す処理を追加するのがめんどうくさく (この理由が一番大きい…) JTA のトランザクションを利用することにしました。
いざ探してみると JTA をサポートしたミドルウェアというのは結構あるのですが、 オープンソースなもので私がみつけられたのは JOTM (http://jotm.objectweb.org/) と Tyrex (http://tyrex.sourceforge.net/) だけでした。

サーブレットコンテナには Tomcat (http://jakarta.apache.org/tomcat/) を使う予定だったため、 Tomcat 4 のドキュメントにも載っていた Tyrex を試そうとしたのですが、 Tomcat 5.0.28 + Tyrex 1.0.1 の組み合わせでは、トランザクション制御に使う javax.transaction.UserTransaction を取得するためのファクトリクラスを Tyrex 内に見つけることができず、 断念しました。
いろいろ情報をあさっていると、Tomcat 4 の時点では使えたようで、また Tomcat にバンドルされていたような気配がうかがえるのですが、Tomcat 5 のドキュメントでは Tyrex の記述はなくなっていました (日本語のドキュメントには載っているのが不可解ですが…)。 結局、このあたりの事情が不明なまま、使いたいけど使えないので、あきらめることにしました。

次に試したのが JOTM です。Tomcat 5.0.28 + JOTM 1.5.3 の組み合わせで試しました。 JOTM は付属のドキュメントに Tomcat での設定方法が書かれており、その通りに設定すると動作させることができました。
ただ、試しにと思いトランザクションを連続して発行してみると (UserTransaction#begin() してレコードを 3 つ INSERT し、 最後に UserTransaction#commit() する)、私の設定/環境では 300 回くらい繰り返したあたりで、 たまにコネクションを取得できなくなるエラーが発生し始めました。 調べてみると、解放されていないコネクションがあるように見受けられます。
また、挿入したレコードがロックされたままになる現象も発生し (UserTransaction#commit() してもエラーにはならないため、 プログラムからは検知できていない)、一定時間ごとに起動されるコネクションプールの掃除が終わるまでは、 データベースのテーブル上に反映されないという現象も起きました。
設定/環境が悪いという可能性もあるので、なんともいえないのですが、 少なくとも業務で使用するのはちょっと怖いなぁと思い、後ろ髪引かれる思いで断念しました。

で、最終的に JBoss を使ってみることにしました。「鶏を割くに牛刀を用う」と突っ込まれそうなのですが、 負荷をかけても問題なく動作するし、設定も JTA + サーブレットコンテナとしてのみ使うだけなら、 ややこしくもなく、軽かったので、「これはいい!」と飛びついたのです。
なお、以下では Windows XP SP2、JBoss 3.2.6、JDK 1.4.2_06 を使っています。また、JBoss はデフォルトサーバのみ利用しています。

JBoss のインストール/起動

事前に JDK をインストールしておく必要があります。
また、JBoss (http://www.jboss.org/) は JBoss 3.2.6 を使いました。
JBoss のインストールはダウンロードしてきたファイルを展開するだけです。 私は C:\Bin 以下に展開し、C:\Bin\jboss-3.2.6 というディレクトリができました。
JBoss を正しく動作させるために環境変数を設定する必要があるのかどうかは、実はよくわかりません。 環境変数なしでも一応起動はできました。
ちなみに、私の場合 JBOSS_HOME という名前で C:\Bin\jboss-3.2.6 を設定してありますが、 これはネット上の情報に従っただけで、本当に必要なのかは自信がありません。
ただ、以下の説明では C:\Bin\jboss-3.2.6 を %JBOSS_HOME% と表記していますので適宜置き換えてください。

インストールが済んだら起動してみます。
コマンドプロンプトを開き %JBOSS_HOME%\bin\run.bat を実行します。 コンソールの最後に、

12:18:16,520 INFO  [Server] JBoss (MX MicroKernel) [3.2.6 (build: CVSTag=JBoss_3_2_6 date=2004101401
06)] Started in 14s:421ms

のように表示されれば起動は完了です。
※デフォルトサーバが起動します。

また、終了は %JBOSS_HOME%\bin\shutdown.bat -S を実行します。 コンソールの最後に、

Shutting down
Shutdown complete
Halting VM

のように表示されれば JBoss は終了しています。

データソースの定義と設定

データベースには MySQL を使いました。これも別途インストールしてください。
データソース定義のテンプレートが %JBOSS_HOME%\docs\examples\jca\mysql-ds.xml に 用意されています。デフォルトサーバを使う場合、これを %JBOSS_HOME%\server\default\deploy にコピーして修正します。

テンプレートは各データベース毎に用意されており、使用するデータベースに合致するテンプレートを %JBOSS_HOME%\server\default\deploy にコピーし、内容を自分の環境にあわせて設定すれば完了です。
私の環境では、localhost の test という名のデータベースにユーザ kok、パスワード kok で接続しますので、 以下のようになりました。
なお、プログラムから参照するための jndi-name を "jdbc/KOK" としていますが、 プレフィックス "jdbc/" をつけずそのまま "KOK" などとしても構わないようです。というか、 テンプレートのサンプルにはプレフィックスが書かれていないので、そちらが正統なのかもしれません。

<?xml version="1.0" encoding="UTF-8"?>

<datasources>
  <local-tx-datasource>
    <jndi-name>jdbc/KOK</jndi-name>
    <connection-url>jdbc:mysql://localhost/test</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>kok</user-name>
    <password>kok</password>
  </local-tx-datasource>

</datasources>
jndi-name:このデータソースをプログラムから参照する際の名前を設定します。
connection-url:データベースへの接続文字列を設定します。
driver-class:JDBC ドライバのクラス (java.sql.Driver の実装クラス) 名を設定します。
user-name:ユーザ名を設定します。
password:パスワードを設定します。

データソース定義ができあがったら、JDBC ドライバをコピーします。
私は MySQL (http://www.mysql.com/) のサイトから 現時点での最新版の JDBC ドライバ (3.0.15) をダウンロードしてきました。
ダウンロードしたファイル (mysql-connector-java-3.0.15-ga.zip) を展開し、mysql-connector-java-3.0.15-ga-bin.jar を取り出し、%JBOSS_HOME%\server\default\lib にコピーします。

サーブレットからトランザクションを使う

サーブレットからトランザクションを制御するには、javax.transaction.UserTransaction を使用します。 UserTransaction は以下のような構文で取得できます。

import javax.naming.InitialContext;
import javax.transaction.UserTransaction;

...

    InitialContext ctx = new InitialContext();
    UserTransaction utx = (UserTransaction) ctx.lookup("/UserTransaction");

トランザクションの開始には UserTransaction#begin() を使い、 トランザクションのコミット/ロールバックには、それぞれ UserTransaction#commit()/UserTransaction#rollback() を使います。
構文例としては、以下のような感じでしょうか。

try {
    // トランザクションを開始する。
    utx.begin();

    ...

    // トランザクションをコミットする。
    utx.commit();

} catch (Exception e) {
    ...
    // トランザクションをロールバックする。
    utx.rollback();
}

UserTransaction#begin() を呼び出してから UserTransaction#commit()/UserTransaction#rollback() のどちらかを呼び出してトランザクションを完了するまでの間に行うデータベースへのアクセスは、 すべて同一のトランザクションとして扱われます。
データソースは以下のような構文で取得できます。なお、lookup() の引数にはデータソース定義でつけた名前を指定します。

import javax.naming.InitialContext;
import javax.sql.DataSource;

...

    InitialContext ctx = new InitialContext();
    DataSource ds = (DataSource) ctx_.lookup("java:/jdbc/KOK");

データベースへアクセスするには、上のデータソースからコネクションを取得して使います。 データベースへのアクセスが済んだら、コネクションを閉じます。

import javax.sql.DataSource;
import java.sql.Connection;

...

    Connection conn = null;

    try {
        // コネクションを得る。
        conn = ds.getConnection();

        // コネクションを使用してデータベースへアクセスする(更新処理など)。
        ...


    } catch (Exception e) {
        ...
    } finally {
        if (conn != null) {
            ...
            // コネクションを閉じる。
            conn.close();
        }
    }

JDBC のトランザクションだとコネクションごとにトランザクションの制御を行うことになるため、 コネクションを閉じるとトランザクションも終了してしまうのですが、JTA を利用した場合、 上記の UserTransaction ごとに制御が行われるため、コネクションを閉じてもトランザクションは終了しません。 そのため、UserTransaction#commit() または UserTransaction#rollback() を呼び出して トランザクションを完了するまでの間は、何度、コネクションを取得し、データベースにアクセスした後、 コネクションを閉じたとしても、すべて同一のトランザクションとして扱われます。

業務アプリケーションなどでデータベースを利用する場合、データアクセス専用のクラスを作ることが多いかと思いますが、 アクセスクラスが複数ある場合、JDBC のトランザクションを共有するためには、 コネクションを引き回すための仕組みを作らなくてはなりません。ですが、 JTA を使えば、このような仕組みを作成する必要がなくなります (楽できる?)。

JBoss でのデプロイ/アンデプロイ

アプリケーションサーバなどにアプリケーションを配置することをデプロイ、削除することをアンデプロイと呼ぶようです。 JBoss では、作成した war ファイルや ear ファイルを特定のディレクトリにコピーするだけで、 自動的にデプロイが行われます。アンデプロイするには、そのファイルを削除するだけで構いません。
デフォルトサーバを利用する場合、war ファイルや ear ファイルを %JBOSS_HOME%\server\default\deploy にコピーします。

15:25:04,376 INFO  [TomcatDeployer] deploy, ctxPath=/TranTest, warUrl=file:/C:/Bin/jboss-3.2.6/serve
r/default/tmp/deploy/tmp22564TranTest.war/

のように表示されれば、デプロイ完了です。また、削除時には、

15:26:54,905 INFO  [TomcatDeployer] undeploy, ctxPath=/TranTest, warUrl=file:/C:/Bin/jboss-3.2.6/ser
ver/default/tmp/deploy/tmp22564TranTest.war/

のようにアンデプロイされたことが表示されます。

サンプルソースとか
JBossLaunch.zip (1.37 KB)
JBoss を起動/終了する VB スクリプトです。デスクトップ上にショートカットを作成して使ってください。
なお、ユーザの環境変数 JBOSS_HOME に JBoss をインストールしたディレクトリが設定されている必要があります。
また、アイコンは JBoss サイトの favicon.ico を IE のキャッシュから取り出したものです。 ショートカットが VB スクリプトのアイコンのままだと味気ないので、 これを使うと JBoss のアイコンらしくていいかなぁと思って添付してあります。
※アイコンに関しては JBoss Group に著作権があります。
TestServlet.zip (5.02 KB)
トランザクションを利用して、テーブルにレコードを 3 つ挿入し、その後、 コミットまたはロールバックするサーブレットのサンプルを作ってみました。
JBoss 起動後、TranTest.war を %JBOSS_HOME%\server\default\deploy にコピーすれば動作します。 その後、ブラウザで http://localhost:8080/TranTest/TranTest へアクセスしてみてください。 レコードを 3 つ挿入して、ロールバックします。また、http://localhost:8080/TranTest/TranTest?commit=on とパラメータを与えることで、レコードを 3 つ挿入して、コミットします。
TranTest.war 内にはソースも入っていますので、展開して参照してみてください。
なお、TranTest.war を動作させるには、別途 MySQL をインストールし、 test という名のデータベースを作成 (たしかデフォルトで存在してたと思うが定かでない)、 ユーザ kok、パスワード kok で test にアクセスできるようにし、 TESTTBL という名のテーブルを作成する必要があります (ユーザ kok/kok、TESTTBL の 作成文は TESTTBL.sql として添付してあります)。
なお、余談ですが、MySQL で普通に作成したテーブルでは、 トランザクションの制御ができません。 ご自分でテーブルを作って試される場合、トランザクションをサポートする InnoDB または BDB などの種類のテーブルで行ってください (TESTTBL.sql では、最後に ALTER で InnoDB にしています) 。 詳しくは MySQL のドキュメントをご覧いただくようお願いします。