JCA における論理コネクションハンドル

JCA の仕様って思いの外複雑で,よく分かっていなかったり勘違いしていることが多々あります.
S2JCA の実装を進めていくうちに,先週のwithout EJB 読書会にて誤った発言をしていたことに気づいたので,ここで訂正しておきます.


まずは間違った理解から.
JCA のコネクション管理で中心となるのは javax.resource.spi.ManagedConnection です.こいつはいわゆる物理コネクションを表現するもので,JDBC でいうところの javax.sql.PooledConnectionjavax.sql.XAConnection に相当します.こいつらと同様,ManagedConnection にも addConnectionEventListener() メソッドがあり,論理コネクションがクローズされた際に通知してもらうことができます (JCAConnectionEvent/ListenerJDBC のものとは別パッケージにある別物ですが).
その ManagedConnection には cleanup()destroy() というメソッドがあります.前者は論理コネクションを,後者は物理コネクションをクローズするというものです.
JCA 的には,論理コネクションの close() (相当の) メソッドが呼ばれると ConnectionEventLister を実装したコネクションマネージャに通知され,そのイベントを受けたコネクションマネージャがコネクションをプーリングしたりクローズしたりできる,という事になっています.
さて,ManagedConnectioncleanup() というメソッドがあるという事は,アプリケーションが論理コネクションの close() を呼び出したとしても,実際に論理コネクションがクローズされるわけではなく,それはコネクションマネージャに任されているということではないか? と私は解釈していました.それが読書会で喋ったこと.


ところが,それは大きな間違いでした.アプリが論理コネクションの close() を呼び出すと,それは本当にクローズされてしまいました.しくしくしく.
それじゃあ ManagedConnection#cleanup() ってなんのためにあるのさ?


実は激しく勘違いしていたのですが,ManagedConnection の背後にいるのは物理コネクションと論理コネクションだけではなかったのです.もうひとつ,「論理コネクションハンドル」というものがいたのです.っていうか,上で「論理コネクション」と書いたものの多くは実は論理コネクションハンドルでした.心より恥じる.
つまり,JCAJDBC のような「物理コネクション−論理コネクション」という二階建てではなくて,「物理コネクション−論理コネクション−論理コネクションハンドル」という三階建てだったのです.
このうち物理コネクションと論理コネクションは ManagedConnection の後ろに隠れています.そして ManagedConnection#getConnection() を呼び出すと (論理コネクションではなく) 論理コネクションハンドルが返ってきます.この論理コネクションハンドルがアプリに返される java.sql.Connection だったり,javax.jms.Connection だったりするのです.
アプリが論理コネクションハンドルの close() を呼び出すと,その論理コネクションハンドルは実際にクローズされます.もう使うことはできません (ちょっと嘘).そして,コネクションマネージャには論理コネクションハンドルがクローズされたことが通知されます.
コネクションマネージャは,論理コネクションをクローズして物理コネクションをプーリングしたり,物理コネクションもクローズして破棄したりすることができます.
トランザクションの中では,論理コネクションハンドルがクローズされても論理コネクションはクローズしないでおきます.そしてアプリが同じコネクションをオープンしようとすると新しい論理コネクションハンドルを取得して返します.論理コネクションハンドルは論理コネクションの薄いラッパーに過ぎないので,この操作は比較的軽いものということになっています.


っていうのが JCA のシナリオでした.うー,論理コネクションと論理コネクションハンドルは別物だったのか...
なんでこんな勘違いをするかというと,論理コネクションを明示的にオープンする API が存在しないからではなかろうか? クローズはあるんだけど.うーみゅ...


ともあれ (JW),やっぱりアプリがコネクションをクローズしたらそいつはクローズするものなのですね.S2DBCP はクローズしてないからそのままそいつを使い続けても平気だったりするんですが.
JCA 的な振る舞いよりも S2DBCP 的な振る舞いの方が都合がいいんですけどねぇ.
例えば S2DaoS2Hibernate を一緒に使う場合.あんまりないと思うけど.S2Dao は,DAO のメソッドが呼ばれるたびに DataSource#getConnection() して SQL を実行するとすぐに Connection#close() を呼び出します.一方 S2HibernateHibernateSession を作成する際に DataSource#getConnection() を呼び出し,トランザクションが終了した後の Synchronization#afterCompletion()Connection#close() を呼び出します.つまり,S2DaoS2Hibernate ではコネクションを保持する期間が全然違うのです.つまり,こんな感じ.

S2HibernateS2Dao
getConnection() 
 getConnection()
 close()
 getConnection()
 close()
 getConnection()
 close()
close() 

S2DaoS2Hibernateが結局は同じ DB にアクセスするのであれば,S2DaoS2Hibernate も同じコネクションを使いたくなります.実際,S2DBCP と組み合わせればそれが可能です.それは S2DBCP は Connection#close() が呼ばれてもそれがトランザクション中であればその Connection オブジェクト (論理コネクション) をクローズしないからです.もし論理コネクションをクローズしてしまったら,S2Hibernate がコネクションを使おうとした際に既にクローズされてしまっている可能性があるため,残念な思いをすることになります.


JCA では,Connection#close() が呼び出されると論理コネクションハンドルをクローズしてしまうため,S2DaoS2Hibernate で同じ論理コネクションハンドルを使うことはできません.S2 Hibernate が論理コネクションハンドルを取得した後,S2DaoDataSource#getConnection() を呼び出したら新しい物理コネクションから論理コネクションハンドルを取得して返さなくてはならないのです.これは XADataSourceXAConnection が必須であることを意味します.なんてこった...


JCA ではこのような実装になるため,S2Dao のように論理コネクションハンドルのオープンクローズを繰り返されると S2DBCP よりもかなり効率が悪いことになります.
そんなわけで (どんなわけで?),S2JCA が S2DBCP よりもどれくらい効率が悪いか確認してみました.
S2JCA + Sun JDBC Connector と S2DBCP で,同一トランザクションの中で

dataSource.getConnection().close();

をひたすら繰り返すだけというテストです.ちなみに DBMSHSQLDB
その結果は...


約 10 倍遅い.(ToT)
まぁ,JDBC のコネクションプーリングに S2JCA を使うことは最初から考えていないからいいんですけどね.
ちなみに,うちの環境では上記の処理を 100 万回繰り返して S2JCA が約 5 秒,S2DBCP が約 500ms だったので,トランザクション中に DB アクセスを数千回以上繰り返すようなお馬鹿なアプリでない限り実用上の問題はないと思います.少なくとも JMS のコネクションプールとしては十分に実用的なレベルじゃないかな.
ともあれ (JW),いろいろ勉強になりました.っていうか,なってしまいました...