Google
 
Web andore.com
Powered by SmartDoc

8. トランザクション管理 (Ver 1.2.7)

8.1 Springのトランザクション抽象化

Springでは一貫したトランザクション管理の抽象化が提供されている。この抽象化はSpringで提供している抽象化の中でも最も重要なものの1つであり、下記のような利点が得られる。

伝統的に、J2EE開発者にはトランザクション管理を行うのに2つの選択肢、グローバルトランザクションを使うか、ローカルトランザクションを使うか、がある。グローバルトランザクションはJTAを使ってアプリケーションサーバによって管理される。ローカルトランザクションはリソースに依存する。例えば、あるトランザクションがJDBCコネクションに関連づけられているとする。この選択には深い意味合いが込められている。グローバルトランザクションには複数のトランザクションリソースに対して実行できるようにする機能がある。(注目されるのは、ほとんどのアプリケーションは単一のトランザクションリソースを用いているということだ。)ローカルトランザクションでは、アプリケーションサーバではトランザクション管理を行わないので複数のリソース間を跨いで正確さを保証することができないのだ。

グローバルトランザクションは重要度が低下している。コードはJTAという(一部にはその例外モデルのために)厄介なAPIを使う必要がある。さらに、JTA UserTransactionは通常JNDIから取得する必要がある。これは、JTAを使うのに、JNDIとJTAの両方を一緒に使う必要がある、ということだ。JTAが通常はアプリケーションサーバ環境でしか使えないので、グローバルトランザクションはアプリケーションコードの再利用性を制限するのは明らかだ。

グローバルトランザクションを用いる好ましい方法は、EJB CMT(コンテナ管理トランザクション)つまり、宣言的トランザクション管理の形式(プログラマティックトランザクション管理と区別して)から使う方法だ。EJB CMTはトランザクション関連のJNDIルックアップを使わなくていいようにする。-- しかしながら、もちろんEJBそのものはJNDIを使わないといけないが。つまり全部ではないが、トランザクションを制御するためのJavaコードをほとんど書かなくていいようになる。この重要度の低下は、CMTが(明らかに)JTAやアプリケーションサーバ環境と結びついているからだ。しかも、もしビジネスロジックをEJB、あるいは少なくともトランザクションに関係するEJBファサードでしか実装しないと決めた場合にしか使えない。EJB周りのネガティブファクタが一般にはとても大きいので、宣言的トランザクション管理という代案がある場合は、あまり魅力的な案にはなりえないのだ。

ローカルトランザクションを使うのはこれよりは簡単かもしれないが、これとは別の面で重大な欠点がある。ローカルトランザクションは、複数のトランザクションリソースを跨いだ処理ができないので、プログラミングモデルに介入しがちだ。例えば、JDBCコネクションを使っているトランザクションを管理するコードはグローバルJTAトランザクションと一緒に使うことができないのだ。

Springでは、これらの問題が解決される。アプリケーション開発者が任意の環境で一貫したプログラミングモデルを使うことができるようにする。コードを1回書けば、異なる環境で異なるトランザクション管理ストラテジの利便性が得られるのだ。Springでは、宣言的トランザクション管理とプログラマティックトランザクション管理の両方が容易されている。ほとんどのユーザにとっては、宣言的トランザクション管理は好ましいものであり、ほとんどのケースでお勧めできるものなのだ。

プログラマティックトランザクション管理と共に、開発者はSpringのトランザクション抽象化を使うことができる。これは、下のレイヤにある任意のトランザクションインフラ上で走らせることができるというものだ。好ましい宣言的モデルがあれば、開発者は、トランザクション管理に関するコードをほとんど、あるいは全く書く必要が通常なくなり、したがってSpringあるいはその他のトランザクションAPIに依存しなくなるのだ。

8.2 トランザクションストラテジ

Springのトランザクション抽象化の鍵は、トランザクションストラテジに関する考え方だ。

これは、以下に示したorg.springframework.transaction.PlatformTransactionManagerインタフェースに取り入れられている。

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

これは、プログラマティカルに利用することもできるが、本来はSPIインタフェース。Springの哲学に合致してインタフェースであるという点に注意してほしい。したがって、必要であれば、簡単にモックにするか、スタブを用意することができる。JNDIのようなルックアップストラテジに結びつけられてもいない。PlatformTransactionManagerの実装は他のSpring IoCコンテナのオブジェクトと同じように実装されている。これは、たとえJTAと一緒に動かす場合でさえも、それだけで有益な抽象化ができるという利点がある。つまり、トランザクションコードをJTAだと難しいものでももっと簡単にテストすることができるのだ。

Springの哲学どおりに、TransactionExceptionは未チェックだ。トランザクションインフラでの失敗は、ほとんどが致命的なものだ。アプリケーションコードがこの失敗から回復できるような稀なケースでは、アプリケーション開発者はTransactionExceptionをキャッチして処理を行うかを決めることもできる。

getTransaction()メソッドは、TransactionDefinitionパラメータにしたがってTransactionStatusオブジェクトを返す。この返されるTransactionStatusは新しいトランザクションかあるいは既存のトランザクションを表している(もし、カレント呼び出しスタックにマッチするトランザクションが存在すれば)。

J2EEトランザクションコンテキストでは、TransactionStatusは処理スレッドと関連づけられている。

TransactionDefinitionインタフェースは、下記のように指定されている。

これらのセッティングは標準の概念を反映する。必要であれば、トランザクションの隔離レベルや、その他の中核となるトランザクションの概念について議論しているリソースを参照してほしい。これらの中核となる概念を理解することは、Springやその他のトランザクション管理ソリューションを使う上で必要なものなのだ。

TransactionStatusインタフェースはトランザクションコードで、単純なトランザクションの実行やトランザクションの状態問い合わせを制御するための簡単な方法だ。この概念は、全てのトランザクションAPIに共通しているように、よく知っているべきだろう。

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

Springのトランザクション管理がどれだけ使われようとも、PlatformTransactionManagerの実装を定義することは必要不可欠だ。Springのうまいやり方では、この重要な定義はInversion of Controlを使って作られている。

PlatformTransactionManagerの実装は、JDBC,JTA,Hibernateなど、それが動作する環境に関して知っている必要がある。

SpringのjPetStoreサンプルアプリケーションのdataAccessContext-local.xmlから抜き出した下記の例では、ローカルのPlatformTransactionManagerの実装がどのように定義されるかを示したものだ。これは、JDBCと一緒に動作する。

我々は、JDBCのDataSourceを定義して、そのDataSourceへのリファレンスを与えてSpringのDataSourceTransactionManagerを使わなければならない。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" \
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

PlatformTransactionManagerの定義はこのようになる。

<bean id="txManager" 
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

もしJTAを使うのであれば、同じサンプルアプリケーションのdataAccessContext-jta.xmlファイルのように、JNDIから取得したDataSourceコンテナと、JtaTransactionManagerの実装を使わなければならない。このJtaTransactionManagerDataSourceや、他の特定のリソースについて知っている必要はなく、コンテナのグローバルトランザクション管理のように使う。

<bean id="dataSource" \
    class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/jpetstore" />
</bean>

<bean id="txManager" \
    class="org.springframework.transaction.jta.JtaTransactionManager"/>

Hibernateのローカルトランザクションは、SpringのPetClinicサンプルアプリケーションから抜き出した以下の例のように、簡単に使うことができる。

この場合、HibernateのLocalSessionFactoryを定義する必要がある。このアプリケーションコードは、Hibernateセッションを取得するのに利用する。

DataSourceビーン定義は上述した例とよく似たものなので、ここでは割愛する(もし、それがDataSourceコンテナであれば、コンテナではなく、トランザクションを管理するSpringのように非トランザクションになるはずだ)。

このケースにおいてこの「txManager」ビーンはHibernateTransactionManagerクラスだ。DataSourceTransactionManagerDataSourceへの参照を必要とするのと同様、このHibernateTransactionManagerSessionFactoryへの参照を必要とする。

<bean id="sessionFactory" \
    class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mappingResources">
      <list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
        </props>
    </property>
</bean>

<bean id="txManager" 
    class="org.springframework.orm.hibernate.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

HibernateやJTAを使えば、JDBCや他のリソースストラテジと同じように簡単にJtaTransactionManagerを使うことができる。

<bean id="txManager" \
    class="org.springframework.transaction.jta.JtaTransactionManager" />

これはグローバルトランザクションであり、任意のトランザクションリソースを集めることができるので、これが任意のリソース用JTAを設定するのと全く同じことである、という点に注意してほしい。

この全ての場合において、アプリケーションコードは全く変更する必要はない。単に設定を変更するだけで、たとえ、その変更がローカルトランザクションからリモートトランザクションへの変更であったり、あるいはその逆であってもトランザクションの管理方法を変更することができる。

8.3 トランザクションでのリソース同期

異なるトランザクションマネージャがどういう風に生成されるか、また、トランザクションと同期させないといけない関連するリソースとどうやってリンクさせるか(つまり、DataSourceTransactionManagerをJDBCのDataSourceに、HibernateTransactionManagerをHibernateのSessionFactoryに、など)について、今明らかにすべきだ。しかしながら、アプリケーションコードが直接あるいは間接的に永続性API(JDBC、Hibernate、JDOなど)を使って、どうやってこれらのリソースを取得し、適切に扱うのか、適切なPlatformTransactionManagerを使って適切な生成/再利用/クリーンアップ、およびトランザクション同期のトリガをかける(オプション)ことを保障するのかという疑問が残る。

8.3.1 高レベルアプローチ

望ましい方法は、Springの最も高レベルな永続性インテグレーションAPIを使うことだ。これらは、ネイティブのAPIを置き換えるものではなく、内部的にリソースの生成/再利用、クリーンアップ、オプションのリソースとのトランザクション同期、例外のマッピングを行う。よって、ユーザのデータアクセスコードはこれらに関することは何も意識する必要はなく、純粋に決まり文句ではない永続ロジックに集中することができる。通常、同じテンプレートアプローチは、JdbcTemplateHibernateTemplateJdoTemplateなどのようなクラスとして、すべての永続性APIがサポートされている。これらの統合クラスの詳細については、は本マニュアルで後述してある。

低レベルアプローチ

さらに低レベルには、(JDBC用の)DataSourceUtils、(Hibernate用の)SessoinFactoryUtils、(JDO用の)PersistenceManagerFactoryUtilsなどがある。アプリケーションコードで直接ネイティブの永続性API用リソース型を扱いたい場合、これらのクラスがSpringで管理された適切なインスタンスが取得し、(オプションで)トランザクションがそのリソースと同期し、処理中に発生した例外を一貫したAPIに適切にマッピングされることを保証する。

例えば、JDBCでは、DataSourcegetConnection()メソッドを叩く伝統的なJDBCのやり方の代わりに、下記のようにSpringのorg.springframework.jdbc.datasource.DataSourceUtilsを叩く。

Connection conn = DataSourceUtils.getConnection(dataSource);

既存のトランザクションが存在し、すでに同期した(リンクされた)コネクションがある場合、そのインスタンスが返される。そうでなければ、そのメソッド呼び出しは、新しいコネクションを生成する契機となる。これは、(オプションで)任意の既存のトランザクション同期し、同じトランザクション中でに後で再利用可能になる。これには任意のSQLExceptionがSpringのCannotGetJdbcConnectionException --これはSpringのチェックなしDataAccessException階層のひとつである--にラップされるという追加された利点がある。これは、SQLExceptionから簡単に得られる以上の情報が得られ、たとえ、異なる永続テクノロジであっても、データベースを跨いだポータビリティが保証される。

これは、Springのトランザクション管理(トランザクション同期はオプションだ)がなくてもちゃんと動作するので、トランザクション管理にSpringを使っていようが使っていまいが、この機能を利用することができることは、念を押しておくべきだろう。

もちろん、SpringのJDBCサポートやHibernateサポートを一度でも使ったことがあれば、直接適切なAPIを使うよりSpringの抽象化を使う方が気持ちよく仕事ができるので通常はDataSourceUtilsや他のヘルパークラスを好んでは使わないだろう。例えば、もし、JDBCを使うのを単純にするためにSpringのJdbcTemplatejdbc.objectパッケージを使うのであれば、正しいコネクションの検索がバックグランドで走り余計なコードを書く必要がなくなるだろう。

これらの低レベルなリソースアクセスクラスの詳細については、本マニュアルで後述している。

TransactionAwareDataSourceProxy

一番低いレベルには、TransactionAwareDataSourceProxyクラスがある。これは、ターゲットのDataSourceに対するプロキシであり、Springで管理されたトランザクションのアウェアネスを追加するためにターゲットのDataSourceをラップする。この点で、J2EEサーバで提供されるトランザクションのJNDI DataSourceに似ている。

標準のJDBC DataSourceインタフェースの実装が叩かれ、渡されないといけない既存のコードが存在するときを除いて、このクラスが使われる必要はなく、また望ましくない。そういう場合は、このコードを利用可能であり、なおかつSpringで管理するトランザクションにジョインすることもできる。前述したこれよりも上のレベルの抽象化を使って自分の新しいコードを書く方が望ましい。

詳細については、TransactionAwareDataSourceProxyのJavadocを参照して欲しい。

8.4 プログラマティックなトランザクション管理

Springでは2つの方法によるプログラマティックなトランザクション管理が提供されている。

我々は通常は、1つめの方法をお勧めしている。

2つめのやり方はJTAのUserTransactionのAPIを使うのと同じだ(でも、例外のハンドリングの煩わしさは解消されているが)。

8.4.1 TransactionTemplateを使う

TransactionTemplateでは、JdncTemplateHibernateTemplateのような他のSpringテンプレートと同じやり方が採用されている。これはアプリケーションコードの中でリソースの獲得/開放の処理をしないでいいようにコールバック方式を使っている。(try/catch/finallyももういらない。)他のテンプレートと同じように、TransactionTemplateもスレッドセーフである。

トランザクションコンテキストの中で実行しないといけないアプリケーションコードはこのようになる。値を返すのにTransactionCallbackを使ってもかまわないという点に注目して欲しい。

Object result = tt.execute(new TransactionCallback() {
    public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
    }
});

返り値がない場合は、このようにTransactionCallbackWithoutResultを使うこと。

tt.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

コールバック中のコードはTransactionStatusオブジェクトにあるsetRollbackOnly()メソッドを叩けばトランザクションをロールバックすることができる。

TransactionTemplateを使いたいアプリケーションクラスは、PlatformTransactionManagerにアクセスしなければならない。これは通常は、JavaBeanプロパティかあるいはコンストラクタ引数として公開されている。

このようなクラスをモックやPlatformTransactionManagerスタブを使ってユニットテストを実施するのは簡単だ。そこではJNDIルックアップやスタティックマジックは用いない。これは単純なインタフェースであり、通常のように、Springを用いてユニットテストを簡単にすることができる。

8.4.2 PlatformTransactionManagerを使う

さらに、トランザクションを管理するのにorg.springframework.transaction.PlatformTransactionManagerを直接利用してもよい。使おうとするPlatformTransactionManagerの実装をビーンリファレンスからビーンへ単に渡すだけだ。そして、TransactionDefinitionTransactionStatusオブジェクトを使って、トランザクションの初期化、ロールバック、コミットを行う。

DefaultTransactionDefinition def = new DefaultTransactionDefinition()
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);

try {
    // execute your business logic here
} 
catch (MyException ex) {
    txManager.rollback(status);

    throw ex;
}
txManager.commit(status);

8.5 宣言的トランザクション管理

Springでは宣言的トランザクション管理も提供している。これはSpring AOPによって可能となっているものではある。ところがトランザクション的アスペクトコードはSpringによるものであり、決まりきったやり方で利用される。AOPの概念はこういったコードを有効に使うためものではない、と一般に理解されている。

Springユーザのほとんどは、宣言的トランザクション管理を選んでいる。これは、アプリケーションコードへのインパクトを最小限に抑えるための1つの手段であり、不可侵な軽量コンテナにおける理想と最も合致しているのである。

ここで、EJB CMTを念頭においてSpringの宣言的トランザクション管理との類似点や相違点についての説明を始めることは有効かもしれない。これらの基本的なアプローチは似たようなものだ。つまり、トランザクションの振る舞い(あるいは欠けているもの)を個々のメソッドに落とし込むことができる。また必要であれば、setRollbackOnly()をトランザクションコンテキストで呼ぶことも可能だ。異なっている点は以下のとおり。

ロールバックルールの概念は重要だ。このルールにより、どの例外(もしくはthrowable)により自動ロールバックを発生させなければならないかを指定することができる。我々はこれをJavaのコードでではなく、設定の中で宣言的に指定する。よって、カレントのトランザクションをロールバックするのにTransactionStatusオブジェクトのsetRollbackOnly()を叩くことができる場合でも、MyApplicationExceptionが発生したら必ずロールバックするというルールを指定することができる。これはビジネスオブジェクトをトランザクションインフラに依存させなくてもかまわないという十分な利点になるのだ。例えば、トランザクションあるいはその他の任意のSpring APIをインポートする必要がないのだ。

EJBのデフォルトの振る舞いではEBコンテナはシステム例外(通常はランタイム例外)が発生した場合は自動的にトランザクションをロールバックさせるようになっているが、EJBのCMTはアプリケーション例外(java.rmi.RemoteException以外のチェック済み例外)が発生した場合に、トランザクションをロールバックを自動的には行わない。Springでは宣言的トランザクション管理のデフォルトの振る舞いはEJBの規約(ロールバックは未検査の例外のみ自動で行う)に従っているが、これのカスタマイズが有用であることが多い。

我々のベンチマークでは、Springの宣言的トランザクション管理のパフォーマンスはEJBのCMTよりも勝っている。

Springでトランザクションのプロキシを設定する通常の方法は、トランザクションのプロキシを生成するためにTrnasactionProxyFactoryBeanを使う方法だ。このファクトリビーンは、単に、Springの汎用ProxyFactoryBeanの、ターゲットオブジェクトをラップするためのプロキシの生成を追加した特別版で、通常TransactionInterceptorを自動で生成し、このプロキシにアタッチし、決まり文句のコードを減らしてくれる。(ProxyFactoryBeanのように、プロキシから適用させるために他のインタセプタや、AOPアドバイスを指定してもよい点には注意してほしい)。

TransactionProxyFactoryBeanを使う場合、何よりもまず最初に、target属性でトランザクション的プロキシにラップするターゲットオブジェクトを指定する必要がある。このターゲットオブジェクトは、通常POJOビーン定義だ。また他にも適切なPlatformTransactionManagerへの参照を指定する必要がある。最後に、transaction attributesを指定しなければならない。トランザクション属性には(上述したように)、どこに適用するのかと同様にどのトランザクションセマンティクスを使いたいのかを含める。ここで、下記の例について考えてみよう。

<!-- this example is in verbose form, see note later about concise for \
    multiple proxies! -->
<!-- the target bean to wrap transactionally -->
<bean id="petStoreTarget">
  ...
</bean>

<bean id="petStore"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="txManager" />
    <property name="target" ref="petStoreTarget" />
    <property name="transactionAttributes">
        <props>
<prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

トランザクションプロキシはそのターゲットのインタフェースを実装する。この場合であればpetStoreTargetというIDをもつビーンだ。 (CGLIBを使えば、ターゲットクラスの非インタフェースメソッドを同様にトランザクションを考慮してプロキシすることができるという点に注意してほしい)。ターゲットがどのインタフェースも実装していない場合はこれは自動的に起こるが、強制的にいつも起こるように"proxyTargetClass"プロパティをtrueにセットする。もちろん通常は、クラスではなく、インタフェースでプログラムしたいのだが。)特定のターゲットインタフェースのみをプロキシするために、proxyInterfacesプロパティを使ってトランザクションを考慮したプロキシを制限することは可能(であり、通常はいい考え)だ。また、org.springframework.aop.framework.ProxyConfigから継承したいくつかのプロパティを使ってTransactionProxyFactoryBeanの振る舞いをカスタマイズし、すべてのAOPプロキシファクトリで共有することも可能だ。

トランザクションのインタセプタは究極的には、特定のクラスの特定のメソッドに適用されるセマンティクスを定義しているトランザクションの属性を知るために(TransactionAttributeオブジェクトの形式で)SpringのTransactionAttributeSourceインタフェースを実装したオブジェクトを利用する。最も基本的な方法は、TransactionAttributeSourceインタフェース(Springにはいくつか実装が用意されている)を実装しているビーンを生成するためのプロキシを生成するときにこのTransactionAttributeSourceインスタンスを指定し、それを参照する(あるいは内部クラスとしてそれをラップする)プロキシファクトリビーンのtransactionAttributeSourceプロパティを直接設定することだ。反対に、このプロパティにテキスト文字列を設定して、Springにあらかじめ登録されているTransactionAttributeSourceEditorが自動的にそのテキスト文字列をMethodMapTransactionAttributeSourceインタフェースに変換するのを利用するのもよい。

しかしながら、この例で示したように、ほとんどのユーザはそうではなくtransactionAttributesプロパティを設定することによりトランザクションの属性を定義する。このプロパティはJava.util.Properties型をもっていて、内部的にNameMatchTransactionAttributeSourceオブジェクトに変換される。

上述した定義でもわかるように、NameMatchTransactionAttributeSourceオブジェクトには、名前/値のペアのリストが保持されている。各ペアのキーはメソッド、あるいはトランザクションのセマンティクスを適応させるメソッド(オプションで"*"のワイルドカードが使用可能)である。ここでのメソッド名は、パッケージ名と一致しないが、ラップされるターゲットオブジェクトのクラスに関連したものとみなされる。この例のようにPropertiesの値として指定する場合、TransactionAttributeEditorにより定義されているように、文字列フォーマットで指定する。このフォーマットは以下のようなものである。

PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2

プロパゲーション設定とアイソレーションレベルの設定のフォーマットについては、org.springframework.transaction.transactionDefinitionクラスのJavaDocを参照してほしい。この文字列フォーマットは同じ値のIntegerの定数名と同じものだ。

この例で、insert*のマッピングにロールバックルールが含まれていることに注意してほしい。-MyCheckedExceptionがここで追記されているのは、メソッドがMyCheckedExceptionあるいはそのサブクラスをスローしたら、トランザクションが自動的にロールバックされることを示している。ここではコンマで区切れば多重にロールバックルールを指定できる。-プレフィクスは強制ロールバック、+プレフィクスはコミットを表す。(これは本当に何をやっているかをわかっていれば、未検査例外の場合でさえもコミットしてもよいのだ)。

TransactionProxyFactoryBeanによって、「preInterceptors」と「postInterceptors」プロパティを使って、interceptionの振る舞いを追加するために、オプションでプレアドバイス、ポストアドバイスを設定することができる。任意の数のプレアドバイス、ポストアドバイスを設定することが可能で、アドバイザ(ポイントカットを含めることができるような場合)や、メソッドインタセプタ、あるいは今のSpringのコンフィグレーションでサポートされている任意のアドバイス型(ThrowsAdvice,AfterReturuningAdviceもしくはBeforeAdviceのような、デフォルトでサポートされているもの)でもかまわない。おれらのアドバイスは、共有インスタンスモデルをサポートできなければならない。もしステートフルミックスインのような高度なAOP機能がトランザクションプロキシに必要であれば、TransactionProxtFactoryBeanといった便宜的なプロキシクリエータではなく、通常は汎用のorg.springframework.aop.framework.ProxyFactoryBeanを使うのがベストだ。

注意:ほとんど同じようなトランザクションプロキシを複数生成する必要がある場合、上記のような形のTransactionProxyFactoryBean定義を使うことのは過度に冗長に見えるかもしれない。セクション6.7 "簡潔なプロキシ定義"にも述べたように、トランザクションプロキシ定義の冗長性を大幅に減らすために内部ビーン定義と一緒に親子ビーン定義の利点を使いたいと思うだろう。

8.5.1 トランザクション境界設定のためのソースアノテーション

XMLベースのtransaction attribute sourceの定義は便利で、どんな環境でも動作するが、Java5以降に乗り換える意思があるのであれば、アトリビュートソースの代わりにSpringがサポートするトランザクションアノテーションをJDK標準フォーマットの中で使うことを検討したくなるのは確かだと思う。

Javaのソースコード中に、トランザクションセマンティクスを直接的明示することで、影響を受けるコードに密接な宣言をすることになるが、通常は望ましくない関連ほどには危険性はないので、トランザクションとしてデプロイされるコードは通常この方法でデプロイするのが普通だ。

8.5.1.1 トランザクションアノテーション

org.springframework.transaction.annotation.Transactionalアノテーションは、インタフェース、インタフェースメソッド、クラス、あるいはクラスメソッドがトランザクションのセマンティクスを持っていることを示すのに利用される。

@Transactional
public interface OrderService {

void createOrder(Order order);
List queryByCriteria(Order criteria);

このアノテーションは、インタフェース、クラス、あるいはメソッドがトランザクションであることを指定する。デフォルトのトランザクションセマンティクスは、read/write、PROPAGATION_REQUIREDISOLATION_DEFAULTTIMEOUT_DEFAULTで、ExceptionではなくRuntimeException発生時にロールバックするというものである。

アノテーションのオプションのプロパティを使えばトランザクションの設定を変更することができる。

Transactionalアノテーションのプロパティ
プロパティ 説明
propagation enum: Propagation オプションのプロパゲーション設定(デフォルトは、PROPAGATION_REQUIRED)
isolation enum: Isolation オプションのアイソレーションレベル(デフォルトはISOLATION_DEFAULT)
readOnly boolean read/writeあるいはリードオンリートランザクション(デフォルトはfalseもしくはread/write)
rollbackFor Classオブジェクトの配列。Trowableからの派生クラスであること。 オプションで指定する、発生したときにロールバックする例外クラスの配列。デフォルトでは、チェック済みの例外ではロールバックせず、未チェックの(RuntimeExceptionから派生した)例外の場合にロールバック
rollbackForClassname クラス名文字列の配列。クラスは、Trowableの派生であること。 オプションで指定する、発生したときにロールバックさせる例外クラスの名前の配列
noRollbackFor Classオブジェクトの配列。Trowableからの派生であること。 オプションで指定するロールバックさせない例外クラスの配列。
noRollbackForClassname クラス名文字列の配列。Trowableからの派生であること。 オプションで指定する、発生したときにロールバックさせない例外のクラス名の配列。

アノテーションはインタフェース定義やインタフェースにあるメソッド定義、クラス定義、あるいはクラスに定義されているメソッド定義の前に書く。インタフェースと、そのインタフェースを実装したクラスの両方に書かれるかもしれない。メソッドのトランザクションセマンティクスを評価する際には、継承階層の最も下位で評価される。

8.5.1.1.1. Transactionalアノテーションの例

あるクラスをアノテートする定義:

public class OrderServiceImpl implements OrderService {

  @Transactional
  void createOrder(Order order);
  public List queryByCriteria(Order criteria);

}

下記の例では、インタフェースはリードオンリートランザクションとしてアノテートされる。これは、デフォルトでメソッドに対して設定される。このcreateOrderメソッドのアノテーションは、このメソッドをオーバライドし、read/writeにトランザクションを設定し、(RuntimeExceptionに関するデフォルトのロールバックルールに加えて)DuplicateOrderIdException(おそらくチェックされていない例外)がスローされた場合にトランザクションをロールバックするように指定している。

@Transactional(readOnly=true)
interface TestService {

  @Transactional(readOnly=false,
               rollbackFor=DuplicateOrderIdException.class)
  void createOrder(Order order) throws DuplicateOrderIdException ;

  List queryByCriteria(Order criteria);
}

このインタフェースを実装するクラス定義もこの設定が、クラス、もしくはメソッドに対しオーバライドされる点に注意してほしい。

8.5.1.1.2. Transactionalアノテーションを適用するようにSpringに伝える

このアノテーションそのものをインスタンスをインタフェースやクラスの要素に追加しても実装クラスがトランザクションにラッピングされない。Springに、トランザクションのプロキシをそのアノテーションを持つクラスに生成するように伝えてやらないといけないのだ。

キーは、org.springframework.transaction.annotation.AnnotationTransactionAttribteSourceクラスの利点を生かし、クラスファイルからAnnotationsフォーマットのトランザクション属性を読むことだ。TransactionProxyFactoryBeanを使った前述の例で、テキスト形式でトランザクション属性を指定するTransactionAttributesプロパティAnnotationTransactionAtributeSourceを指定するTransactionAttributeSourceプロパティを直接使って書き換える。

<bean id="petStore" \
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> \
  <property name="transactionManager" ref="txManager"/>
  <property name="target" ref="petStoreTarget"/>
  <property name="transactionAttributeSource">
<bean \
    class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> \
  </property>
</bean>

TransactionAttributeSourceプロパティは各プロキシインスタンスごとに変更する必要はないので、コードのコピーを避けるために親のあるいは子のビーン定義を使う場合、このプロパティは親定義に基づいて設定されるだけで忘れられ、アトリビュートソースはは各クラスファイルから正しい設定を読み込むので、子ビーンの中でオーバライドする必要がない。

Transactionalアノテーションが適用されることを保証するためにAPOを使う

前述した例は理想のものよりもまだ余計なものがある。クラスファイル中のアノテーション自体が、プロキシがアノテートされたクラスのために作成されるのに必要であるという表示として使われる場合、(ターゲットビーンを指定するための)プロキシごとのXMLは原則的に必要ない。

もっとAOPにフォーカスしたアプローチだと、 (ターゲットビーンごとではなく、一回しか使われない)決まり文句のMLを減り、自動的にプロキシがすべてのクラス用にTransactionalアノテーションと一緒に生成されることを保証することができるようになる。Spring AOPは前の章で詳細に解説してあるので、一般的なAOPのドキュメントとして参照すべきであるが、キーポイントはDefaultAdvisorAutoproxyCreatorBeanPostProcessorを使うことだ。これは、ビーンのポストプロセッサなので、生成されると同時に、生成されたすべてのビーンを参照する機会を持っている。もし、ビーンにTransactionalアノテーションが含まれていると、これをラップするのにトランザクショナルプロキシが自動で生成される。

<bean \
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> \

<bean \
    class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> \
  <property name="transactionInterceptor" ref="txInterceptor"/>
</bean>

<bean id="txInterceptor" \
    class="org.springframework.transaction.interceptor.TransactionInterceptor"> \
  <property name="transactionManager" ref="txManager"/>
  <property name="transactionAttributeSource">
<bean \
    class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> \
  </property>
</bean>

これには数多くのクラスが関わっている。

8.5.2 BeanNameAutoProxyCreator, 別の宣言的アプローチ

TransactionProxyFactoryBeanはとても有用であり、オブジェクトをトランザクションプロキシでラップする場合のフルコントロールが可能になる。親/子ビーン定義とターゲットを保持する内部ビーンを一緒に使い、Java5のアノテーションがオプションとして利用できない場合、一般的にトランザクションをラッピングするベストな方法である。 大量のビーンを完全に一意な方法でラップする必要がある場合(例えば、BeanNameAutoProxyCreatorと呼ばれるBeanFactoryPostProcessorを使って「全部のメソッドをトランザクションにせよ」という決まり文句)は、この単純化された用例にはそれほど冗長にはなりえない別のアプローチが取れるだろう。

要約すると、ApplicationContextが一旦初期化情報を読み込むと、BeanPostProcessorインタフェースが実装されたビーンがインスタンス化され、ApplicationContext内のほかのビーン全てに対し、後処理(post-process)を実施する機会ができる。よって、このメカニズムを使うと、適切に設定されたBeanNameAutoProxyCreatorApplicationContext内のほかのビーン(名前で識別される)に後処理を実施するのに用いられ、トランザクションプロキシでラップされる。生成される実際のトランザクションプロキシは本質的には、TransactionProxyFactoryBeanを使って生成されるのと同じなので、これ以上は触れない。

サンプルの設定をみてみよう。

<!-- Transaction Interceptor set up to do PROPAGATION_REQUIRED on all \
    methods -->
  <bean id="matchAllWithPropReq" 
class="org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource">
    <property name="transactionAttribute" value="PROPAGATION_REQUIRED"/>
  </bean>
  <bean id="matchAllTxInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="txManager" />
    <property name="transactionAttributeSource" ref="matchAllWithPropReq"/>
  </bean>

<!-- One BeanNameAutoProxyCreator handles all beans where we want all \
    methods to use
       PROPAGATION_REQUIRED -->
  <bean id="autoProxyCreator" 
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="interceptorNames">
      <list>
        <idref local="matchAllTxInterceptor"/>
        <idref bean="hibInterceptor"/>
      </list>
    </property>
    <property name="beanNames">
      <list>
        <idref local="core-services-applicationControllerSevice"/>
        <idref local="core-services-deviceService"/>
        <idref local="core-services-authenticationService"/>
        <idref local="core-services-packagingMessageHandler"/>
        <idref local="core-services-sendEmail"/>
        <idref local="core-services-userService"/>
      </list>
    </property>
  </bean>
</beans>

ApplicationContext内にすでにTransactionManagerのインスタンスがあると仮定して、まず最初にやらなければいけないことは、使用するTransactionInterceptorインスタンスを生成することだ。TransactionInterceptorは、プロパティとして渡されるTransactionAttributeSourceを実装しているオブジェクトに基づいてインターセプトすべきメソッドを判断する。この場合、全てのメソッドとマッチするとても単純なケースを扱いたい。これは必ずしも最も効率的なやり方ではないが、セットアップが非常に早い。というのは、 あらかじめ定義された特別なMatchAlwaysTransactionAttributeSource(これは単純に全てのメソッドにマッチする)を使うことができるからだ。もっと限定したいのであれば、MethodMapTransactionAttributeSource,NameMatchTransactionAttributeSource,AttributesTransactionAttributeSourceのような他の派生を使うことも可能だ。

これで、トランザクションインタセプタができて、ApplicationContextに、同じやり方でラップしたい6つのビーンの名前と一緒に、定義したBeanNameAutoProxyCreatorインスタンスに渡す。見てわかるように、最終的には6つのビーンを同じようにTransactionProxyFactoryBeanでラップするほど冗長にはならない。7つ目のビーンをラップするには設定ファイルに1行追加するだけだ。

ここで複数のインタセプタが適用できるということに気づいたかもしれない。この場合では、すでに定義した(bean id =hibInterceptor)HibernateInterceptorも適用してある。これは、Hibernateのセッションを管理するものだ。

TransactionProxyFactoryBeanBeanNameAutoProxyCreatorを切り替える場合にビーンの命名に関する考慮と一緒に気をつけてほしいことが1つある。前者には、ターゲットビーンが内部ビーンとして定義されていなければ、通常ラップしたいターゲットビーンにmyServiceTargetと同じようにidをつけ、プロキシオブジェクトにはmyServiceというidをつける。そして、ラップされたオブジェクトのユーザはみな、単純にプロキシ(つまり、myService)を参照する。(これは、命名規則のサンプルで、ポイントは、ターゲットオブジェクトはプロキシとは違う名前をもち、両方がApplicationContextから利用可能である、という点である)。しかしながら、BeanNameAutoProxyCreatorを使う場合、ターゲットオブジェクトにmyServiceのような名前をつける。そしてBeanNameAutoProxyCreatorがターゲットオブジェクトを後処理し、プロキシを生成する場合、プロキシがアプリケーションコンテキストに元のビーンの名前で挿入されるようにする。その点から、プロキシ(ラップされたオブジェクト)だけがApplicationContextから利用可能になる。内部ビーンとしてターゲットを指定してTransactionProxyFactoryBeanを使う場合は、内部ビーンには通常名前をつけないので、この命名の問題は関係ない。

8.5.3 AOPとTransaction

これまで本章を読んできてお分かりのように、Springの宣言的トランザクション管理を効果的に用いるのに、本当にAOPのエキスパートになる必要はない、つーか、AOPに関して詳しく知らなくてもよい。しかしながら、Spring AOPの「パワーユーザ」になりたいのであれば、宣言的トランザクション管理と強力なAOPの能力を組み合わせるのが簡単だというのがわかるだろう。

8.6 プログラミング的トランザクション管理か宣言的トランザクション管理かを選択する

プログラミング的トランザクション管理は、トランザクション操作が少ない場合に限っては、いい方法だと思う。例えば、いくつかの更新操作にのみトランザクションが必要になるようなウェブアプリケーションがあるとすると、Springや他のテクノロジを使ってトランザクションプロキシをセットアップしたいとは思わないだろう。この場合は、TransactionTemplateを使うのがいいやり方だと思う。

一方、大量のトランザクション操作があるようなアプリケーションであれば、宣言的トランザクション管理が有益だ。宣言的トランザクション管理では、トランザクション管理をビジネスロジックから切り離すことができ、またSpringではその設定は難しくはない。Springを使えば、EJB CMTよりも宣言的トランザクション管理の設定にかかるコストが大幅に削減される。

8.7 トランザクション管理のためにアプリケーションサーバが必要?

Springのトランザクション管理機能、特に宣言的トランザクション管理は、J2EEアプリケーションがいつアプリケーションサーバを必要とするかについての従来の考え方を大きく変える。

特に、EJBを使って宣言的なトランザクションを実行するだけであれば、アプリケーションサーバは必要ではない。実際、強力なJTA機能をもつアプリケーションサーバがあっても、Springの宣言的トランザクションの方が強力で、EJB CMTよりも生産的なプログラミングモデルが用意されていると判断するのは正しい。

もし、多数のトランザクションリソースを集める必要がある場合に限り、アプリケーションサーバのJTA機能が必要となる。多くのアプリケーションではこういった必要性には直面しない。例えば、多くのハイエンドアプリケーションでは単一でスケーラビリティが高い、Oracle 9i RACのようなデータベースを用いる。

もちろん、JMSやJCAのようなアプリケーションサーバのほかの機能を必要とするかもしれない。しかしながら、JTAだけを必要とするのであれば、JOTMのようなオープンソースのJTAアドオンも検討することができる。(SpringはJOTMと[out of the box]統合する。)しかしながら、2004年の早い時期に、ハイエンドアプリケーションサーバはより堅牢にXAトランザクションのサポートを提供する。

最も重要な点は、Springでは、アプリケーションサーバで一杯一杯の規模になるまで、あなたのアプリケーションをいつスケールさせるかを選択するすることができる、という点だ。JDBCコネクションのようなローカルトランザクションを使ってコードを書くにはEJB CMTやJTAを使う他に手がなく、グローバルでコンテナで管理されたトランザクションで走るようなコードが必要な場合、多くの手戻りに直面するような時代はもう過ぎ去った。Springであれば、設定に変更をいれないといけないだけで、コードに手を入れる必要はないのだ。

8.8 アプリケーションサーバに応じたインテグレーション

Springのトランザクション抽象化は、通常アプリケーションサーバを問わない。さらに、JTAのUserTransactionオブジェクトやTransactionManagerオブジェクト用にオプションでJNDIルックアップを実行できるSpringのJtaTransactionManagerクラスは、後者オブジェクトの位置を自動認識して設定することができるがこれは、アプリケーションサーバの種類による。TransactionManagerインスタンスへのアクセスにより、トランザクションセマンティクスを拡張することができる。詳細についてはJtaTransactionManagerのJavadocを参照されたい。

8.8.1 BEA WebLogic

WebLogic 7.0, 8.1あるいはそれ以降の環境では通常、JtaTransactionManagerの代わりにWebLogicJtaTransactionManagerを使いたいと思うだろう。これは特別にWebLogicに特化した,通常のJtaTransactionManagerのサブクラスだ。これを使えば、トランザクション名、トランザクション単位のアイソレーションレベル、およびすべての場合においてトランザクションを適切に再会できるといった機能を含んだ、標準のJTAセマンティクス以上に、WebLogicで管理されたトランザクション環境でSpringのトランザクション定義をフルに使うことができる。

詳細については、Javadocを参照してほしい。

8.8.2 IBM WebSphere

WebSphere 5.1, 5.0あるいはバージョン4の環境では、SpringのWebSphereTransactionManagerFactoryBeanクラスを使いたいかもしれない。これはWebSphere環境でJTAのTransactionManagerを検索するファクトリビーンであり、WebSphereのスタティックアクセスメソッドを使って検索を行う。このメソッドはWebSphereのバージョンごとに異なる。

一旦このファクトリビーンを使ってJTAのTransactionManagerインスタンスを取得すると、SpringのJtaTransactionManagerには、JTAのUserTransactionオブジェクトしか使わない場合よりもトランザクションセマンティクスを拡張するために、このインスタンスへの参照が保持される。

詳細は、Javadocを参照してほしい。

8.9 共通の問題

8.9.1 特定のデータソースに対する間違ったトランザクションマネージャの利用

開発者は、要求を満たすためには正しいPlatformTransactionManagerの実装を使うように注意する必要がある。

どのようにSpringのトランザクション抽象化が、JTAグローバルトランザクションを使って行われているかを理解することは重要だ。適切に用いれば、矛盾は発生しない。Springでは単純化されたポータブルな抽象化が提供されるだけだ。

もしグローバルトランザクションを使う場合、全てのトランザクション操作にSpringのorg.springframework.transaction.jta.JtaTransactionManagerを使わないといけない。そうでないと、コンテナDataSourceのようなリソースに対するローカルトランザクションとみなされてしまう。このようなローカルトランザクションは意味をなさないし、優れたアプリケーションサーバであれば、エラーとして扱うだろう。

8.9.2 すでにアクティブでないトランザクションもしくはDataSourceに関するウソの警告

非常に厳密なXADataSourceの実装をもつJTA環境において、-- 現時点では、WebLogicとWebSphereのいくつかのバージョンだけ -- JTAのTransactionManagerオブジェクトを意識せずに設定されたHibernateをそのような環境で使う場合、ウソの警告や例外がアプリケーションサーバのログに残されることがある。この警告、もしくは例外は、トランザクションがすでにアクティブでないという理由により、アクセスされたコネクションがすでに有効でない、あるいはJDBCアクセスがすでに有効でないということを言っている。例として、WebLogicから上げられた実際の例外についてみてみよう。

java.sql.SQLException: The transaction is no longer active - \
    status: 'Committed'.
   No further JDBC access is allowed within this transaction. 

この警告は*unresolved*に記述されているように、解決するのは簡単だ。