Spring Framework 入門記 Transactionその2 Declarative Transaction using the Atuoproxy
いやいや,前回のDataSourceUtils#getConnection(DataSource)
,予想通り不評なようで.当然ですよねぇ.あんなの使わされるのなんて,ご勘弁いただきたいってのが普通でしょう.
そのDataSourceUtils#getConnection(DataSource)
ですが,トランザクションマネージャと連携すること以外に,SQLException
をCannotGetJdbcConnectionException
というuncheckedな例外にするという役割もあるようです.こだわってるなぁ.プライオリティの設定を間違ってないか?
まぁ,その辺については後ほど対処法を考えることにして,今回はautoproxyを使用した宣言的トランザクションに挑みましょう.
私の場合,新しいことを1つ覚えると代わりに4つくらい忘れてしまいます.無念だ.でも,これを読んでいるあなたも仲間に違いない! autoproxyってなんだっけ? って思った人,「AOP その13 AutoProxyCreator」を読んで思い出しましょう.私も読み直しました.心より恥じる.
ま,覚えておけないから日記に書くわけですよ.そんなもんです,はい.
さて,宣言的トランザクションをautoproxyを使って実現するには,TransactionInterceptor
を使います.じつは,前回使用したTransactionProxyFactoryBean
も内部ではこいつをWeavingするわけですが,表には出てこなかったんですよね.裏でこっそりとWeavingされていたのです.しかし,autoproxyを使う場合には,こいつを明示的に用意してあげなければいけないようです.うーん,あっちを立てればこっちが立たず?
そのTransactionInterceptor
ですが,プロパティが2つあります.
transactionManager
transactionAttributeSource
transactionManager
は,このTransactionInterceptor
が使用するトランザクションマネージャです.そのまんまですね.
そしてtransactionAttributeSource
ですが,これはどのメソッドにどんな性質のトランザクションを適用するかを設定するもので,次の実装クラスが用意されています.
MatchAlwaysTransactionAttributeSource
NameMatchTransactionAttributeSource
MethodMapTransactionAttributeSource
まずMatchAlwaysTransactionAttributeSource
ですが,これはターゲットになるBeanのメソッドを片っ端からトランザクショナルにしてしまうという豪快なもので,次のプロパティを持っています.
-
transactionAttribute
このプロパティはTransactionAttribute
というinterface
型です.これは前回TransactionProxyFactoryBean
のtransactionAttribute
に設定したもので,あの"PROPAGATION_REQUIRED
"とか指定したやつです.
次のNameMatchTransactionAttributeSource
は,個々のメソッドをどんな性質でトランザクショナルにするかを指定できるというもので,次のプロパティを持っています.
-
properties
このプロパティはもちろんProperties
型で,そのキーはメソッド名(末尾にワイルドカード'*
'を使うことができます),値はトランザクションの性質を示す文字列です.これもまた,前回TransactionProxyFactoryBean
のtransactionAttribute
に設定したものと同じです.
最後のMethodMapTransactionAttributeSource
ですが,これは基本的にNameMatchTransactionAttributeSource
と同じみたいです.ただし,ProeprtyEditor
が用意されているため,文字列で一気に表現できるという特徴があります.つまり TransactionInterceptor
の <property>
要素に <bean>
要素ではなく <value>
要素を書くと,このMethodMapTransactionAttributeSource
が設定されるというわけです.その文字列は,method=transactionAttribute
という行の並びです.method
は末尾にワイルドカード'*
'を使うことができます.transactionAttribute
はこれまたトランザクションの性質を示す文字列です.
さて,前回もちょっと使ったトランザクションの性質を示す文字列ですが,これの正体はTransactionAttribute
というinterface
らしいです.そして,文字列からその実装クラスのインスタンスに変換するためのPropertyEditor
である,TransactionAttributeEditor
が用意されています.そのJavaDocに,この文字列表現の説明がちゃんとありました.前回はそこまで見ませんでした.心より恥じる.ともかくその文字列は,次のように記述できるようです.
PROPAGATION_NAME,ISOLATION_NAME,readOnly,+Throwable,-Throwable
このうち,PROPAGATION_NAME
だけが必須で,その他は任意です.また,順序も任意らしいです.
+Throwable
と-Throwable
は任意の数だけ記述できます.この意味は前回も書いたように,JoinpointのメソッドがそのThrowable
をスローした場合に,トランザクションをコミットする('+
')か,ロールバックする('-
')かを指定します.
ということで,今回必要な予習はこれくらいのようです.後は使うだけなのですがその前に! DataSourceUtils
問題の対処を考えましょう.
誰がどう考えたって,DataSourceUtils
をアプリで使いたいとは思わないはず.Springに依存しちゃいますからね.じゃあどうればいいか? こんな手が思いつきます.
DataSource
のラッパーを作ってそのgetConnection
でDataSourceUtils
を使う.DataSourceTransactionManager
に代わるPlatformTransactionManager
を実装する.- S2のJTAとコネクションプールを使う.
今回は,1番目の方法を試してみましょう.魅力的な3番目の方法は,次回挑戦しようと思います.
そのラッパーですが,こんな感じになりました.
package study; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.TransactionSynchronizationManager; public class DataSourceWrapper implements DataSource { private DataSource actualDataSource; public Connection getConnection() throws SQLException { if (!TransactionSynchronizationManager.hasResource(this)) { return actualDataSource.getConnection(); } return DataSourceUtils.getConnection(this); } public Connection getConnection(String userName, String password) throws SQLException { throw new UnsupportedOperationException("getConnection(String, String)"); } public DataSource getActualDataSource() { return actualDataSource; } public void setActualDataSource(DataSource actualDataSource) { this.actualDataSource = actualDataSource; } public int getLoginTimeout() throws SQLException { return actualDataSource.getLoginTimeout(); } public void setLoginTimeout(int seconds) throws SQLException { actualDataSource.setLoginTimeout(seconds); } public PrintWriter getLogWriter() throws SQLException { return actualDataSource.getLogWriter(); } public void setLogWriter(PrintWriter writer) throws SQLException { actualDataSource.setLogWriter(writer); } }
こいつのgetConnection()
では,DataSourceUtils
と同じようにTransactionSynchronizationmanager
に自分が登録済みかどうかをチェックしています.登録されていなければ(DataSourceUtils
から呼び出された場合)本当のDataSource
からConnection
を取得します.登録済みのとき(アプリから呼び出された場合)は,DataSourceUtils
からConnection
を取得します.
これでアプリは普通にDataSource#getConnection()
ができる! はず!
では,autoproxyによる宣言的トランザクションを実践しましょう.
まずトランザクションAspectをWeavingされるFoo
ですが,基本的に前回と同じです.ただし,せっかく作ったDataSourceWrapper
を使うために,
Statement st = DataSourceUtils.getConnection(dataSource).createStatement();
を
Statement st = dataSource.getConnection().createStatement();
に変更しています.
そして定義ファイル.
<bean id="actualDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property> <property name="url"><value>jdbc:hsqldb:.</value></property> <property name="username"><value>sa</value></property> <property name="password"><value></value></property> <property name="minIdle"><value>1</value></property> </bean> <bean id="dataSource" class="study.DataSourceWrapper"> <property name="actualDataSource"><ref bean="actualDataSource"/></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"><ref bean="dataSource"/></property> </bean> <bean id="txAttribute" class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource"> <property name="properties"> <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> </bean> <bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager"><ref bean="transactionManager"/></property> <property name="transactionAttributeSource"><ref bean="txAttribute"/></property> </bean> <bean id="debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"><value>foo</value></property> <property name="interceptorNames"><value>txInterceptor,debug</value></property> </bean> <bean id="foo" class="study.Foo"> <property name="dataSource"><ref bean="dataSource"/></property> </bean>
うーん,ラクチンなはずのautoproxyを使ったら逆に複雑になったような? 無念だ.
というのもですね,以前autoproxyを学習したときには気づかなかったのですが,autoproxyを使った場合,<property>
要素の中で<bean>
要素を記述するとエラー(例外)になっちゃうみたいなんですね.なので,一つ一つ個別に定義しなきゃいけなくて,こんな大変な事態になってしまったというわけで.要するにSpringを使う場合,定義ファイルは優しくもなければ易しくもないというのが結論です.無念だ.
それはともかく,今回はBeanNameAutoProxyCreator
を使っています.そこでターゲットとしてfoo
を,AspectとしてtxInterceptor
とdebug
を指定しています.
これを前回と同じ実行用クラスで実行!!!!
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
バッチリ,前回と同じ結果になりました.DataSourceUtils
を忘れてもよさそうです.
ということでautoproxyの使い方も一応マスターできましたが,たいして記述が簡潔になるわけでもないのに不明瞭さが増すだけという感じがするので,基本的にはTransactionProxyFactoryBean
を使っていきたいと思います.
さて,せっかくのTransaction編だというのに,一度もJTAを使っていないのはもぐりでしょう.どう考えたって,JTAを使わずしてTransaction編を終えるわけにはいきません.そこで,次回はJTAです.Spring的にはJOTMとか使えってことだと思われますが,はてな的には断然S2です.S2のJTAをSpring上で使うという,なかなか楽しげなことをやってみようと思います.うん,その方が素敵!(キラッ)