レベル: 中級 Jonathan Sagorin (jonathan@javoncall.com), Freelance Software Developer
2006年 8月 15日 Jonathan Sagorin は、Enterprise Java™Beans (EJB) トランザクションに関する記事全体を全 3 回シリーズのこの最終回でまとめています。この記事で、Apache Geronimo アプリケーション・サーバーでのコンテナー管理 EJB トランザクションと Bean 管理 EJB トランザクション両方の特徴、そしてその他のインプリメンテーション・オプション、構成オプションを把握してください。
はじめに
このシリーズのパート 1 とパート 2 では、Bean 管理とコンテナー管理による EJB トランザクションの概要、そしてこのトランザクションの Geronimo アプリケーション・サーバーへのインプリメンテーション方法について説明しました。では、その他にも可能なトランザクション設定には何があるでしょう。EJB トランザクションに使用するときに、他にも考慮しなければならないことは何でしょう。
この記事では、まず、パート 1 とパート 2 で紹介したコンテナー管理トランザクションと Bean 管理トランザクションという 2 つのトランザクション・オプションについてまとめます。次に、並行性制御ストラテジー、つまりデータ損失のないトランザクションを確実にするための方法を紹介します。また、分離レベル (トランザクション同士の分離を制御する方法) を取り上げ、トランザクション・タイムアウトの設定方法について説明します。最後に、分散トランザクションを使用する是非についても検討します。
EJB トランザクションのオプションには何を設定しているでしょう?
EJB トランザクションをインプリメントする際には、コンテナー管理トランザクションと Bean 管理トランザクションのどちらかを選択できます。
コンテナー管理トランザクションでは、トランザクションの動作をデプロイメント記述子内に指定します。トランザクション境界は、EJB コンテナーによって制御されます。トランザクション属性は、エンタープライズ Bean 全体、Bean の個別メソッドごと、またはその両方に対して指定します。トランザクション属性は、以下のものから選択します。
-
required
-
RequiresNew
-
Supports
-
Mandatory
-
NotSupported
-
Never
Bean 管理トランザクションでは、トランザクション境界をプログラムで制御して、トランザクションの開始、コミット、そしてロールバックのタイミングを決定します。Bean 管理トランザクション内では、JTA (Java Transaction API) トランザクションと JDBC (Java Database Connectivity) トランザクションのいずれかを選択できます。JDBC トランザクションでは java.sql.Connection インターフェースを介して直接操作を行うことによってトランザクションの動作を制御する一方、JTA トランザクションでは javax.transaction.UserTransaction インターフェースを使ってトランザクションを制御します。
セッション Bean またはメッセージ駆動型 Bean (MDB) を使用している場合は、Bean 管理トランザクションまたはコンテナー管理トランザクションをインプリメントできます。エンティティー Bean の場合には、コンテナー管理トランザクションしか使用できません。
表 1 に、エンタープライズ Bean インプリメンテーションごとのトランザクション・タイプのオプションをまとめます。
表 1. エンタープライズ Bean ごとのトランザクション・タイプのオプション
| トランザクション・タイプ | セッション Bean | エンティティー Bean | メッセージ駆動型 Bean |
|---|
| Bean 管理 | x | | x | | コンテナー管理 | x | x | x |
ご使用の Bean にどちらのトランザクション・タイプを使用すればよいかわからない場合、Sun Microsystems では、エンタープライズ Bean に
required
属性を指定してコンテナー管理トランザクションを使用することを推奨しています。
開発者にとっては、コンテナー管理トランザクションを使用する方が簡単で、必要な作業も少なくなります。Bean メソッドに必要なトランザクション・ロジックはありません。トランザクション境界は、エンタープライズ Bean のメソッド・レベルで区分します。つまり、Bean メソッドはトランザクションのコンテキスト内で実行されるか、あるいは実行されないかのどちらかです。
一方、トランザクション境界をもっと厳密に制御する必要がある場合は、Bean 管理トランザクションを使用してください。つまり、エンタープライズ Bean 内で長時間にわたるプロセスが予測される場合には、Bean 管理トランザクションを使用します。この記事の目的は、トランザクションの実行時間をできるだけ短くすることです。コンテナー管理トランザクションを使用すると、トランザクションの境界区分が Bean メソッド・レベルになるため、細かさが足りません。
Bean 管理トランザクションを使用すると、トランザクション期間を短時間に制限できます。トランザクション内のデータベース操作を分離させて、長期プロセスをトランザクションのスコープの外で実行させることができるためです。これによって、他のトランザクションが同じデータにアクセスできないという事態を妨ぐことができます。
並行性制御ストラテジー
EJB による並行性制御をインプリメントするには、ペシミスティック・ロックまたはオプティミスティック・ロックの 2 つのストラテジーのどちらかを採用できます。
ペシミスティック・ロックでは、変更する必要のあるデータをトランザクション期間中ロックして、他には誰もそのデータが変更できないようにします。このストラテジーは、操作中のデータを他の誰かが変更しようとする可能性の高いシステムで使用します。このストラテジーによってデータへのアクセスは確実になりますが、これは小規模システム向けです。システムの規模が大きくなって必要なロックの数が増えると、パフォーマンスが落ちてしまうためです。
オプティミスティック・ロックでは、トランザクション中にロックを維持しません。データを使用している間、そのデータが他のトランザクションによって変更されないだろうという前向きな見通しを基にしています。データの競合という例外的な事態に対応することは可能ですが、このような事態はめったに発生しないことを前提とします。データの更新が必要になった場合は、最後に読み出されたときから変更前に読み出されるまでの間にデータが変更されていないことをチェックするストラテジーをインプリメントします。データが変更されていなければ、更新を実行します。このストラテジーは大規模なシステムに適しています。欠点としては、データ競合を検出して対処するためのコードをインプリメントしなければならないことです。
次のセクションでは、トランザクションの分離レベルの変更について説明します。これはペシミスティック・ロックの一例で、ペシミスティック・ロックの程度と有効性は、トランザクションの分離レベルを変更することによって制御できます。
独立性
トランザクションのACID 特性のうちの 1 つは、独立性です。独立性は、トランザクションの操作 (データの読み取りであろうが、書き込みであろうが) を、並行して実行中のその他のトランザクションから独立 (つまり、分離) させることを可能にします。この独立性を制御することにより、それぞれのトランザクションは、その時点でデータベースを変更している唯一のトランザクションであるかのように動作します。トランザクションをその他のトランザクションから分離させる程度のことを、分離レベルと呼びます。
分離レベルの制御には、ロック・メカニズムと同期を使用します。分離レベルが高くなればなるほど、必要になるロックと同期が増えます。ロックはデータ・リソース上で維持されるため、データ操作を実行しようとするその他のトランザクションは、ロックが解除されるまで待たなければなりません。つまり、分離レベルを引き上げると、パフォーマンスに影響します。逆に、分離レベルが低くなるほど、トランザクションがロックの解除を待つ時間が短くなるため、パフォーマンスが向上します。
データの整合性
分離レベルはトランザクションに設定され、以下のデータ整合性問題に対処します。
- ダーティー読み取り
- 繰り返し不可の読み取り
- ファントム読み取り
ダーティー読み取り
ダーティー読み取りは、まだコミットされていないデータベースからデータが読み出されると発生します。読み出されたデータは、データベース内の実際のデータと一致しません。
次のようなシナリオを検討してください。このシナリオでは、2 つのトランザクションがデータベース上のストリング・フィールド X を読み取ります。ストリング X の初期値は
foo です。
- トランザクション 1 が、ストリング X の値
foo を読み取ります。
- トランザクション 1 は、ストリング X の現行値を bar に連結し、データベースに保管します。
- X の新規の値は
foobar となります。トランザクション 1 は、commit 文をまだ発行していません。
- トランザクション 2 が、ストリング X の値
foobar を読み取ります。
- トランザクション 1 がアボートします。
- トランザクション 2 は、ストリング X を bar に連結し、データベースに保管します。
- X の新しい値は
foobarbar となりますが、正しい値は footbar です。
- トランザクション 2 はストリング X の値でダーティー読み取りを行ったことになります。
ここでの問題は、あるトランザクションが値を変更する可能性があるのに、他のトランザクションが当初の変更がコミットされる前にこの値を読み取ってしまうことです。そのデータはダーティーで、データの実際の状態を表していません。
繰り返し不可の読み取り
繰り返し不可の読み取りは、アプリケーションがデータベースからデータを読み取り、(おそらく同じトランザクションで後から) そのデータの再読み取りを行ったときに、データが変更されていた場合に発生します。以下のように、2 つのアプリケーションが同じデータを読み取って更新するシナリオがあるとします。
- アプリケーション 1 が、ストリング X の値
foo を読み取ります。
- アプリケーション 2 がストリング X の値を
foobar に更新します。
- アプリケーション 1 がストリングの値を再び読み取り、値が
foobar に変更されていることを検出します。
つまり、2 回の読み取りの間にデータの値が変更されたため、値の不整合という結果になります。
ファントム読み取り
ファントム読み取りは、繰り返し不可の読み取りと同様です。ただし、ファントム読み取りの場合は、新しいデータがデータベースに挿入されます。アプリケーションはデータベースから一連のデータを読み取り、この同じデータ・セットを再び読み取ったときに、データが追加されていることを検出します。以下のように、2 つのアプリケーションがデータベースの同じデータを読み取って更新するシナリオがあるとします。
- アプリケーション 1 が特定の基準でデータを検索し、5 つの行を持つデータ・セットを返します。
- アプリケーション 2 が、アプリケーション 1 の検索基準を満たす 5 つの行をデータベースに追加します。
- アプリケーション 1 が最初の基準に基づいてデータベースを (5 行が検出されることを期待して) 再び読み取ると、10 行が返されます。
ここでも、データが 2 回の読み取りの間で一致しません。
分離レベルを選択する
以下に、最小レベル (最弱) から最大レベル (最強) の順に 4 つのトランザクションの独立性をリストします。分離レベルを引き上げると、アプリケーションのパフォーマンスが落ちることに注意してください。
-
Read uncommitted (読み取り非コミット) -- このオプションは、(アプリケーションでは珍しいケースですが) データが共有されていない非ミッション・クリティカルのシステムにのみ使用します。パフォーマンスは最大限になりますが、並行性制御が犠牲になります。このオプションは、他に並行トランザクションがないことが確実な場合に使用してください。このオプションを使用すると、上記にリストしたデータ問題は何も解決されません。
-
Read committed (読み取りコミット) -- これは、ほとんどのデータベースのデフォルト分離レベルで、Apache Geronimoでもデフォルト値となっています。コミット済みのデータのみが読み取られるため、このオプションによってダーティー読み取りの問題がなくなります。データベース上で必要なロックの数が増えるため、処理速度は落ちることになります。
-
Repeatable read (反復可能読み取り) -- ダーティー読み取りと読み取り非コミットの問題に対処するには、この分離レベルを使用します。読み取ったどの行も後で再び読み取ることができるため、行の値が矛盾するということがありません。
-
Serializable (順序付け可能) -- これは最も厳しい分離レベルで、3 つのすべてのデータ問題に対処します。トランザクションを完全に分離し、他のトランザクションから完全に独立させて動作させたい場合は、このレベルを使用してください。これによって、データの整合性が保証されます。これはミッション・クリティカルで、完全に分離したトランザクション動作を保証するために使用します。ただし、この分離によってパフォーマンスが犠牲になることに注意してください。
表 2 に、分離レベルの選択肢をまとめ、それぞれが前述の 3 つのデータ問題に対処するかどうかを示します。
表 2. 分離レベルによるデータ問題のソリューション
| データ・ソリューション | 読み取り非コミット | 読み取りコミット | 反復可能読み取り | 順序付け可能 |
|---|
| ダーティー読み取りの解決 | | x | x | x | | 繰り返し不可の読み取りの解決 | | | x | x | | ファントム読み取りの解決 | | | | x |
Bean 管理トランザクションでの分離レベル
分離レベルは、ベースにあるデータベース・リソース・マネージャーによって指定されます。Bean 管理トランザクションを使用すると、ベースとなる接続をプログラムで操作できます。java.sql.Connection インターフェースにアクセスできるため、setTransactionIsolation(int level) メソッドを使用して接続の分離レベルを変更することが可能です。
以下の定数を使って、該当する分離レベルを設定します。
-
Connection.TRANSACTION_READ_UNCOMMITTED
-
Connection.TRANSACTION_READ_COMMITTED
-
Connection.TRANSACTION_REPEATABLE_READ
-
Connection.TRANSACTION_SERIALIZABLE
その他に以下の対象メソッドもあります。
-
Connection.getTransactionIsolation()
-
DatabaseMetaData.supportsTransactionIsolationLevel(int)
(上記のメソッドについての詳細は、「参考文献」セクションに記載したSun JavaDoc API を参照してください。)
注: 分離レベルを変更することは、すなわちデータベース・リソース・マネージャーに対して該当リソースの独立性の変更を要求することになります。データベース・ベンダーが分離レベルの変更をサポートしなければならないという要件はありません。実際には、多くのデータベース・ベンダーでは許可されないため、分離レベルの変更は慎重に行わなければなりません。データベース・リソース・マネージャーの資料で、サポートされている分離レベルを確認してください。
また、トランザクションの分離レベルは、トランザクションを開始する前に設定してください。トランザクションの途中で分離レベルを切り替えることはできません。ほとんどのリソース・マネージャーでは、トランザクションの参加プログラムすべてに対して、同じ分離レベルを使用する必要があります。
コンテナー管理トランザクションでの分離レベル
コンテナー管理トランザクションを使用する場合、デプロイメント記述子に分離レベルを指定することは不可能です。デフォルトでは、Geronimo はEJB コンテナーの分離レベルに Read Comitted を使用します。分離レベルをより厳密に制御する必要がある場合は、Bean 管理トランザクションを JDBC トランザクションで使用することを考慮してください。
トランザクション・タイムアウト
Bean 管理トランザクションを JTA トランザクションで使用する場合、 javax.transaction.UserTransaction インターフェースで setTransactionTimeout メソッドを使用できます。このメソッドは、トランザクションがアボートするまでの最大実行時間 (秒単位) を設定します。
分散トランザクション
単一のトランザクションに含まれる複数の参加プログラムが物理的にネットワークに分散されている場合、このトランザクションは分散トランザクションと呼ばれます。分散トランザクションでは、異なるタイプのリソースをトランザクションに参加させることができます。分散トランザクションには、以下のような例があります。
- 単一のセッション Bean がトランザクションを開始して、データベース A を更新します。これによって、同じアプリケーション・サーバーで実行する 2 番目のセッション Bean が呼び出されてデータベース B を更新します。トランザクションは最初のセッション Bean によってコミットされます。このように、両方のデータベース更新は同じトランザクション内で発生します。
- 単一のセッション Bean がトランザクションを開始して、データベース A を更新します。これによって、異なるアプリケーション・サーバーで実行する 2 番目のセッション Bean が呼び出されてデータベース B を更新します。両方のデータベースが同じトランザクション内で更新されることを確実にするのは、それぞれのアプリケーション・サーバーのトランザクション・マネージャーです。
- 単一のセッション Bean がトランザクションを開始してデータベース A を更新し、続いて JMS (Java Message Service) 操作が行われます。どちらの作業単位も、同じトランザクションの一部です。JMS 操作が失敗すると、トランザクションによるデータベースの更新は行われません。
分散トランザクションを実行するには、複数のトランザクション・マネージャーの連携が必要となります。通常はトランザクション・マネージャーのなかから 1 つが指定され (トランザクション・コーディネーター、または分散トランザクション・マネージャーとも呼ばれます)、その他のトランザクション・マネージャーを調整します。
トランザクション・マネージャーはリソース・マネージャーと連携して、リソース (おそらく、データベースまたはメッセージ・サーバー) で必要なコミットまたはロールバックを実行します。ほとんどのデータベースでは、トランザクション・マネージャーとリソース・マネージャーが密結合されています。
二相コミット
分散トランザクションは、二相コミットと呼ばれるプロトコルを使用した通信によって行われます。この名前から、2 つのフェーズがあることが分かるはずです。
- 最初のフェーズ (コミットの準備):
- トランザクション・コーディネーターが信号を送信して、各トランザクション・マネージャーに操作の準備をさせます。
- トランザクション・マネージャーは、操作 (通常はデータ更新) のステップ (または詳細) をトランザクション・ログに書き込みます。失敗した場合は、トランザクション・マネージャーはこれらのステップを使用して操作を繰り返します。
- トランザクション・マネージャーがローカル側でトランザクションを作成し、リソース・マネージャーにリソース (データベースまたはメッセージ・サーバーなど) 上で操作を実行するように通知します。
- リソース・マネージャーが操作を実行し、トランザクション・マネージャーに成功したか (コミット準備完了信号) または失敗したか (ロールバック準備完了) を示します。
- リソース・マネージャーは、トランザクション・マネージャーからの次の指示を待ちます。
- トランザクション・マネージャーは、トランザクション・コーディネーターに成功または失敗の結果を示します。
- 2 番目のフェーズ (コミット・フェーズ): 最初のフェーズの結果が、2 番目のフェーズのすべてのトランザクション・マネージャーに伝えられます。トランザクション・マネージャーが失敗を報告した場合は、すべてのトランザクション参加プログラムがロールバックします。
- トランザクション・コーディネーターは、すべてのトランザクション・マネージャーにコミット (またはロールバック) するように指示します。
- すべてのトランザクション・マネージャーがそれぞれのリソース・マネージャーに、コミットまたはロールバック情報を渡します。
- リソース・マネージャーがトランザクション・マネージャーに成功または失敗の結果を返します。
- トランザクション・マネージャーは、トランザクション・コーディネーターに成功または失敗の結果を示します。
対話は可能ですか?
分散トランザクションにおける最大の課題は、トランザクション・マネージャー間で共通の通信プロトコルを確立することです。二相コミットには XA プロトコルという標準化されたプロトコルがありますが、すべてのベンダーがこの標準をサポートするわけではありません。XA プロトコルは、トランザクション・マネージャーとリソース・マネージャー間のインターフェースを定義しています。
Geronino のトランザクション・マネージャーは、オープン・ソースのトランザクション・マネージャーである JOTM (Java Open Transaction Manager) です。これは XA プロトコルをインプリメントするもので、JTAに準拠します。JTA を覚えていますか? JTA は、トランザクション・マネージャーと通信するためにこのシリーズで使用したインターフェースです。Bean 管理トランザクションで JTA を使用してトランザクションを開始、コミット、またはロールバックするタイミングを指定しました。
すべてのトランザクション参加プログラムが通信プロトコルについて一致する限り、同じ分散トランザクションに参加することが可能になります。
分散トランザクションの欠点
分散トランザクションでは、ローカル・トランザクションに比べ処理速度が遅くなり、すべてのトランザクション参加プログラムに必要なシステム・リソースも多くなります。また、トランザクション・コーディネーターとすべてのトランザクション参加プログラムとの間のネットワーク通信は、システム応答時間に影響を及ぼします。関係するトランザクション・マネージャーおよびリソース・マネージャーの数により、分散トランザクションにかかる時間が長くなることは避けられません。
まとめ
この EJB トランザクションを紹介するシリーズの最終回では、オプションについてまとめ、Geronimo で EJB トランザクションを使用する場合の、その他の構成オプションと選択肢について説明しました。
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Jonathan Sagorin はフリーランスの開発者です。その10年にわたる経歴の大半をコンサルタントとして過ごし、カスタム Java ソリューションを実現してきました。余暇にはソフトボールとアドリブ・プレー (彼のソフトボール・チームの仲間は反論することでしょうが、必ずしもアドリブではありません) を楽しんでいます。
|
記事の評価
|