- PR -

Spring FrameworkでJTAを使用した際にConnectionリーク

投稿者投稿内容
コジコジ
会議室デビュー日: 2006/05/31
投稿数: 6
投稿日時: 2006-05-31 17:25
Spring Frameworkを使用したWebアプリで、JTAを利用したトランザクションを実現しようとしています。
トランザクション制御自体は機能しているのですが、Connectionのリークが発生してしまっております。
どなたかご助力のほど、よろしくお願いいたします。

【動作環境】
 OS : Windows XP Professional SP2
 アプリケーションサーバ : Weblogic Server 9.0
 Spring Framework Version : 1.2.6

【アプリ環境】
 Spring Frameworkを使用してService層、DAO層(SpringJDBC抽象フレームワーク)を構築。
 DataSourceはアプリサーバーに登録したものをJNDI経由で取得(SpringのJndiObjectFactoryBean使用)。
 JTAもアプリサーバーにあらかじめ登録されているものをSpringのJtaTransactionManagerとして取得(WebLogicJtaTransactionManager使用)。
 Service層、DAO層の各Beanとも、Spring定義ファイルにsingleton="true"として定義。

【現状】
 DAOに複数のスレッドから同時にアクセスした際に、Connectionの解放が1度しか行われず、Connectionのリソースリークが発生。
 つまり、n回のDAO層アクセスが起こると、n-1個のConnectionが解放されずに残ってしまいます。
 →各スレッドで毎回DataSource#getConnection()をしてしまい、それら全てに対して1度しかclose()されない様子(SpringJDBCがスレッドセーフ実装ではないため)。
 - - - -
 (スレッドA)
  Connection con = datasource.getgetConnection();
   ……
(スレッドB)
  Connection con = datasource.getgetConnection();
   ……
 (スレッドA)
  con.close(); ←スレッドBのConnection!! ... スレッドAのConnectionでリーク発生。
   ……
(スレッドB)
  con.close();

【現在までに調査した結果】
 書籍やWeb上の情報より、DataSourceから取得したConnectionはThreadLocalに紐付けられてスレッドごとにユニークに管理されることまでは分かりました。
 ex)<http://www.iwahrt.com/iwahrt/ListProblemSolution.do;jsessionid=C7E6B5E8543FAE08F26F008CD4A0E8FB?categoryNo=2&problemNo=30>
 
 Springのソースコードを入手し、デバッグ。
 org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection(DataSource dataSource)で、ThreadLocalが有効になっていないような動きをし、そのためにConnectionがユニークに管理されない。
 (113行目、TransactionSynchronizationManager.isSynchronizationActive()が常にfalseで返ってきてしまう)
 
 冗長な説明で恐縮ですが、SpringJDBC内部のTransactionSynchronizationManagerで使用するThreadLocalが有効にならないことが直接の原因のようです。
 下記、言葉だけの説明ではうまく伝えられませんので、実際のソースも転記いたします。
 
 
 [org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection(DataSource dataSource)]
 (113行目から)
 if (TransactionSynchronizationManager.isSynchronizationActive()) { ←ここがtrueになりません!
  …… ConnectionをThreadLocalに紐付けて管理する処理 ……
 }
 
 
 [org.springframework.transaction.support.TransactionSynchronizationManagerのフィールド]
 (84行目)
 private static final ThreadLocal synchronizations = new ThreadLocal();
 
 
 [org.springframework.transaction.support.TransactionSynchronizationManager]
 (201行目から)
 public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
 }

 同様にSpring FrameworkでJTA利用のトランザクションを実装している方がいらっしゃいましたら、情報お願いいたします。
zilloll
常連さん
会議室デビュー日: 2006/02/01
投稿数: 24
投稿日時: 2006-05-31 18:48
Spring FrameworkもJTAでのトランザクションも
使ったことが無いので恐縮なのですが、次のような状況ということでしょうか。

1.DAOオブジェクトをSingletonにしており、その中で持っている
Connection変数は、複数のスレッド間からのアクセスでも共有されている。

2.ひとつの処理内で、平行処理を行っており、その複数スレッドの処理を
ひとつのトランザクションとして処理することを想定している。

3.複数スレッドを動作させた際、Connection変数の上書きが
行われてしまい、参照を失ったConnectionオブジェクトのクローズが
できない。
※SpringFrameworkのスレッド判定処理の実装によりgetConnectionで返却される
Connectoinオブジェクトは、ひとつの処理の流れの中では
同じオブジェクトへの参照が返却されることにより、
上書きされても問題ないと想定しているが実際には異なるオブジェクトで
上書きされてしまっている?

この通りでしたら、平行処理のそれぞれのスレッドでgetConnectionを行っているため
違うスレッドと判断され異なるConnectionが返るということでしょうか?
ここはわかりませんが、実際に異なる処理で、同時に同じDAOへのアクセスが
発生した際、Connection変数の上書きが発生し、同じDBへのConnectionで
異なるトランザクションの処理が行われてしまうことはないでしょうか?
Connectionオブジェクトは同じトランザクション内では共有し、
異なるトランザクションでは共有しないような仕組みを考える必要が
あるように思います。

見当違いであったらすみません。
コジコジ
会議室デビュー日: 2006/05/31
投稿数: 6
投稿日時: 2006-05-31 19:17
> zillollさん

ご丁寧なレスポンス、ありがとうございます。
状況としては、まさしくご明察の通りです。

> Connectionオブジェクトは同じトランザクション内では共有し、
> 異なるトランザクションでは共有しないような仕組みを考える必要が
> あるように思います。

こちらもまさしく、その通りだと思います。
私の書き方が冗長なために問題の本質がボヤけてしまったのですが、本来Springで実現されるべき上記のような「トランザクションとコネクションの1対1関係」が実現されないのが問題となっております。
上記は、Spring FrameworkのDataSourceUtilsクラスによって、ThreadLocalにConnectionを紐付けることで実現されるはずなんです。

詳細な周辺情報ももらさずというつもりで、いろいろ書いてしまいましたが、つまるところ問題は……

- - - - - - - - - - - - - - - - -
[ org.springframework.transaction.support.TransactionSynchronizationManager ]にてなぜ isSynchronizationActive() がtrueとなってくれないのか。

isSynchronizationActive() は上記にも書いたように、ThreadLocal#get()がnullならfalseを返してしまいます。
つまり、どこかでThreadLocal#set(T value)が先行すると思うのですが、なぜそれが起こらないのか。
- - - - - - - - - - - - - - - - -

といったところです。
長々とスイマセン。
コジコジ
会議室デビュー日: 2006/05/31
投稿数: 6
投稿日時: 2006-05-31 19:41
たびたび申し訳ございません。
zillollさんからご指摘いただいた

> 同じDBへのConnectionで
> 異なるトランザクションの処理が行われてしまうことはないでしょうか?

という点ですが、確かにその通りですね。
ですから、冒頭にある私の「トランザクション制御自体は機能しているのですが……」という表記は正しくないです。

この「ThreadごとにConnectionを1対1で管理する」という件を解決しなければ、全てのトランザクションでcommitやrollbackが共有されてしまいますね。
フライト
ベテラン
会議室デビュー日: 2005/03/11
投稿数: 63
お住まい・勤務地: 津田沼・東京
投稿日時: 2006-06-01 14:41
横道に逸れてしまう話ですが

どうしてもJTAでトランザクション管理を行わなければならない
理由がないのであれば、Springでトランザクション管理を
行ってみるのもいいんではないでしょうか?
DAOを採用しているみたいですし。。

org.springframework.transactionパッケージにクラスも
便利なクラスがありますよ

私の個人的な印象ですが、JTAだとSunはインターフェイスを
提供しているだけで、実装を配布している会社は複数あって、
問題があった時とか調査しにくい印象があるんですよね・・
zilloll
常連さん
会議室デビュー日: 2006/02/01
投稿数: 24
投稿日時: 2006-06-01 16:30
恥ずかしながらThreadLocalを知りませんでした・・・

とりあえず現状の問題点は2つではないでしょうか。

1.並行処理で、ConnectionがThreadLocalになっていない

これは、子スレッド同士でThreadLocalになるのかという問題と
思います。
ちゃんと確認したわけではないですが、違うスレッドなのだから
ThreadLocalにはならないのではないでしょうか。
※違っていたら、識者の方ご指摘ください。

同じトランザクションで処理するのであれば、
順次処理を行うか、メインスレッドで取得した
コネクションを受け渡すようにするしかないのではないでしょうか。

2.DAOをSingletonとしており、ひとつのDAOでの
Connectionがシステム全体で共有されてしまう。

これは、DAO自体をThreadLocalなSingletonにするか、
Singletonをやめるかする必要があるのではないでしょうか。
コジコジ
会議室デビュー日: 2006/05/31
投稿数: 6
投稿日時: 2006-06-05 12:01
> フライトさん

ご返答、ありがとうございます。

> どうしてもJTAでトランザクション管理を行わなければならない
> 理由がないのであれば、Springでトランザクション管理を
> 行ってみるのもいいんではないでしょうか?

貴重なご意見、ありがとうございます。
おっしゃるとおり、Spring FrameworkにはPlatformTransactionManagerというトランザクション実装のインターフェースが用意されていますね。
この中のDataSourceTransactionManagerあたりなら、個々のDataSourceに対してではありますがトランザクションを実現することができます。
JTAで上手く解決できなかった際の有力なオプションとして、検討したいと思います。

> 私の個人的な印象ですが、JTAだとSunはインターフェイスを
> 提供しているだけで、実装を配布している会社は複数あって、
> 問題があった時とか調査しにくい印象があるんですよね・・

そうですね。
これは、オープンソースプロダクト全般にいえることかもしれません。
仕様が公開されていても、実装は公開されていない場合もありますしね。
コジコジ
会議室デビュー日: 2006/05/31
投稿数: 6
投稿日時: 2006-06-05 12:16
> zillollさん

たびたびご返答いただきまして、恐縮です。

> 恥ずかしながらThreadLocalを知りませんでした・・・

いえ、私もそんなひとりです。
ThreadLocalなんて、自分で何かを実装する際に利用したことはありませんから。

> 1.並行処理で、ConnectionがThreadLocalになっていない

これが今回の一番の原因でした。
そして後にも書きますが、実はこの点はおかげさまで解消いたしました。

> これは、DAO自体をThreadLocalなSingletonにするか、
> Singletonをやめるかする必要があるのではないでしょうか。

そうですね。
パフォーマンス的にあまりやりたくはないですが、最終的にはSingletonをfalseにするなどの手も考えなくてはなりませんね。
いずれにしろ、確保しなければならない要件は下記になると思っております。

・Connectionは、各アクセス(Thread)ごとにそれぞれ別個の独立したものになること。
・同一Thread内では、同じConnectionが共有されること。
・Connectionの解放が毎回確実に行われること。

スキルアップ/キャリアアップ(JOB@IT)