Spring Framework 入門記 Transactionその3 JTA

今日は待ち時間の長い作業をやっているため普段より早めの入門記,今回はJTA(Java Transaction API)を使用します.
JTAといえば,先日書いた「JTA/JTS」なんですが,はぶさんの日記からたどって来てくれた人がたくさんいらしゃるようで.リファ見てる限り過去最高です.それまでは「僕の生きる道」のキーワードからが最高だったのですが,さすがはぶさん,人気ドラマを越えてます.すごいなぁ.
その「JTA/JTS」で書いたように,JTAはX/Open DTPの焼き直しのような仕様で,

UserTransaction
アプリとトランザクションマネージャ間のインタフェース.X/Open DTPのTXに相当.
XAResource
トランザクションマネージャとリソースマネージャ間のインタフェース.X/Open DTPのXAに相当.

という二つの主要なinterfaceがあるのですが,JTAにはもう一つ,

TransactionManager
アプリケーションサーバ(コンテナ)とトランザクションマネージャ間のインタフェース.

というのも用意されています((JTAを実装するには他のinterfaceも理解する必要がありますが,ここでは不要ということで他は無視.)).
X/Open DTPの頃は,TMは事実上TPモニタそのもので,独立したコンポーネントという扱いではなかったのですが,JTAでは一つのコンポーネントとしてプラガブルに扱えるようにデザインされているのですね.
これらのinterfaceのうち,XAResourceは通常JDBCドライバの一部として提供されたりします.そして,UserTransactionTransactionManagerは,JTAの実装が提供します.


Springでは独自のJTA実装を提供していませんが,別途JTAを用意して利用することができるようになっています.
そのためには,前回まで使ってきたDataSourceTransactionManagerに代えて,JtaTransactionManagerを使用します.これも当然,PlatformTransactionManagerimplementsしたクラスです.
JtaTransactionManagerは,次のプロパティを持っています.

  • userTransaction
  • userTransactionName
  • transactionManager
  • transactionManagerName
  • jndiTemplate

このうち,userTransactionNametransactionManagerNameは,JNDIからルックアップしてそれぞれのインスタンスを取得する際に設定します.その場合は,InitialContextの初期化に必要なプロパティ(Propertiesの意味のプロパティ)をjndiTemplateに設定します.
JNDIを使わない場合は,userTransactionおよびtransactionManagerを設定します.
なお,userTransactionimplementsしたクラスがTransactionManagerimplementsしている場合は,transactionManagerまたはTransactionManagerNameの設定は省略できます.これ,逆でもいいと思うんですけどねぇ.インタフェース的にUserTransactionTransactionManagerのサブセットなので,TransactionManagerが設定されていればUserTransactionは設定しなくてもいいっていう方がいいような気がするのですが... 無念だ.
SpringでJTAを使うのに必要なことは以上でOKみたいです.


それで次はJTA実装を用意するわけですが,通常はアプリケーションサーバが提供するJTA実装を使うことが多いのだろうと思われます.しかし今回は,我らがひがさんによるJTA実装,S2JTAをSpringで使ってみることにしましょう.S2JTAはS2のコンテナとは独立しているということで,たぶんSpring上でも使えるとひがさんの03/25の日記に書かれていたので,いつかやらねばと思っていたのです.ついに,その日がやってまいりました!
ということで,S2JTAを見ていきます.
S2JTAはS2コンテナからは独立しているのですが,そのS2JTAをS2コンテナに組み込むための設定ファイルがj2ee-config.xmlです.その解説が「コネクションプーリング」というドキュメントにまとまっています((ConnectionPoolImplxaDataSourceというプロパティはXADataSourceが正しいと思われ>ひがさん)).なるほど.これをそのままSpringの定義ファイルに持っていけばよさげです.
必要なコンポーネントは次の4つです.

org.seasar.extension.jta.TransactionManagerImpl
TransactionManagerimplementsしたクラス.文字通りのトランザクションマネージャです.
org.seasar.extension.dbcp.impl.XADataSourceImpl
XAResourceimplementsしたクラスで,普通(XA非対応)のJDBCドライバをJTAで使えるようにしてくれます.
org.seasar.extension.dbcp.impl.ConnectionPoolImpl
いわゆるコネクションプールで,トランザクションマネージャと連携して,トランザクション終了時にコネクションをプールに戻してくれたりします.
org.seasar.extension.dbcp.impl.DataSourceImpl
DataSourceimplementsしたクラス.コネクションプールをDataSourceとして使うためのアダプタという位置付けみたいです.

このように,トランザクションマネージャと連携するコネクションプール上にDataSourceを用意してくれているので,SpringのDataSourceUtilsのようなものが不要なんですね.さすが.
ちょっと問題になったのは,(2.0.5以前の)S2にはUserTransactionimplementsしたクラスが存在しないこと.S2で使う分には必要ありませんからねぇ.ということで,S2のTransactionManagerImplを次のように修正しました.

public final class TransactionManagerImpl implements TransactionManager, UserTransaction {

先ほども書いたように,UserTransactionTransactionManagerのサブセットなので,新たにメソッドを実装する必要はありません.この修正はS2の新しいリリースに含めてもらえるみたい? です.


さて,それではSpringでS2のJTAを使うための定義ファイルです.基本的に前々回TransactionProxyFactory版をベースに,JTA対応を行いました.こんな感じです.

<!-- S2の設定 -->
<bean id="jtaTM" class="org.seasar.extension.jta.TransactionManagerImpl"/>
<bean id="xaDataSource" class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
    <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>
    <property name="URL"><value>jdbc:hsqldb:.</value></property>
    <property name="user"><value>sa</value></property>
    <property name="password"><value></value></property>
</bean>
<bean id="connectionPool" destroy-method="close" 
    class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
    <property name="transactionManager"><ref bean="jtaTM"/></property>
    <property name="XADataSource"><ref bean="xaDataSource"/></property>
</bean>
<bean id="dataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl">
    <constructor-arg><ref bean="connectionPool"/></constructor-arg>
</bean>

<!-- Spring TMの設定 -->
<bean id="springTM"
    class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction"><ref bean="jtaTM"/></property>
</bean>
<bean id="debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<!-- アプリケーションの設定 -->
<bean id="foo" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="target">
        <bean class="study.Foo">
            <property name="dataSource"><ref bean="dataSource"/></property>
        </bean>
    </property>
    <property name="transactionManager"><ref bean="springTM"/></property>
    <property name="transactionAttributes">
        <props>
            <prop key="insert">PROPAGATION_REQUIRED,+java.lang.UnsupportedOperationException</prop>
            <prop key="update">PROPAGATION_REQUIRED,-java.io.IOException</prop>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
    <property name="postInterceptors"><ref bean="debug"/></property>
</bean>

うん,やっぱりTransactionProxyFactoryの方が素敵!(キラッ)
S2JTAの設定のところは,ほとんどj2ee-config.xmlのパクリです.
そして,SpringのTMとしてJtaTransactionManagerを定義しています.そのuserTransactionプロパティには,S2JTAのTransactionManagerImplを設定しています.これはTransactionManagerでもありますから,transactionManagerプロパティの設定は省略しています.
残りのお試しクラスFoo前回と同じ,実行用のクラスは前々回と同じです.
それを実行すると...

 Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.createTable 以下略
 Debug interceptor: next returned
 - Initiating transaction commit
 Debug interceptor: count=2 invocation=[Invocation: method=[public void study.Foo.insert 以下略
 Debug interceptor: next returned
 - Initiating transaction commit
 Debug interceptor: count=3 invocation=[Invocation: method=[public java.lang.String study.Foo.query 以下略
 Debug interceptor: next returned
 - Initiating transaction commit
 Ebihara
 Debug interceptor: count=4 invocation=[Invocation: method=[public void study.Foo.insert 以下略
 - Initiating transaction commit
 Debug interceptor: count=5 invocation=[Invocation: method=[public java.lang.String study.Foo.query 以下略
 Debug interceptor: next returned
 - Initiating transaction commit
 Yada
 Debug interceptor: count=6 invocation=[Invocation: method=[public void study.Foo.update 以下略
 - Invoking rollback for transaction on method 'update' in class [study.Foo] due to throwable [java.io.IOException]
 - Initiating transaction rollback
 Debug interceptor: count=7 invocation=[Invocation: method=[public java.lang.String study.Foo.query 以下略
 Debug interceptor: next returned
 - Initiating transaction commit
 Yada

かーんぺき!!


と,こんな感じなんですが,これで早くもトランザクション編は終わりにしようかと.まだプログラマティックにトランザクションを扱うとか,アプリケーションサーバ上で使うとかがドキュメントにはあるのですが,あまり興味をもてないもので.心より恥じる.
ということで,次回からは「Chapter 7. Source Level Metadata Support」に進みたいと思います.「AOPその14 Metadata-Driven Autoproxying」でやったメタデータのことなのかな? それにいきます.