Hibernate EntityManagerその4
ソースを読みながら、更に細かい部分を確認してみたいと思います。
まずは、EntityManager実装クラスのフィールド関連について。
アプリケーション管理のEntityManager実装であるEntityManagerImplクラスは、内部にSessionをフィールドで保持していました。つまり、HibernateのSessionと同等の扱いをする必要があり、スレッドセーフではないということですね。アプリ管理のEntityManagerを利用する場合は、DIで依存性注入するよりも、EntityManagerFactoryインスタンスから作成して使うべきなんでしょう。
そしてJTA管理のEntityManager実装である、CurrentEntityManagerImplクラスの場合。基本的に動作の度にSessionFactoryからgetCurrentSession()メソッドを使ってSessionを取得してますので、Sessionをフィールドには保持していません。通常の操作範囲内であれば、このEntityManagerはスレッドセーフだと思います。しかし、親クラスを見ると、EntityTransactionクラスの実装であるTransactionImplオブジェクトをフィールドに持っています。これがgetTransaction()メソッドで返されます。TransactionImplクラスの中には、AbstractEntityManagerImplオブジェクトと、HibernateのTransactionオブジェクトを持っていました。Transactionオブジェクトは、SessionのbeginTransaction()メソッドで取得されます。・・・ってことは、このクラスもスレッドセーフではありません。
・・・がしかし、EntityManagerのgetTransaction()メソッドのコメントを読むと・・・
Returns the resource-level transaction object. Throws the IllegalStateException if invoked on a JTA EntityManager or on an EntityManager that has been closed. The EntityTransaction instance may be used serially to begin and commit multiple transactions.
・・・と書いてあるので、JTA EntityManagerのときは例外を返すのが本来の実装のような気がします・・・とりあえず、このメソッドを使わないのであれば、JTA管理のEntityManagerは(Hibernate実装の場合)スレッドセーフとして扱っても問題無さそうです。
次に、JTA連携機能について。SessionFactoryImplクラスのソースの中に、この定義が書いてありました。
「hibernate.current_session_context_class」のプロパティを設定しておらず、かつTransactionManagerLookupの実装クラスのgetTransactionManager()メソッドでTransactionManagerが取得できた場合、SessionFactoryImpl内のCurrentSessionContextインターフェイスの実装がJTASessionContextで新規作成されます。このオブジェクトが、SessionFactory.getCurrentSession()メソッドでSessionオブジェクトを作成する役割を果たします。このソースを読むと・・・
getCurrentSession()でSessionを作成するとき、
「the session should be closed by transaction completion.」
「the session should be flushed prior transaction completion.」
「Indicates that JDBC connection should be aggressively released after each SQL statement is executed.」
と定義され、更に作成時に、TransactionManagerによって、作成したSessionが現在のTransactionに紐付けられます。これはafterCompletion時に紐付けが削除されます。
・・・よって、同一Transaction内の処理には同じSessionを使い、コネクションはSQL出力後すぐにクローズされ、トランザクション完了時にflushとSessionクローズが行われる・・・という状態になる訳ですね。DIコンテナのHibernate連携機能とほぼ同じことをやってるように見えます。とりあえず、安心してJTA連携機能を使っても大丈夫そうかな?
最後に、persistence.xmlを読み込んで@Entityを登録する時の処理について。EJB3Configurationクラス内に定義がありました。
まずは「"META-INF/persistence.xml"」という文字列をキーに、ClassLoaderからURLを取得します。このURLを見て、JARかファイルかディレクトリか判断し、そこから@Entity等の定義をしたクラスや、hbm.xmlファイル等を検索して登録している模様。細かい処理の流れまでは追えませんでしたが、多分、Seasar2.3のコンポーネント自動登録機能と似てる処理なのかな? S2の場合は命名規約による処理ですが、EJB3Configurationの場合はアノテーションによる検索処理になるんですね。成る程・・・
昨日までの処理結果を見て想定していた内容が、ソースを読んだところ、だいたい合っていたことがわかりました。日頃からHibernateのJTA連携機能を使ってる人には何を今更な情報なのかもしれませんが・・・
そういえば、Springの場合はどう連携させるんだろう? 多分、Hibernate連携機能を使うのではなく、今回と同じようにJTAに連携したDataSourceとTransactionManagerをJNDIで取得さえ出来れば、後はDIコンテナはトランザクション実行だけをAOPで定義すればいい筈。ただし、Springの場合JTA実装を別に用意しないといけませんし、S2のJndiContextクラスのように、BeanFactoryに定義されたクラスをInitialContextから取得する機能が無いと、ローカル環境では連携出来ませんが・・・機会があれば一度試してみたいですね。