第8回 J2EEのトランザクション処理


 トランザクションの基礎を知る

 では、具体例を挙げながら、トランザクション処理の基本を説明していきましょう。

 

 ネットワークでお金を振り込む

 稚内北星学園大学がW銀行の口座から、丸山のM銀行の口座に、東京への出張旅費10万円をオンラインで振り込むという処理を考えてみましょう。当初、W銀行の口座には100万円が、M銀行の口座には5万円が入っていたことにします。10万円の振り込みで、W銀行とM銀行の口座の残高は、90万円と15万円になります。

図1 

 こうしたことは、何の変哲もないことに思えますが、「W銀行からM銀行への10万円の振込み」がプログラム的にはどのように実現されるのかを、もう少し詳しく考えて見ましょう。まず、簡単に分かることは、WからMへの10万の振込み処理は、基本的には2つの部分から構成されていることです。すなわち、Wからの10万円の引きおろし処理とMへの同額10万円の預け入れ処理です。プログラム的に言えば、W銀行のデータベースの稚内北星学園大学の口座のデータが100万から90万に更新され、M銀行のデータベースの丸山の口座のデータが、5万から15万に更新されるということです。

図2

 

 ネットワーク上の振込みの「トラブル」を想定する

 次に考えなければいけないことは、処理Wないしは処理Mが失敗した場合、どうなるかということです。振り込み自体が、成功あるいは失敗と判断されるかにも依存して、いろいろのケースがあり得ます(3つの処理の成功・失敗ですから8通りあります)。

■ケースA

処理Wが成功して処理Mが失敗しているのに振り込み側が気付かなければ、すなわち、振り込みが成功したと判断されれば、丸山は困ってしまいます。大学側は、「振り込みは行った(振り込みの成功)。大学の通帳にも証拠がある(処理Wの成功)」と主張するでしょうし、それはそれでもっともなのですが、丸山には、お金が入りません(処理Mの失敗)。

■ケースB

処理Wが成功して処理Mが失敗するという先と同じケースでも、振り込みは失敗したと判断するだけでは、困ったことが起こります。振り込みが失敗したということは、振り込みが行われなかったということなのですが、このままでは、大学の口座から、10万円が消えてしまいます(処理Wの成功)。この場合には、いったん成功した処理Wを、振り込みの失敗の判断と同時に、元の状態に、再更新する必要があるのです。

■ケースC

逆に、処理Mが成功して処理Wが失敗し、振り込みは失敗したと判断されれば、丸山の口座には、突然10万円が登場することになります。こういう「バグ」は、個人的には大歓迎です。もっとも、こうしたずさんなプログラムを使っている金融機関なら、先の例のように知らないうちに預金がなくなることも起こり得るわけですから、手放しで喜べる問題ではないと思います。ここでも、振り込みが失敗したと判断されるなら、それだけで終わらないで、データベースを元の状態に再更新する必要があるのです。当然ですね。

 全部のケースを枚挙することはしませんので、残りのいくつかのケースを想定してみてください。

 

 トランザクション = ロールバック + コミット

 もちろん、振り込み処理が成功したと判断できるのは、処理Wと処理Mの両方が成功した場合だけであって、それ以外の場合には、振り込みは成功したと考えることはできません。ここでのポイントはむしろ、振り込み処理に失敗したのなら、処理Wと処理Mのどちらかがもしも成功していたなら、その成功したデータの更新は改めて打ち消し、元のデータの状態に戻さなければいけないということです。

 トランザクション処理では、このような状態の後戻り処理をロールバックと呼んでいます。処理をなかったことにする「巻き戻し」だと思えば分かりやすいかもしれません。逆に、処理が成功して前に進んでいいよという判断をコミットといいます。ゆだねますから次に進んでください、ということだと思います。

 この問題の見かけ以上の複雑さは、1つには、振り込み処理を行うところと、処理W、処理Mを行うところとが、ネットワーク上の別々のノードであるということに起因しています。もう1つは、どこまで処理が進んだら、ロールバックする必要がなくなるのか、逆にいえば、処理のどの段階までは、ロールバックがあり得るのかを明確にしなければならないということです。

 

 2フェーズ・コミットによる解決

 そろそろ先の問題の答えを示しましょう。この問題は、データベースでよく使われる「2フェーズ・コミット(2相コミットメント)」という手法を使って解くことができます。2フェーズ・コミットというのは次のような処理のことです。

 データベースの更新が必要なトランザクションがあったとします。2フェーズ・コミットでは、その作業は2つのフェーズで行われます。まず、第1のフェーズでは、トランザクションは作業領域で行われ、その結果はデータベースではなくログに書き出されます。第2フェーズでは、このログをデータベースに書き込みます。第1フェーズの途中で障害が発生した場合には、データベース自身は更新されていないので、元の状態に戻るためには、ログのデータを捨てるだけで済みます。第1フェーズが完了していれば、いつでもログからデータベースへの書き込みが可能です。要するに、「仮の処理」のフェーズと「本当の処理」のフェーズの2つを分離するわけです。

 この例の場合には、W処理とM処理は、まず、それぞれの初期状態を記憶し、振り込み処理からの指示を作業領域で実行して結果をログに書き出します。実行が成功したら、データをロックして、振り込み処理に成功の応答を返します。実行が失敗したら、失敗の応答を返します。これが、最初の「仮の処理」のフェーズです。続いて、「本当の処理」のフェーズに入ります。

 W処理あるいはM処理いずれかから失敗の応答があれば、振り込み処理は、双方の処理に対して、初期状態への復帰を指示し、データのロックを解きます。双方ともに成功した場合、ログから、データベースの更新を指示します。

 

 OSは、ネットワーク上でのトランザクションの面倒は見ない

 こうした方法を知らずに、この問題を解くことは不可能ではないでしょうが、結構、難しいと思います。「車輪を、二度、発明する必要はない」といいますから、知っていた方がいいのです。こうした知識は、以前なら、個別のアプリケーションのレベルではなく、むしろ、OSのコアの部分でリソースの排他制御のロジックに関連して学ばれたもののような気がします。

 市場では、2つか3つのOSしか存在しない状態になり、ややこしい問題は、OSがすべて面倒を見てくれるというOSへの奇妙な信頼感が生まれる中で、OS内部への関心は薄れているようです。一方では、ネットワークの一般化は、かつては、OSの中核部分で問題となっていたような、面倒なリソースの協調・排他制御の問題を、OSの外部で普通のネットワーク・アプリケーションのレベルで、ある意味では日常的に生み出しています。ネットワーク・アプリケーションには、十分な注意を払わないと社会的に大きな問題を引き起こしかねない死角があり得るのです。忘れてならないのは、OSはOS管理下のリソースの面倒を見るだけで、ネットワーク上でのトランザクションの面倒は見てはくれないということです。個人的には、こうした問題に一番理論的でスマートなアプローチを用意してくれるのは、流行のWebサービスではなく、JiniベースのJavaSpaceだと考えているのですが、それについては、別の機会に論じてみたいと思います。

2/5

J2EEの基礎(第8回)
  コンピューティングにおけるトランザクションの必要性
トランザクションの基礎を知る
  J2EEのトランザクション
  トランザクション属性の特長
  トランザクション属性

連載内容
J2EEの基礎
  第1回 Java Pet Storeで、J2EEを体験する(1)
  第2回 Java Pet Storeで、J2EEを体験する(2)
 

第3回 J2EEアプリケーションと配置(deployment)

  第4回 J2EEアプリケーションを構成するコンポーネント
  第5回 データベースのブラウザを作る
  第6回 EJBにおけるコンテナとコンポーネント
  第7回 J2EEのセキュリティのキホンを知る
第8回 J2EEのトランザクション処理


連載記事一覧




Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間