seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

JTAとJNDIの謎を追う

動機

今日は道草、寄り道。

50%:50%の合弁会社なのでシステム部門も3つあったりする会社での仕事。
相手先がウチよりもレベルが高い(CMMレベル5)ことを鼻にかけて「キミんところで無理なようなら、うちが全部やってあげるよ」とか言われたらしくて、その場にいた一同、ムカッときたらしい。
いや、やってくれるのはかまわないのだが、その見下した態度はどうなのか。
私は、その場に居なかったのでいいんですけど。
実力ないのは事実だし。

final InitialContext ctx = new InitialContext();
final UserTransaction userTx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
final DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myDB");

この見慣れたコードの断片はWeblogic6.1Jの公式ガイドブックに書かれていたものを見て以来、なにやってるんだろうなぁ、と思っていた。

まず、JNDIの「java:」って、どうゆうメカニズム? という疑問。
java:comp/env/... で、どこからでも今実行しているアプリケーションごとの設定を見れるんだよ。
実行する場所が変われば同じコードでも違う設定が見れるし、位置透過性があるというか。
もう1つは、なんでDataSourceからコネクションを取得するだけでJTAトランザクションが管理できるの?

javax.transaction.TransactionManagerからjavax.transaction.Transaction#enlistResource()で、XAResourceを分散トランザクションに参加させられることは分かったけれど、
DataSourceからコネクションを取得すると、如何して、このトランザクションに参加することになるのかよ?
と、不思議に思っていたわけだ。

だが、この2つは調べてみてもJ2EEの奥深さに畏敬の念をより強く感じさせられるだけで、まあ、Weblogicとかよろしくやってくれているから、まあ、いいか、と断念して1年が経過してしまった。

最近になって「今回のプロジェクト、TomcatJTAを使うらしいよ」と聞いて、そんなことできるのか*1と驚いて、あらためて、いろいろ調べてみた。

DataSourceは、いつトランザクションに参加するのか。

JOTMとTomcatの組み合わせ例とか見ながら、まずは、JOTMのソースを見てみる。
うむ…、やはりenlistするタイミングは、依然不明じゃないか。
だが、server.xmlの中に指定するDataSourceに不思議な違和感を感じた。
なぜか、データソースにJOTMのクラスを指定している…。もしや、これはWrapperなのか、と気がつく。

あとは、それらしきものを頼りにGoogleでさまようこと1時間。途中で眠くなったり、もうやめようかと思ったりしながら、ようやく、たどり着いた。

http://www-128.ibm.com/developerworks/java/library/j-jtp0410/

Every J2EE container can create transaction-aware pooled DataSource objects, but the J2EE specification doesn't show you how, because it's outside the spec. If you browse the J2EE documentation, you won't find anything on how to create JDBC data sources. You'll have to look in the documentation for your container instead. Depending on your container, creating a data source might involve adding a data source definition to a property or configuration file, or might be done through a GUI administration tool.

Each container (or connection pool manager, like PoolMan) provides its own mechanism for creating a DataSource, and it is in this mechanism that the JTA magic is hidden. The connection pool manager obtains a Connection from the specified JDBC driver, but before returning it to the application, wraps it with a facade that also implements Connection, interposing itself between the application and the underlying connection. When the connection is created or a JDBC operation is performed, the wrapper asks the transaction manager if the current thread is executing in the context of a transaction, and automatically enlists the Connection in the transaction if one exists.

幽霊の正体見たり枯尾花(?)。
マジックも種を明かされれば、そんなものか、と思ったり。

コンテナがRDBMSのDataSourceなどをラップして、コネクションを取得するときにトランザクションに参加させているだけのことだったのだ。
しかも、それはJ2EEの仕様に、そうしろとは書かれていないらしい。(自明だから、ですかね?)

JNDIプロバイダは、どうやって拡張されるのか。

あと、JNDIのほうも、なんか分かった気がした。
実装できるレベルじゃないけど、ためしにlookup("seraphyware:comp/env")みたいにできるものを作ってみた。

InitialContextの実装で対処するのか、もしそうなら、2つ以上のプロバイダをインストールしようとしたらかち合うんじゃないか、とか、いろいろ余計な心配をしていたが、そうではなかった。

InitialContextからContextを取得したあとlookupするときに、名前の先頭が「スキーマ付き」だと特別扱いになって、xxxURLContextFactoryというクラスを呼び出すだけなんだ。
xxxは、そのままスキーマ名。スキーマは小文字が普通だから、クラス名も、なんと小文字で始まるという異例さ。
あとは、どのパッケージから検索するか、であるが、これも「jndi.properties」というプロパティファイルに書いてクラスパス上に置いておくだけらしい。
クラスパス上に複数のjndi.properiesがあることも当然予想されるが、これらは、すべて連結して検索されるから問題ないらしい。
スキーマでContextの実装が振り分けられた後は、そのContextの実装の中で名前をよろしく解析して、それなりにオブジェクトを返すだけでよい。

幽霊の正体見たり枯尾花(?)。
マジックも種を明かされれば、そんなものか、と思ったり。

結論

とりあえず、満足。
なにかをやり遂げた充実感でいっぱいです。(なにもしてないけど。)

*1:つーか、素直にJ2EEサーバ使おうよ、とか思ったり。