Spring Framework 入門記 Transactionその4 Declarative Transaction using Metadata

前回でトランザクション編は終わり,今回はメタデータだと宣言したのですが,「Chapter 7. Source Level Metadata Support」を見たところ,すでに「AOPその14」で学習済みのことがほとんどでした.無念だ.うーむ,「5.9.2. Using metadata-driven autoproxying」に7章へのリンクを張っておいてくれれば別途Commons Attributesを学習しに行かなくても済んだかもしれないのに... ま,いっか.
そんなわけで,改めて7章へ取り組むのはやる気Nullなのです.しかし日記である以上,何かしないわけにはいきません.ということで,メタデータを使った宣言的トランザクションをやってみましょう.5〜7章の集大成という感じです.というか,情報が散らばっていて困っちゃってるんですが.


AOPその14」で学習したように,Springでは現在のところApache Jakarta Commons Attributesを使ってメタデータを扱うことができます.Commons Attributesでは,次のようにソースにメタデータを埋め込みます.

/**
 * @@class(constructor-arg, property=value, ...)
 */

では,トランザクションを扱うのにどのようなclassを書くことができるかというと,次のものが用意されているようです.

  • org.springframework.transaction.interceptor.DefaultTransactionAttribute
  • org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
  • org.springframework.transaction.interceptor.RollbackRuleAttribute
  • org.springframework.transaction.interceptor.NoRollbackRuleAttribute

まず最初から.DefaultTransactionAttributeは一番単純なもので,コンストラクタ引数またはプロパティでPROPAGATION LEVELを指定することができます.省略するとPROPAGATION_REQUIREDになります.また,readOnlyなどのプロパティがあります.このメタデータ属性を使った場合,該当のメソッドが例外をスローすると,その例外がRuntimeExceptionまたはError(unchecked例外)ならロールバック,それ以外(checked例外)ならコミットとなります.
その次のRuleBasedTransactionAttributeは,メソッドが例外をスローした場合にロールバックするかしないかを設定できるもので,RollbackRuleAttributeおよびNoRollbackRuleAttributeと組み合わせて使用します.それぞれ,これまで使ってきた'-'や'+'に対応します.なんですが,RuleBasedTransactionAttributeのコンストラクタを見ると,PROPAGATION LEVELを設定するためのintのみを受け取るものがありません.プロパティで設定するしかないようです.
RollbackRuleAttributeおよびNoRollbackRuleAttributeは,コンストラクタで例外クラスを設定することができます.メソッドが該当の例外をスローすると,それぞれロールバックしたりコミットしたりすることを指定します.
このようなメタデータ属性をソースに記述して,Commons Attributes の Attribute Compiler で処理すればいいわけですね.


次に,そのようにして指定されたトランザクションの特性に従って制御をしてくれるAspectが必要です.この場合,すでに「Transactin その2」で学習済みのTransactionInterceptorをそのまま使えるようです.こいつは,メソッドJoinpointに対してどんな特性のトランザクションを制御するかを,TransactionAttributeSourceから取得します.ということで,メタデータで定義された属性を扱うための実装クラスが用意されています.それが

  • AttributesTransactionAttributeSource

です.そんなのもあったんですね.「Transactin その2」の時は気づきませんでしたよ.心より恥じる.
ということで,TransactionInterceptorAttributesTransactionAttributeSourceを組み合わせて autoproxy creator でターゲットにWeavingすることで,メタデータによる宣言的トランザクションができるということのようです.分かったような分からないような? 心より恥じる.
そんな場合は実践です.長々と能書きばかり書いてないで(いつもよりは短い気がしますが),実践あるのみ!


ということで,まずはトランザクショナルなクラス,おなじみのFooトランザクション属性を埋め込んでみましょう.これまでのTransaction編と同じ結果になるように指定してみました.

package study;
import java.sql.ResultSet;
import java.sql.Statement;

import javax.sql.DataSource;

public class Foo {
    private DataSource dataSource;
    public DataSource getDataSource() {
        return dataSource;
    }
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
     */
    public void createTable() throws Throwable {
        Statement st = dataSource.getConnection().createStatement();
        st.execute("create table pair (key varchar, value varchar, primary key(key))");
    }
    /**
     * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute(readOnly=true)
     */
    public String query(String key) throws Exception {
        Statement st = dataSource.getConnection().createStatement();
        ResultSet rs = st.executeQuery("select value from pair where key='" + key + "'");
        rs.next();
        return rs.getString(1);
    }
    /**
     * @@org.springframework.transaction.interceptor.RuleBasedTransactionAttribute()
     * @@org.springframework.transaction.interceptor.NoRollbackRuleAttribute(java.lang.UnsupportedOperationException.class)
     */
    public void insert(String key, String value, Throwable throwable) throws Throwable {
        Statement st = dataSource.getConnection().createStatement();
        st.execute("insert into pair (key, value) values('" + key + "', '" + value + "')");
        if (throwable != null) {
            throw throwable;
        }
    }
    /**
     * @@org.springframework.transaction.interceptor.RuleBasedTransactionAttribute()
     * @@org.springframework.transaction.interceptor.RollbackRuleAttribute(java.io.IOException.class)
     */
    public void update(String key, String value, Throwable throwable) throws Throwable {
        Statement st = dataSource.getConnection().createStatement();
        st.execute("update pair set value='" + value + "' where key='" + key + "'");
        if (throwable != null) {
            throw throwable;
        }
    }
}

createTable()およびquery()ではDefaultTransactionAttributeを指定しています.ともにPROPAGATION LEVELは指定していないので,デフォルトのPROPAGATION_REQUIREDになります.
query()ではさらに,readOnlyプロパティにtrueを指定しています.
insert()およびupdate()では,RuleBasedTransactionAttributeを指定しています.こちらもPROPAGATION LEVELは指定していないので,デフォルトのPROPAGATION_REQUIREDになります.
insert()にはNoRollbackRuleAttributeupdate()にはRollbackRuleAttributeを指定しています.ここで最後に ".class" なんて書いてあるのは,この部分がそのままソースファイルにコピペされるからで,その場合にちゃんとClassインスタンスへの参照になるわけです.
さて,次は定義ファイルです.「Transactin その2」の定義ファイルをベースにしました.

<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="attributes"
    class="org.springframework.metadata.commons.CommonsAttributes"
/>

<bean id="txAttribute"
    class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
    <constructor-arg><ref bean="attributes"/></constructor-arg>
</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>

Transactin その2」のと異なっているのは,txAttributeAttributesTransactionAttributeSourceになっていることと,attributesが追加されていることくらいです.
これを,これまでと同じ実行用クラスでゴー!!!!

 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

よっしゃー!!
ということで,トランザクションメタデータあわせて7章まで完了だということにしてしまいましょう.強引? 心より恥じる.


今後の予定ですが,この週末は新たな学習はお休みして,ここまでの入門記をSpring Padにまとめようと思っています.Founderのjunoさんにも快く了承していただけたので,本家からリンクされているところで恥をさらす目立ってみようかと.むふふ.
ということで編集のルールとか見ていたのですが,結構はてなと違うものなんですね.手作業じゃやってられません.スクリプト書かねば.っていうかどこかに転がってるもの?
それから,この入門記の売りである「無念だ」「心より恥じる」を始めとするZaizen語録を残していいものかも悩み中.現在のSrping Padの雰囲気をぶち壊しそうですからねぇ.あとサンプル中に埋め込まれた特定タレントの名前も? うーむ.


そんなこんなで来週には「Chapter 8. DAO support」から再開して,GW明けくらいかなぁ,「Chapter 9. Data Access using JDBC」を終えた後は,「Chapter 10. Data Access using O/R Mappers」に突入する前に,そこで必要になるHibernateの入門記をやりたいと思います.ひがさんにつつかれているしぃ.(^^;
なのですが,今のところHibernateをどこからダウンロードすればいいかも知りません.無念だ.大丈夫か?>俺


あう! ここに来て,もっと大切なことを忘れていることに気づきました.昨夜リリースされた1.0.1へのバージョンアップを行わずにここまで書いてしまいました.なんてこった... 心より恥じる.