レベル: 中級 Neal Sanche (neal@nsdev.org), Java developer and author
2005年 10月 11日 どんなJ2EE(Java™ 2 Platform, Enterprise Edition)アプリケーションにとっても、トランザクションは必要な部分であり、データベースにアクセスする場合には頻繁にトランザクションを使用します。実際のところ、予測不可能なフェールの際にもデータ整合性を維持するためには、トランザクションは致命的に重要です。この記事では、J2EEトランザクションに関する重要事項について、またApache Geronimoアプリケーション・サーバーでのトランザクションの使い方について、Java開発者であり、developerWorksに定期的に寄稿しているNeal Sancheが解説します。ここではトランザクションをデモするために、Transaction Demoというサンプル・プログラムを使います。これは星の名前に関するデータベースと対話動作する、単純なメニューを持ったプログラムです。
トランザクションの概要
Geronimo用のアプリケーションを書く場合には、ほとんど必ずと言ってよいほど、トランザクションを作成、処理する必要が出てきます。Geronimoでは多くの場合、つまりデータベース・レコードの操作やCMR(container-managed relation)に関するオペレーション、JMS(Java Message Service)メッセージの送信などには、いやでもトランザクションを使わざるを得ません。そうしないとGeronimoはエラーを返すのです。
 |
トランザクションの定義
「トランザクション」は、エラーがある場合には取り消し可能な、一連のアトミック・アクションです。どれか1つでもアクションがフェールした場合には、どのアクションも実行されません。
「CMR(container-managed relation)」はJ2EEの概念であり、一連のエンティティーの自動パーシスタンスと、それらの間の関係を提供するものです。例えば、発注のレコードには何行ものアイテムがあるものですが、Geronimoコンテナーはこれを、(すべてのSQLステートメントが見えない所で実行されるように)自動的に処理します。
CMT(container-managed transaction)はGeronimoコンテナーによって自動的に提供されるトランザクションであり、Geronimoコンテナーによって管理されます。これを処理するためのコードを書く必要はありません。
EJB(Enterprise JavaBeans™)コンテナーはCMP(container-managed persistence)によって、パーシスタンス状態を自動管理します。CMPは高いパフォーマンスを提供する一方、コードは少なくて済みます。
|
|
Geronimoアプリケーションでトランザクションを使う上で重要となる詳細事項に慣れるために、ここではTransaction Demoという、Geronimoアプリケーション・サーバーにデプロイするために作られた小さなアプリケーションを使います。ここで行う開発は、Geronimo M5(5番目のマイルストーン・リリース)の初期のリリースに基づいています。(参考文献には、Apache Software FoundationサイトからGeronimoアプリケーション・サーバーの最新リリースをダウンロードするためのリンクがあります。)
J2EEアプリケーションでCMP中にデータベースをアクセスする場合には、必ずトランザクションを使います。エンティティーbeanの作成中、あるいは更新、削除中にエラーが発生すると、それ以前のデータベースの状態が復元され、問題が起きたことがユーザーに通知されます。ここで問題になるのは、データの整合性です。トランザクションは、潜在的に不完全なオペレーションをすべて取り消すことによって、データベースの中にあるデータの整合性を保護するのです。
J2EEアプリケーションの場合、自分の独自コードの中でトランザクションを作成し、そのライフタイムを管理するか(UserTransactionクラスのインスタンスを取得、利用します。Directly remove Sol(太陽を直接削除する)のセクションを見てください)、あるいはCMTを利用することによって、EJBコンテナーがトランザクションの作成、管理を行ってくれるのに任せます。以下では、この両方の方法をGeronimoアプリケーション・サーバーで使うための詳細を解説します。
先に触れた通り、トランザクションは単にデータベースのためだけのものではありません。J2EE仕様の鍵となる部分では、JMS(Java Message Service)の中でのトランザクションの使い方を記述しています。JMSはMDB(message-driven bean)の致命的コンポーネントであり、トランザクションの中で使われます。何らかの例外のために送達できなかったメッセージはトランザクションをロールバックし、そのメッセージが再送達されるようにするのです。GeronimoはActiveMQ JMSサービスを埋め込んでいるため、手軽にMDBを作成することができます。また、Geronimoコンソールを使ってSystemJMSコンフィギュレーションを開始し、JMSサーバーをコンフィギュレーションすることさえできるのです。ただし、それはこの記事の範囲外です。(この件に関しては参考文献を見てください。)
この記事では、トランザクションの使い方の範囲として、Webアプリケーション内部から使う場合と、CMTを使ってステートレス・セッションbeanインスタンスを通して使う場合について説明します。また、フェールしたトランザクションから優雅に回復するための例についても説明します。
サンプル・プログラム
この記事でトランザクションのデモに使用するサンプル・プログラム、Transaction Demoは、Strutsアプリケーションです。このアプリケーションは、Geronimoアプリケーション・サーバーにデプロイするために必要な、すべてのデプロイメント・プランを持っています。また単純なメニューを持っており、このメニューを使って、星(star)に関する基本的なデータベースと対話動作することができます(ここでstarと言っているのは太陽などの天体に関するもので、ハリウッド映画のスターのことを言っているのではありません)。このプログラムによって、Geronimoプログラムの中でトランザクションを使う上で鍵となる、数多くの概念をデモすることができます。
Transaction Demoを実行するためには、まずGeronimoアプリケーション・サーバーをソースコードからビルドする必要があります。サンプル・プログラム用のMavenビルド・スクリプトは、ビルド時にGeronimoのビルド製品の多くが利用可能でないと完了しません。また、sourceforge.netからXDoclet 1.2.3をダウンロードし(参考文献にリンクがあります)、さらに、欠けているバイナリーをすべてMavenリポジトリーにインストールする必要があります。(もしIBiblio MavenリポジトリーにXDoclet 1.2.3バイナリーが全部含まれているのであれば、このステップは必要ありません。この記事の執筆時点では、少なくとも1つのバイナリーが欠けていました。)
ビルドが正しく動作するようになったら、できあがったターゲット・ディレクトリーの中に、transactiondemo-ejb.sqlファイルがあるはずです。これが、このアプリケーションに対するSQLスキーマです。下記に示すステップを完了すれば、このスキーマを、Geronimoの中に組み込まれているデータベース、Apache Derbyの中にインストールすることができます。
まず、次のコマンドラインでGeronimoサーバーを起動します。
これでGeronimoサーバーが起動し、また、新しいGeronimoコンソール(サーバーとアプリケーション・デプロイ用の一連のGeronimoサービスを管理します)が起動します。(注意: これは、GeronimoのM5バージョンを使っている場合です。これよりも前のバージョンでは最小限のサーバーしか起動せず、このサーバーにデプロイするためには、さらに他のサービスを起動する必要があります。)
次に、Webブラウザーでhttp://localhost:8080/consoleに行き、コンソールに接続します。そうすると、コンソールのwelcome画面が見えるはずです(図1)。デフォルトのユーザー名、systemと、パスワードmanagerでログインします。
図1. Geronimoコンソールのwelcome画面
ログインしたら、左側のナビゲーション・バーを使ってAll Configurationsを選択します。Installed Applicationsのリストを見てorg/apache/geronimo/SystemDatabaseコンフィギュレーションを確認し、その隣にあるStartリンクをクリックして、このコンフィギュレーションを開始します(あるいは、このコンフィギュレーションが実行していることを確認します)。これでApache Derbyデータベース・エンジンが起動するので、これをコンフィギュレーションすることができます(図2)。デモ・アプリケーションをコンパイルし、実行させるためには、少なくとも次のコンフィギュレーションが起動している必要があります。
org/apache/geronimo/RuntimeDeployer
org/apache/geronimo/JettyRuntimeDeployer
org/apache/geronimo/SystemDatabase
|
図2. システム・データベースが起動した時の全コンフィギュレーション
次に、ナビゲーション・バーの一番下までスクロールし、DB Managerリンクを選択します。このポートレット(コンソール開発者はそのように呼びます)によって、単純な方法でシステム・データベースにSQLクエリーを行うことができ、また新しいデータベースを作成することができます。transactiondemo-ejb.sqlファイルからSQL Commandテキスト・エリアに、(drop tableステートメントなしに)SQLを入力し、Run SQLボタンをクリックします。(図3)。
図3. DB Managerを使ってアプリケーション・データベース・スキーマをインストールする
これによって、デモ・アプリケーションで必要なテーブルが作られます。データベースの中にあるテーブルを見るには、DB ManagerポートレットのDatabase Listセクションにある、View Tables選択の下にあるApplicationリンクをクリックします。当然のことですが、この時に、Geronimoコンソールの持っている他の素晴らしい機能も調べてみるべきでしょう。さて、ソース・ディレクトリーでMavenコマンドを実行すると、transactiondemoアプリケーションがインストールされます。そこにアクセスするには、URL http://localhost:8080/trasactiondemoに行きます。そうすると、次のような画面が見えるはずです(図4)。
図4. Transaction Demoアプリケーションの画面
画面の左側にあるメニュー・リンクは、様々な方法でトランザクションを使ってデータベース・コードをコールします。これらのメニュー項目の意味は下記の通りですが、より正確な機能の説明は、この記事の後の方で行います。
-
Clear(データベースをクリアーする):Starsデータベース・テーブルをクリアーします。
-
Add Records with Failure(フェール有りでレコードを追加する):幾つかのレコードを追加しますが、メソッドが終了前に例外を投げ、CMTは自動的にロールバックされます。
-
Add Records(レコードを追加する):例外を投げることなくレコードを追加します。
-
Directly Remove Sol(太陽を直接削除する):UserTransactionインスタンスを取得し、それをStrutsアクションの中で使うことによって、Web階層でのトランザクション処理をデモします。
これらのオプションのコードについては、この記事の後の方で説明します。
Clear(データベースをクリアーする)
Clearというメニュー項目を選択すると、データベース・レコードがクリアーされます。これによって、全く初めからデモをいじり直すことができます。このプログラムはデータベースをクリアーするために、自動的にCMTを開始するステートレス・セッションbeanを使います。TransactionDemoSessionBean.javaクラスにはclear() メソッド(リスト1)が含まれています。このメソッドは、要求に従って実体の繰り返しを行い、各レコードを削除します。これは単一のトランザクションとして行われるため、(恐らく下位レベルでは1つのstar.remove() に対して1つの(SQL)deleteステートメントとして変換されると思いますが)比較的高速なオペレーションである必要があります。
リスト1. TransactionDemoSessionBean.java clear() メソッド
/**
* Perform the first test.
* @throws NamingException
* @throws CreateException
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void clear() {
try {
Collection stars = StarUtil.getLocalHome().findAll();
Iterator i = stars.iterator();
while(i.hasNext()) {
StarLocal star = (StarLocal)i.next();
star.remove();
}
} catch (Throwable ex) {
ex.printStackTrace();
}
} |
このコードは、XDocletタグ、具体的には@ejb.transactionタグで装飾されています。このタグは、clear()メソッドへのエントリーに対してトランザクションが必要なことを規定します。XDocletタグは、ejb-jar.xmlデプロイメント記述子(リスト2)の中のエントリーに変換されます。このエントリーは、呼び出し側スレッドがトランザクションと既に関連付けられているのでなければ、CMTを作成するようにGeronimoに指示します。
リスト2. clear() メソッドに対するejb-jar.xmlの断片
<container-transaction>
<method>
<ejb-name>TransactionDemoSession</ejb-name>
<method-intf>Local</method-intf>
<method-name>clear</method-name>
<method-params>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
|
CMTの良いところは、CMTを開始したりロールバックしたり、あるいはコミットしたり、ということを気にせずに済むことです。コンテナーが、それらをすべて自動的に行ってくれるのです。例えば、データベースから全部の星のレコードを削除している最中に何らかの例外が発生すると、Geronimoはトランザクションのロールバックを開始するため、clear() メソッドの中では、どの削除も全くコミットされないことになります。次のセクションでは、このロールバックの振る舞いを説明します。
Add Records with Failure(フェール有りでレコードを追加する)
Add Records with Failureというメニュー項目では、オペレーションの最中に例外が発生した場合にトランザクションで何が起きるかをデモします。この前のセクションで触れた通り、CMTを持つとマーキングされたメソッドの期間中に例外が発生すると、そのトランザクションは自動的にロールバックされます。リスト3に示すコードでは、メソッドの最後で必ず例外が投げられるようになっており、その結果、強制的にロールバックが行われます。
リスト3. ロールバックをデモするために強制的に例外を起こすtest1() メソッド
/**
* Perform the first test. This test makes an attempt to
* add a number of stars, but then throws an exception.
*
* @throws NamingException
* @throws CreateException
* @throws IllegalStateException always
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void test1() throws CreateException, NamingException,
IllegalStateException {
StarLocalHome home = StarUtil.getLocalHome();
home.create("Sol");
home.create("Betelguese");
home.create("Andromeda");
throw new
IllegalStateException("Pretending that something"+
" bad happened.");
} |
このメソッドの最後には、必ず投げられるIllegalStateExceptionがあります。このトランザクションが動作すると、この例外によって全ての変更がロールバックされるため、データベースの中では、太陽(Sol)やベテルギウス(Betelguese)、アンドロメダなど、どの星も作られないはずです。ご覧の通り、実際にそうなっています。
Add Records(レコードを追加する)
Add Recordsというメニュー項目は、Add Records with Failureと全く逆のことをします。このメニュー項目は、例外条件が全く無い場合に起きること、つまりデータベースへのレコード挿入をデモします。リスト4では、コードによって投げられる例外が何も無い、という以外、ほとんど何も行われていません。ですからデータベースに何事もなければ、CMTはコミットを行い、リストの中にレコードが現れるはずです。テスト・アプリケーションの中での、このオペレーションの結果を図5に示します。ただし、このメニュー・オプションで例外を発生させる方法もあります。単純に2回実行すると、エラーが起きるのです。同じ星の名前(テーブルに対するプライマリー・キーになっています)が再度使われるため、プライマリー・キー値の重複というエラーが起きます。4番目のメニュー・オプションを使ってSol(太陽)を削除してから再度レコードを作ろうとすると、この例外が起きるため、Solが再度現れることはありません。この場合では、トランザクションはロールバックされます。
リスト4. データベースに星を追加するtest2() コード
/**
* Perform the second test. This test adds the stars,
* and if nothing goes wrong, they will be in the database.
* @throws NamingException
* @throws CreateException
*
* @ejb.interface-method view-type="both"
* @ejb.transaction type="Required"
*/
public void test2() throws NamingException, CreateException {
StarLocalHome home = StarUtil.getLocalHome();
home.create("Sol");
home.create("Betelguese");
home.create("Andromeda");
home.create("Aries");
home.create("Omicron Cetus");
} |
図5. デモ・アプリケーションに表示された結果
Directly remove Sol(太陽を直接削除する)
Directly remove Solというメニュー項目は、Webアプリケーションのパースペクティブからトランザクション・コードを呼ぶ方法の例です。この場合では、星という実体に直接アクセスし、その中の1つ、Solという名前の星を削除するために、Web階層に幾らかのコードが必要です。リスト5に示すコードは、これをUserTransactionを使って行う方法を示しています。J2EE標準名 .java:comp/UserTransactionを使ったUserTransactionのインスタンスを参照するために、JNDI(Java Naming and Directory Interface)APIの一部であるInitialContextオブジェクトが使われています。このインスタンスが取得できると、それを使ってトランザクションの開始とコミットが行われます。もし何らかのエラーがあると、そのトランザクションはロールバックされます。
リスト5. Web階層でのトランザクション処理の例
/**
* Remove the star Sol from the database by performing
* the operation directly on the Star EJB instead of going
* through the session bean.
* @throws NamingException if the user transaction cannot be found.
* @throws SystemException if the transaction cannot be used.
* @throws NotSupportedException if the transaction cannot be used.
*/
private void removeSol() throws NamingException,
NotSupportedException, SystemException {
InitialContext ctx = new InitialContext();
UserTransaction tx = null;
try {
// Look up the transaction
tx = (UserTransaction)ctx.lookup("java:comp/UserTransaction");
// Begin the transaction now
tx.begin();
StarLocal sun =
StarUtil.getLocalHome().findByPrimaryKey("Sol");
sun.remove();
// We've succeeded, so commit the transaction
tx.commit();
} catch (Throwable e) {
// An error occurred, so roll back.
tx.rollback();
e.printStackTrace();
} finally {
// Dispose of the initial context.
if (ctx != null) {
try {
ctx.close();
} catch (Throwable ex) {
}
}
}
}
|

 |
新しいトランザクションにステートレス・セッションbeanを使う
Transaction Demoのソースコードをよく見ると、トランザクションに関する部分が非常少ないことに気がつくでしょう。これは、Geronimoコンテナーが大部分のトランザクション処理を背後で行ってくれるためです(良きJ2EEコンテナーの望ましき姿です)。デプロイメント記述子(ejb-jar.xml)のコンテナー・トランザクション・セクションでマーキングされたステートレス・セッションbeanメソッドは全て、Geronimo EJBコンテナーによって、自動的にCMTが用意されています。CMTは、コードを単純化する上で、非常に良い方法です。コンテナー管理のトランザクション・メソッドを持つステートレス・セッションbeanによって、データベース実体の管理に付随する複雑さが軽減されるのです(これをリスト5とリスト6に示します)。複雑な関係を持たない、1つのタイプのエンティティーbeanに対してオペレーションを行う場合には、RequiredあるいはRequiresNewというトランザクション・タイプで単純にメソッドをマーキングすれば充分です(DTDの例としてはリスト6を見てください)。
リスト6. ejb-jar 2.0 DTDの抜粋
"The trans-attribute element specifies how the container
must manage the transaction boundaries when delegating a
method invocation to an enterprise bean's business method.
The value of trans-attribute must be one of the following:
<trans-attribute>NotSupported</trans-attribute>
<trans-attribute>Supports</trans-attribute>
<trans-attribute>Required</trans-attribute>
<trans-attribute>RequiresNew</trans-attribute>
<trans-attribute>Mandatory</trans-attribute>
<trans-attribute>Never</trans-attribute>
Used in: container-transaction"
|
Transaction Demoアプリケーションは、XDocletコード・ジェネレーターを使ってトランザクション・タイプを設定します。ですからリスト4のメソッドに関するドキュメンテーションのコメントを調べれば、@ejb-transaction type="Required"タグがあることが分かります。Requiredトランザクション・タイプは、セッションbeanメソッドが、そのコードの実行中にトランザクションの存在を必要とすることを規定します。もしトランザクションが無い場合には、コンテナーがトランザクションを提供します。もし呼び出し側スレッドに既にトランザクションが存在している場合には、そのトランザクションが使われます。
RequiresNewトランザクション・タイプは、それとは異なります。もし呼び出し側スレッドにトランザクションが無い場合には、コンテナーがトランザクションを作成します。これはRequiredトランザクション・タイプと似ています。しかし、もし呼び出し側のスレッドに既にトランザクションが存在している場合には、そのトランザクションは使用停止され、新しいトランザクションが作成、使用されます。その後で、呼び出し側のトランザクションが続行されるのです。
まとめ
Geronimoアプリケーション・サーバーの下で実行するように作られたTransaction Demoアプリケーションの例を見ることによって、J2EEアプリケーション・サーバーの中でのトランザクションの使い方がよく理解できたと思います。また、ステートレス・セッションbeanを使ってCMTを設定する例も見てきました。さらに、UserTransactionクラスのインスタンスを取得することによってトランザクションを作成する方法、そのライフタイムの管理方法についても学びました。これで皆さんも、データ整合性の維持にGeronimoを活用できるでしょう。
ダウンロード
参考文献 学ぶために
製品や技術を入手するために
著者について  | |  | Neal Sancheは、最近Microsoft® .NETの世界に上陸したJava開発者として、居心地の良い自分の過去のルーツと格闘しています。これまでに、幾つかの商用J2EEアプリケーションと、スタンドアローンのJavaアプリケーションを経験しています。時間がある時には音楽を書き、写真を撮り、技術記事を書いています。その幾つかの例を、彼のWebサイトで見ることができます。連絡先はneal@nsdev.orgです。 |
記事の評価
|