kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのV. Data Access 16. Transaction Management 16.5までをテキトーに訳した

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。

Part V. Data Access

リファレンスドキュメントの当パートでは、データアクセスおよびデータアクセスレイヤとビジネスもしくはサービスレイヤーとの相互作用の焦点をあてます。

Springの包括的トランザクション管理サポートは、Spring Frameworkにより統合される各種データアクセスフレームワークと関連技術によってカバーされています。

16. Transaction Management

16.1 Introduction to Spring Framework transaction management

包括的トランザクション管理サポートはSpring Frameworkを使う最も良くある理由の一つです。Spring Frameworkは以下のような利点を提供するトランザクション管理用の一貫性のある抽象化(consistent abstraction)を提供します。

以降のセクションではSpring Frameworkトランザクションの付加価値とテクノロジについて解説します。(また、このチャプターには、ベストプラクティス・アプリケーションサーバ統合・良くある問題の解決策、についての解説も含みます)

16.2 Advantages of the Spring Framework’s transaction support model

伝統的に、Java EE開発者にはトランザクション管理には二つの選択肢がありました。グローバル(global)かローカル(local)トランザクションで、どちらも深刻な制限があります。グローバルおよびローカルトランザクション管理について次の2セクションで検討し、続いて、Spring Frameworkトランザクション管理サポートがどのようにグローバルおよびローカルトランザクションモデルの制限を処理するのか、について解説します。

16.2.1 Global transactions

グローバルトランザクションにより複数トランザクションリソース、良くあるのはリレーショナルデータベースとメッセージキュー、の動作が可能になります。アプリケーションサーバは使いにくいAPIを持つJTA(例外モデルに起因)経由でグローバルトランザクションを管理します。また、JTAUserTransactionは通常はJNDI経由で取得する必要があり、JTAを使うにはJNDI使う必要があります。JTAは通常はアプリケーションサーバ環境でのみ利用可能なので、グローバルトランザクションの使用はアプリケーションコードの再利用の可能性を明らかに制限しています。

これまで、グローバルトランザクションの使用が推奨されるのはEJB CMT (Container Managed Transaction)でした。CMTは宣言的トランザクション管理(declarative transaction management)の形態を取ります(プログラムによるトランザクション管理((programmatic transaction management))と区別するための用語)。EJB CMTは、EJBそれ自体は当然ながらJNDIを必要としますが、トランザクションに絡むJNDIルックアップの必要をなくしました。しかし、トランザクション制御をするJavaコードを書く必要性をすべてなくしたわけではありません。重大な欠点は、CMTはJTAアプリケーションサーバ環境に結び付けられています。トランザクションEJBファサードをバックに置くかEJB上にビジネスロジックを実装している場合、他に選択肢はありません。一般的にはEJBの負の側面は極めて大きく、特に宣言的トランザクション管理の魅力的な代案としては、魅力的な提案ではありません。

16.2.2 Local transactions

ローカルトランザクションは、JDBC接続に関連付けられたトランザクションなど、リソース固有のものです。ローカルトランザクションの使い方は簡単ですが、深刻な欠点があり、複数トランザクションリソースをまたがる動作は出来ません。たとえば、JDBC接続を使うトランザクション管理を行うコードはグローバルJTAトランザクション下に入れられません。アプリケーションサーバトランザクション管理には含まれないので、複数リソースにまたがる正しさを保証できません。(大半のアプリケーションは単一のトランザクションリソースを使う点は注目に値するでしょう)他の欠点としては、ローカルトランザクションはプログラミングモデルに侵襲的な点があります。

16.2.3 Spring Framework’s consistent programming model

Springはグローバルおよびローカルトランザクションの欠点を解決しています。アプリケーション開発者は任意の環境で一貫性のあるプログラミングモデルを使用可能です。コードは一度書くだけで、異なる環境の異なるトランザクション管理ストラテジの利点を得られます。Spring Frameworkは宣言的およびプログラム的トランザクション管理の両方をサポートしています。大半のユーザは宣言的トランザクション管理を好み、多くのケースにおいてそちらが推奨されます。

プログラム的トランザクション管理では、開発者はSpring Frameworkトランザクション抽象化を使用し、この抽象化は任意の基底トランザクションインフラ上で動作が可能です。推奨される宣言的モデルでは、通常、開発者はトランザクション管理に関するコードは全く書かないか極少規模となり、よって、Spring FrameworkトランザクションAPIやその他のトランザクションAPIには依存しません。

トランザクション管理にアプリケーションサーバは必要か?
Spring Frameworkトランザクション管理サポートはエンタープライズJavaアプリケーションはアプリケーションサーバを要求するという伝統的なルールを変えました。
特に、EJB経由の宣言的トランザクションを単純化するアプリケーションサーバを必要としません。実際のところ、アプリケーションサーバが強力なJTAの機能を有するとしても、EJB CMTよりも強力で生産性のあるプログラミングモデルを有するSpring Frameworkの宣言的トランザクションを選ぶと思われます。
一般的には、アプリケーションが複数リソースにまたがるトランザクションを処理する必要がある場合にだけアプリケーションサーバJTA機能を使う必要がありますが、大半のアプリケーションでそうした要求は生じません。代わりに、ハイエンドサプリケーションの多くは、単一の高スケーラブルデータベース(Oracle RACなど)を使います。スタンドアローントランザクション管理にはAtomikos Transactions JOTMなどがオプションとして挙げられます。Java Message Service (JMS)とJava EE Connector Architecture (JCA)などのアプリケーションサーバの他の機能を必要とする場合もあるかと思われます。
Spring Frameworkは機能満載なアプリケーションサーバのアプリケーションをスケールする選択肢を与えるものです(gives you the choice of when to scale your application to a fully loaded application server)。JDBC接続などローカルトランザクションのコードを書くのにJTAEJB CMTを使う選択肢しかなかった時代は終わり、グローバルなコンテナ管理トランザクションでコードを書く必要がある場合には面倒な作業に直面します。Spring Frameworkでは、変更の必要がある場合、コードではなく設定ファイルのビーン定義のみで済みます。

16.3 Understanding the Spring Framework transaction abstraction

Springのトランザクション抽象化のカギはトランザクションストラテジ(transaction strategy)の考え方にあります。トランザクションストラテジは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;
}

このインタフェースはprimarily a service provider interface (SPI)で、アプリケーションコードからプログラム的にも使われます。PlatformTransactionManagerはインタフェース(interface)なので、必要に応じて簡単にモックやスタブに出来ます。JNDIなどのルックアップストラテジには結び付けられていません。PlatformTransactionManagerの実装はSpring Framework IoC container上のその他のオブジェクト(やビーン)のように定義します。これにより、JTAで動作させる場合であっても、Spring Frameworkトランザクションをより価値のある抽象化にしています。トランザクション下のコードのテストが、JTAを直接使う場合よりも簡単になります。

Springの思想の話を続けますが、PlatformTransactionManagerインタフェースのメソッドがスロー可能なTransactionException未チェック(unchecked)です(つまりjava.lang.RuntimeExceptionクラスの拡張)。トランザクションのインフラの失敗はほとんど常に致命的です。アプリケーションコードがトランザクション失敗から回復可能なレアケースの場合には、アプリケーション開発者はTransactionExceptionをキャッチして処理可能です。ここでのポイントは、開発者が例外処理を強制されない点にあります。

getTransaction(..)メソッドTransactionDefinitionパラメータに依存するTransactionStatusオブジェクトを戻します。戻されたTransactionStatusは、新規トランザクションを表現するか、カレントコールスタックにマッチするトランザクションが存在する場合には既存トランザクションを表現します。後者の意味合いについては、Java EEトランザクションコンテキストと同様に、TransactionStatusは実行スレッド(thread)に関連付けられます。

TransactionDefinitionインタフェースは以下を定義します。

これらの設定は標準的なトランザクションの考え方を反映しています。必要に応じて、トランザクション分離レベルやその他の核となるトランザクションの考え方に関する資料を参考にしてください。Spring Frameworkやその他のトランザクション管理ソリューションを使う上でこれらの考え方を理解することは不可欠です。

TransactionStatusインタフェースは、トランザクション実行制御とトランザクションステータスの問い合わせをするトランザクションコード向けの手段を提供します。すべてのトランザクションAPIに共通なので、考え方自体は良く見かけるような感じになっています。

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

Springにおいて宣言的かプログラム的トランザクション管理のどちらを選ぶかに関わらず、正しくPlatformTransactionManagerの実装を定義することが絶対に不可欠です。通常はDIを通して実装を定義します。

PlatformTransactionManagerの実装は、通常、JDBC, JTA, Hibernateなど動作環境の知識を要求します。以下のサンプルはローカルPlatformTransactionManager実装を定義する方法について示しています。(このサンプルは素のJDBCで動作します)

JDBC DataSourceを定義します。

<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ビーン定義は上記のDataSource定義を参照します。以下のようになります。

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

Java EEコンテナでJTAを使う場合はJNDI経由で取得するコンテナDataSourceをSpringのJtaTransactionManagerと組み合わせて使います。以下はJTAとJNDIルックアップバージョンになります。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

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

    <!-- other <bean/> definitions here -->

</beans>

JtaTransactionManagerにはDataSourceへの参照やその他のリソース指定をする必要はなく、これはコンテナのグローバルトランザクション管理インフラを使うためです。

上記のdataSourceビーン定義はjee名前空間<jndi-lookup/>タグを使っています。スキーマベース設定の詳細については、Chapter 40, XML Schema-based configurationを参照し、<jee/>タグの詳細についてはSection 40.2.3, “the jee schema”セクションを参照してください。

なお、以下のサンプルで見るように、Hibenateのローカルトランザクションは簡単に使えます。この場合、HibernateLocalSessionFactoryBeanを定義する必要あり、これによりアプリケーションコードでHibernateSessionインスタンスを得られるようになります。

DataSourceビーン定義は先に見たローカルJDBCサンプルと同様なので以下のサンプルでは省略しています。

JTAトランザクションマネージャで使用するDataSourceをJNDI経由でルックアップしたりJava EEコンテナで管理する場合、Java EEコンテナではなくSpring Frameworkトランザクションを管理するので非トランザクションになります*1

この場合にtxManagerビーンはHibernateTransactionManager型になります。DataSourceTransactionManagerDataSourceへの参照を必要とするように、HibernateTransactionManagerSessionFactoryへの参照を必要とします。

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.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">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

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

HibernateJava EEコンテナ管理JTAトランザクションを使う場合、前述のJDBCでのJTAサンプルと同じようにJtaTransactionManagerを使えます。

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

JTAを使う場合、トランザクションマネージャ定義はJDBCHibernate JPAやその他のデータアクセス技術に関わらず同じになります。JTAトランザクションはグローバルトランザクションであり任意のトランザクションリソースを取得可能、という事実に基づいています。

すべてのケースにおいて、アプリケーションコードを変更する必要はありません。ただ単に設定変更をすることでトランザクション管理の方法が変更可能で、たとえローカルからグローバルないしその逆へトランザクションを変える場合でも同様です。

16.4 Synchronizing resources with transactions

異なるトランザクションマネージャを生成する方法と、同期化の必要がある関連リソースにトランザクションをリンクする方法(例:JDBC DataSourceDataSourceTransactionManager, Hibernate SessionFactoryHibernateTransactionManagerなど)について解説します。このセクションでは、リソースの適切な生成・再利用・クリーンアップを、JDBC, Hibernate, JDOなどの永続化APIを直接・非直接に使用するアプリケーションコードで保証する方法について解説します。また、関連PlatformTransactionManager経由でトランザクション同期化をトリガする方法についても解説します。

16.4.1 High-level synchronization approach

推奨する方法は、Springの高レベルテンプレートベースの永続化インテグレーションAPIを使うか、 ネイティブリソースファクトリを管理するためのプロキシもしくはtransaction-awareファクトリビーンでネイティブORM APIを使います。transaction-awareを使う方法は、内部的にリソース生成と再利用・クリーンアップ・オプションでリソースのトランザクション同期化・例外マッピング、を処理します。この場合のユーザデータアクセスコードはタスクを処理する必要がなく、ボイラープレートの永続化ロジックを無くすことが可能です。基本的には、ネイティブORM APIもしくはJdbcTemplateを使用したテンプレートによるJDBCアクセスを使います。これらの方法の詳細については以降のチャプターで扱います。

16.4.2 Low-level synchronization approach

低レベルにおいては、DataSourceUtils(JDBC用), EntityManagerFactoryUtils (JPA用), SessionFactoryUtils (Hibernate用), PersistenceManagerFactoryUtils (JDO用)、などを生成します。ネイティブ永続化APIのリソース型で直接処理を行うコードを書きたい場合、適切なSpring Frameworkマネージドインスタンスの取得・同期化されたトランザクション(オプション)・処理中の例外を一貫性APIへ適切にマッピング、することを保証するために前述のクラスを使います。

たとえば、JDBCの場合、伝統的なDataSourcegetConnection()メソッドを呼んでJDBCを使う代わりに、以下のようなSpringのorg.springframework.jdbc.datasource.DataSourceUtilsクラスを使います。

Connection conn = DataSourceUtils.getConnection(dataSource);

もし既存のトランザクションが既に同期化(リンク済)コネクションを持っている場合、そのインスタンスが戻されます。そうでない場合、上記のメソッドは新規コネクション生成トリガを呼び出し、既存のトランザクションが(オプションで)同期化され、同一トランザクションの後続処理で再利用が可能となります。上述したように、SQLExceptionはSpring FrameworkのCannotGetJdbcConnectionExceptionでラップされます。この例外クラスは未チェックDataAccessExceptionsのSpring Framework階層の一つです。これにより、SQLExceptionを使うよりも多くの情報が得られ、また、異なる永続化テクノロジをまたがる場合でも、データベース間のポータビリティを保証します。

なお、この方法はSpringのトランザクション管理無し(トランザクション同期化はオプション)でも動作するので、トランザクション管理にSpringを使うかどうかに関わらず、この方法を使用可能です。

SpringのJDBCサポート・JPAサポート・Hibernateサポートを使っているなら、一般的にはDataSourceUtilsやその他のヘルパークラスを使わないと思われます。なぜなら、関連APIを直接扱うよりもSpring抽象化の方が楽だからです。たとえば、JDBCの使用を単純化するためにSpringのJdbcTemplatejdbc.objectを使う場合、正しいコネクション取得はそれらの機能の背後に隠ぺいされるので、コネクション取得などの特殊なコードを書く必要はありません。

16.4.3 TransactionAwareDataSourceProxy

最も低レベルのクラスはTransactionAwareDataSourceProxyです。これはターゲットDataSource用のプロキシで、Springマネージドトランザクションのawarenessを追加するためにターゲットDataSourceをラップします。Java EEサーバが提供するトランザクショナルJNDIDataSourceに類似しています。

このクラスを必要とする場合はなるべく無いのが望ましく、例外は、既存コードが標準JDBCDataSourceインタフェースの実装を渡されたり呼ぶ必要がある場合です。その場合、コードは使用可能ですが、Springマネージドトランザクションに参加します。前述の通り、新しいコードを書く場合は高レベル抽象化を使うことを推奨します。

16.5 Declarative transaction management

Spring Frameworkユーザの大半は宣言的トランザクション管理を選択しています。このオプションではアプリケーションコードには最小の影響で済み、よって、非侵襲的(non-invasive)な軽量コンテナの理想と最も合致します。

Spring Frameworkの宣言的トランザクション管理はSpringのアスペクト指向プログラミング(AOP)で実現されており、トランザクショナルなアスペクトコードはSpring Frameworkと一緒に使うことになりボイラープレートのコードを書くことにはなりますが、AOPではコードがどのように使われるを理解する必要はありません*2

Spring Frameworkの宣言的トランザクション管理は、個々のメソッドレベルにトランザクションの振る舞いを指定可能という点で、EJB CMTに似ています。必要に応じてトランザクションコンテキスト内でsetRollbackOnly()を呼び出せます。二つのトランザクション管理の違いは以下の通りです。

Where is TransactionProxyFactoryBean?
Spring 2.0以上の宣言的トランザクション設定はそれ以前のバージョンと大幅に異なります。主要な違いはTransactionProxyFactoryBeanビーンの設定が不要になった点です。
Spring 2.0以前の設定スタイルは現在でも100%妥当な設定です。TransactionProxyFactoryBeanを定義することは、新規の<tx:tags/>を設定することと見なされます。

ロールバックルールの考え方は重要です。これは例外(とthrowables)が自動ロールバックとなるような設定が可能です。Javaコードではなく設定で宣言的に設定します。カレントのトランザクションロールバックするのにTransactionStatusオブジェクトのsetRollbackOnly()を呼ぶことも可能ですが、MyApplicationExceptionが常にロールバック必須というルールを指定可能です。このオプションの利点はビジネスオブジェクトがトランザクションインフラに依存しない点です。たとえば、SpringのトランザクションAPIやその他のSpring APIをインポートする必要がありません。

16.5.1 Understanding the Spring Framework’s declarative transaction implementation

@Transactionalアノテーションをクラスに付与するだけ、では不十分で、設定に@EnableTransactionManagementを追加し、それから、それらの動作を理解する必要があります。このセクションでは、トランザクションが絡む問題における、Spring Frameworkの宣言的トランザクションインフラの内部動作について解説します。

Spring Frameworkの宣言的トランザクションを理解する上で最も重要なことは、AOPプロキシ経由で有効化されるもので、トランザクショナルなアドバイスがmetadataXMLもしくはアノテーション)によって駆動されます。トランザクショナルなメタデータAOPの組み合わせはAOPプロキシを構成し、そのプロキシはaroundメソッド実行でトランザクションを駆動する適切なPlatformTransactionManager実装とTransactionInterceptorの組み合わせを使います。

Spring AOPについてはChapter 10, Aspect Oriented Programming with Springを参照してください。

概念的には、トランザクショナルなプロキシ上でのメソッド呼び出しは以下のようになります。

tx.png

16.5.2 Example of declarative transaction implementation

以下のインタフェースとそれに付随する実装を考えてみます。以下のサンプルは、特定のドメインモデルに基づくことなくトランザクションの使用法に集中するため、プレースホルダとしてFooBarクラスを使っています。サンプルの目的のために、DefaultFooServiceクラスは実装側のメソッドUnsupportedOperationExceptionをスローしており、生成されたトランザクションUnsupportedOperationExceptionインスタンスに対する反応としてロールバックする様子を見ることができます。

// the service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
// an implementation of the above interface

package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

FooServiceインタフェースのgetFoo(String)getFoo(String, String)メソッドはリードオンリーのトランザクションコンテキストでの実行が必須で、他のメソッドinsertFoo(Foo)updateFoo(Foo)はread-writeのトランザクションコンテキストでの実行が必須である、と想定します。以降のパラグラフで以下の設定の詳細について解説します。

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- don't forget the DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <!-- similarly, don't forget the PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

上記の設定を見ていきます。まず、サービスオブジェクトfooServiceビーンをトランザクショナルにしたいとします。適用するトランザクションセマンティクスは<tx:advice/>定義にカプセル化します。<tx:advice/>定義は文にすれば次のようになります。"... 'get'で始まる全メソッドはread-onlyトランザクションコンテキストで実行され、他のすべてのメソッドはデフォルトのトランザクションセマンティクスで実行される"。<tx:advice/>タグのtransaction-manager属性にはPlatformTransactionManagerビーンを設定します。PlatformTransactionManagerトランザクション駆動(drive)するもので、上記の例ではtxManagerビーンのことです。

ワイヤリングに使うPlatformTransactionManagerの名前がtransactionManagerの場合、トランザクショナルなアドバイス(<tx:advice/>)のtransaction-manager属性は省略可能です。ワイヤリングに使うPlatformTransactionManagerの名前が他の名前の場合、前述の例のように、明示的にtransaction-managerの指定が必須となります。

<aop:config/>では、txAdviceビーンで定義したトランザクショナルなアドバイスがプログラム上の適切なポイントで実行するよう、設定しています。まず、FooServiceインタフェース(fooServiceOperation)に定義されているオペレーションの実行にマッチするポイントカットを定義しています。次に、このポイントカットとadvisorを使用してtxAdviceを関連付けます。つまり、fooServiceOperation実行時にtxAdviceで定義したアドバイスが実行される、という意味になります。

<aop:pointcut/>要素内に定義してある式はAspectJのポイントカット式で、Springでのポイントカット表現の詳細についてはChapter 10, Aspect Oriented Programming with Springを参照してください。

よくある要求としてはサービスレイヤ全体をトランザクショナルにすることです。これを実現するもっとも簡単な方法は、サービスレイヤの任意のオペレーションにマッチするポイントカット式に書き換えることです。

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

上の例では、すべてのサービスインタフェースはx.y.serviceパッケージに定義されている、と仮定しています。詳細についてはChapter 10, Aspect Oriented Programming with Springを参照してください。

いま、上記の設定を調べ終わったとして、次にあなたはこう尋ねることでしょう, "それで、この設定で実際のところ何が起きるの?"

上記の設定は、aroundのトランザクショナルなプロキシを生成するのに使われ、そのプロキシオブジェクトはfooServiceビーン定義を基に生成されます。プロキシはトランザクショナルなアドバイスとして設定され、対象となるメソッドプロキシを介して呼ばれた場合、トランザクション開始・サスペンド・read-onlyとしてマーク等々、がメソッドに関連付けられたトランザクション設定に基づいて行われます。上述の設定をテスト実行する以下のプログラムを考えてみます。

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
        FooService fooService = (FooService) ctx.getBean("fooService");
        fooService.insertFoo (new Foo());
    }
}

上記のプログラム実行の出力は以下のようになります。(DefaultFooServiceクラスのinsertFoo(..)メソッドがスローするUnsupportedOperationExceptionのLog4Jの出力とスタックトレースは分かりやすくするため省略しています。)

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

16.5.3 Rolling back a declarative transaction

先のセクションでは、アプリケーションのいわゆるサービスレイヤーのクラスで、宣言的にトランザクション設定を指定する方法の基本について概観しました。当セクションではシンプルな宣言的な方法でトランザクションロールバックを制御する方法について説明します。

Spring Frameworkトランザクションインフラにトランザクションロールバックを指示するための推奨方法はトランザクションコンテキストで現在実行中のコードからExceptionをスローすることです。Spring Frameworkトランザクションインフラのコードはコールスタックを上がってくる未処理Exceptionをキャッチし、トランザクションロールバックとマークするかどうかを決定します。

デフォルト設定では、スローされた例外がRuntimExceptionサブクラスのインスタンスの未チェック例外の場合、Spring Frameworkトランザクションインフラのコードはトランザクションロールバックとマークだけします*3。(なおErrorはデフォルトではロールバックされます)トランザクショナルなメソッドからスローされたチェック例外はデフォルト設定ではロールバックされません

トランザクションロールバックとマークするException型を厳密に設定可能です。これにはチェック例外を含みます。以下XMLはアプリケーション固有のチェックExcepiton型例外でロールバックする方法について示しています。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

また、'no rollback rules'を指定可能です。ある例外がスローされた場合にはロールバックしない場合に指定します。以下の例は、Spring Frameworkトランザクションインフラが未処理のInstrumentNotFoundExceptionに受け取る場合には付随するトランザクションをコミットするよう指定しています。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

Spring Frameworkトランザクションインフラが例外をキャッチし、トランザクションロールバックにマークするかどうかを決定するロールバックルールが設定されている場合、最も強くマッチするルールが使われます。よって以下の設定ではInstrumentNotFoundException以外の例外がトランザクションロールバックします。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

なお、ロールバックプログラム的に指示することも可能です。極めてシンプルではありますが、処理が非常に侵襲的であり、Spring Framworkのトランザクションインフラとコードが密結合します。

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

ロールバックには可能な限り宣言的な方法を取るよう努めてください。Programmatic rollback is available should you absolutely need it, but its usage flies in the face of achieving a clean POJO-based architecture.

16.5.4 Configuring different transactional semantics for different beans

いま、多数のサービスレイヤーオブジェクトがあるとして、それぞれに全く異なるトランザクション設定をしたい、とします。これの実現には、異なるpointcutadvice-ref属性値で別々の<aop:advisor/>要素を定義することで行います。

解説用の仮定として、すべてのサービスレイヤクラスはx.y.serviceパッケージに定義されている、とします。すべてのビーンはそのパッケージ(もしくはサブパッケージ)に定義されるクラスのインスタンスであり、``Service```で終わる名前のクラスはデフォルトのトランザクション設定をもつよう設定するには、以下のような設定ファイルを作ります。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>

        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

    </aop:config>

    <!-- these two beans will be transactional... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- ... and these two beans won't -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

以下の例は、異なるトランザクション設定で二つの異なるビーンを設定する方法です。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>

        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    </aop:config>

    <!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this bean will also be transactional, but with totally different transactional settings -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        </tx:attributes>
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

16.5.5 <tx:advice/> settings

このセクションでは<tx:advice/>タグで指定可能な各種のトランザクション設定についての概要を説明します。デフォルトの<tx:advice/>設定は、

これらのデフォルト設定は変更可能です。<tx:advice/><tx:attributes/>内にネストする<tx:method/>タグの各属性の概要は以下の通りです。

Table 16.1. <tx:method/> settings

属性 必須 デフォルト 説明
name 必須 トランザクション属性を関連付けるメソッド名。ワイルドカード(*)により複数メソッドに同一のトランザクション属性を設定可能です。例えば、get*, handle*, on*Eventなど
propagation - REQUIRED トランザクション伝播の振る舞い
isolation - DEFAULT トランザクションの分離レベル
timeout - -1 トランザクションタイム(秒)
read-only - false トランザクションがread-onlyかどうか
rollback-for - ロールバックをトリガするException(s)。カンマ区切り。例:com.foo.MyBusinessException,ServletException
no-rollback-for - ロールバックをトリガしないException(s)。カンマ区切り。例:com.foo.MyBusinessException,ServletException

16.5.6 Using @Transactional

XMLによる宣言的なトランザクション設定に加え、アノテーションも使用可能です。Javaソースコードに直接トランザクションのセマンティクスを宣言することで、コードにより近い位置に宣言を入れられます。There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.

Springのアノテーションの当座の代替として標準javax.transaction.Transactionalアノテーションもサポートされています。詳細についてはJTA 1.2ドキュメントを参照してください。

@Transactionalによる使いやすさは以下の例に示すとおりです。

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

上記のPOJOはSpring IoCコンテナにビーンとして定義されており、XMLこの一行を加えるだけでビーンインスタンスはトランザクショナルになります。

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

もし関連付けたいPlatformTransactionManagerのビーン名がtransactionManagerの場合、<tx:annotation-driven/>タグのtransaction-manager属性は省略可能です。もしPlatformTransactionManagerビーンを別の名前でDIしたい場合、前述の例のように、transaction-manager属性を明示的に指定する必要があります。

Javaコンフィグレーションを使う場合には、同等のサポートを@EnableTransactionManagementアノテーションが提供します。使うにはただ単に@Configurationクラスにこのアノテーションを追加するだけです。詳細についてはjavadocを参照してください。

Method visibility and @Transactional
プロキシを使う場合、publicのメソッドにのみ@Transactionalアノテーションを適用するようにしてください。もしprotected, private, パッケージレベルのメソッド@Transactionalアノテーションを付与する場合、何もエラーは発生せず、そのメソッドでは設定したトランザクション設定が動作しません。もし非publicメソッドアノテーションを付与する必要がある場合、AspectJの使用を検討してください。

@Transactionalアノテーションは、インターフェイス定義・インタフェースのメソッド・クラス定義・クラスのpublicメソッド、に付けられます。ただし、トランザクションの振る舞いを有効化するには、ただ単に@Transactionalアノテーションを配置するだけでは不十分です。@Transactionalアノテーションは単なるメタデータであり、@Transactionalを処理可能なランタイムのインフラが参照するもので、そのメタデータを使用してトランザクションの振る舞いを対象のビーンに設定します。前述の例では、<tx:annotation-driven/>要素がトランザクションの振る舞いを有効化します。

Springでは、インタフェースにアノテーションを付与するのではなく、具象クラス(と具象クラスのメソッド)にのみ@Transactionalアノテーションを付与することを推奨します。確かに、インタフェース(もしくはインタフェースのメソッド)に@Transactionalアノテーションを付与可能ですが、これはインタフェースベースプロキシを使用すると想定している場合にだけ動作します。事実として、Javaアノテーションインタフェースから継承されない(not inherited from interfaces)ため、もしクラスベースプロキシ(proxy-target-class="true")やウィービングベースアスペクト(mode="aspectj")を使う場合、トランザクション設定はプロキシとウィービングのインフラからは見えず、オブジェクトはトランザクションプロキシでラップされず、明らかによろしくない結果となります。

プロキシモード(デフォルト)では、外部からのメソッド呼び出しがプロキシを通過するときのみインターセプトされます。つまり、自己呼び出し、ターゲットオブジェクトのメソッドがそのターゲットオブジェクトの別のメソッドを呼ぶ場合、たとえ呼び出されるメソッド@Transactionalがついていても実行時にトランザクションは開始しません。また、プロキシは期待される振る舞いを行うには完全に初期化済みなことが必須なので、初期化コード中、@PostConstructなど、でアスペクトに依存してはいけません。

自己呼び出しもトランザクションでラップしたい場合はAspectJモード(属性は以下の表を参照)の使用を考慮してください。この場合は、プロキシが主要な要素ではなくなり、代わりに、メソッドの実行時の振る舞いを@Transactionalを付与するためにターゲットクラスでウィービング(つまりバイトコードが修正される)が行われます。

Table 16.2. Annotation driven transaction settings

XML属性 アノテーション属性 デフォルト 説明
transaction-manager N/A(TransactionManagementConfigurerjavadocを参照) transactionManager 使用するトランザクションマネージャ名。トランザクションマネージャ名がtransactionManagerでない場合のみ必須。
mode mode proxy デフォルトモード"proxy"はSpring AOPフレームワークでプロキシ化したビーンのアノテーションを処理します(上述の通り、プロキシを経由するメソッド呼び出しだけに適用される、プロキシのセマンティクスに従います)。もう一つの"aspectj"モードはSpringのAspectJトランザクションアスペクトで対象クラスをウィービングします。これは、メソッド呼び出しにトランザクションを提供するのにターゲットのバイトコードを修正します。AspectJのウィービングはロード時ウィービング(もしくはコンパイル時ウィービング)有効化と同様にクラスパスにspring-aspects.jarを必要とします(ロード時ウィービングのセットアップ方法の詳細についてはthe section called “Spring configuration”を参照してください)。
proxy-target-class proxyTargetClass false プロキシモードにだけ適用されます。@Transactional付与したクラスで生成されるトランザクショナルなプロキシの種類を制御します。proxy-target-class属性がtrueの場合、クラスベースプロキシが生成されます。proxy-target-class属性がfalseか属性未指定の場合、標準JDKインターフェースベースプロキシが生成されます(プロキシタイプ別の詳細な解説についてはSection 10.6, “Proxying mechanisms” を参照してください)。
order order Ordered.LOWEST_PRECEDENCE @Transactionalを付与したビーンに適用されるトランザクションアドバイスの順序を定義します。(AOPアドバイスに関するルールの詳細についてはthe section called “Advice ordering”を参照してください)この属性が未指定の場合はAOPのサブシステムがアドバイスの順序を決定します。

proxy-target-class属性は@Transactionalを付与したクラスクラスに生成されるトランザクショナルなプロキシの種類を制御します。もしproxy-target-classtrueの場合、クラスベースプロキシが生成されます。proxy-target-classfalseもしくは属性を未指定の場合、標準JDKインタフェースベースプロキシが生成されます。(プロキシの種類に関する説明についてはSection 10.6, “Proxying mechanisms” を参照してください。)

@EnableTransactionManagement<tx:annotation-driven/>はそれが定義されているのと同一のアプリケーションコンテキストのビーンの@Transactionalのみ参照します。よって、DispatcherServlet向けのWebApplicationContextアノテーション駆動設定をする場合、コントローラーの@Transactionalビーンのみ参照し、サービスは含みません。詳細はSection 21.2, “The DispatcherServlet”を参照してください。

あるメソッドのトラザンクション設定評価時には最も派生した(derived)位置のものが優先します。以下の例では、DefaultFooServiceはクラスレベルにread-onlyトランザクション設定のアノテーションを付与していますが、同クラスのupdateFoo(Foo)メソッド@Transactionalアノテーションは、クラスレベルに定義したトランザクション設定よりも、優先されます。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}
@Transactional settings

@Transactionalアノテーションはインターフェース・クラス・メソッドに指定するメタデータトランザクションセマンティクスの指定が必須です。セマンティクスの例としては、"このメソッドが呼びされるとき、完全に新規のread-onlyトランザクションを開始し、既存のトランザクションサスペンドする"などです。デフォルトの@Transactionalは以下の通りです。

上記のデフォルト設定は変更可能で、@Transactional各プロパティの概要は以下の表の通りです。

Table 16.3. @

プロパティ 説明
value String オプション。使用するトランザクションマネージャを指定する識別子。
propagation enum: Propagation オプション。伝播設定。
isolation enum: Isolation オプション。分離レベル。
readOnly boolean Read/Write vs. read-only
timeout int(秒) トランザクションタイムアウト
rollbackFor Classオブジェクトの配列。Throwableの拡張が必須。 オプション。ロールバックのトリガ必須な例外クラスの配列
rollbackForClassName クラス名の配列クラス。Throwableの拡張が必須。 オプション。ロールバックのトリガ必須な例外クラス名の配列
noRollbackFor Classオブジェクトの配列。Throwableの拡張が必須。 オプション。ロールバックのトリガを抑制する例外クラスの配列
noRollbackForClassName Stringのクラス名の配列クラス。Throwableの拡張が必須。 オプション。ロールバックのトリガを抑制する例外クラス名の配列

現在のバージョンではトランザクション名を明示的に制御することは出来ません。ここでいうトランザクション名とは、トランザクションモニタに表示されるトランザクション名のことです。モニタが利用可能(たとえばWebLogicトランザクションモニタなど)であればログ出力に表示されます。宣言的トランザクションにおいては、トランザクション名は常に、完全修飾クラス名 + "." + トランザクションアドバイス対象クラスのメソッド名、になります。たとえば、BusinessServiceクラスのhandlePayment(..)メソッドトランザクションを開始した場合、トランザクション名はcom.foo.BusinessService.handlePaymentとなります。

Multiple Transaction Managers with @Transactional

ほとんどのSpringアプリケーションは単一のトランザクションマネージャのみ必要としますが、単一アプリケーション内に複数の独立したトランザクションマネージャを必要とする状況もありえます。@Transactionalアノテーションのvalue属性に、オプションで、使用したいPlatformTransactionManagerの識別子を指定可能です。この指定にはトランザクションマネージャ―のビーンのビーン名あるいは識別子の値を指定可能です。たとえば、識別子記法を使うには、以下のJavaコードとなります。

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

上記はアプリケーションコンテキストの以下のトランザクションマネージャビーン宣言と組み合わせます。

<tx:annotation-driven/>

    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    </bean>

    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    </bean>

この場合、TransactionalServiceの二つのメソッドは、"order"と"account"の識別子で区別される、別々のトランザクションマネージャ下で動作します。PlatformTransactionManagerビーンが見つからない場合、デフォルトの<tx:annotation-driven>のターゲットビーン名transactionManagerが使われます。

Custom shortcut annotations

多数の異なるメソッドに同じ属性の@Transactionalを繰り返し使うような場合、Springメタアノテーションサポートによりカスタムのショートカットアノテーションを定義可能です。例えば、以下のアノテーション定義があるとします。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

前述のセクションのサンプルは以下のように記述可能です。

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

ここではトランザクションマネージャの識別子を定義するためのシンタックスとして使用しています。なお、伝播の振る舞い・ロールバックルール・タイムアウトなどは含まれていません。

16.5.7 Transaction propagation

このセクションではSpringにおけるトランザクション伝播のセマンティクスについて解説します。注意点として、このセクションはトランザクション伝播の入門としては適切ではないです。そうではなく、Springのトランザクション伝播に関するセマンティクスの解説となっています。

Springマネージドトランザクションでは、物理(physical)と論理(logical)トランザクションの違いと、その違いに伝播設定がどのように適用されるか、について注意して下さい。

Required

Required

PROPAGATION_REQUIRED

伝播設定がPROPAGATION_REQUIREDの場合、設定の適用される各メソッドごとに論理トランザクションスコープが作成されます。内部トランザクションスコープとは論理的に独立した外部トランザクションスコープを使用して、各論理トランザクションスコープは個別にrollback-onlyを指定可能です。なお、標準的なPROPAGATION_REQUIREDの振る舞いの場合、今述べたスコープはすべて同一の物理トランザクションマッピングされます。よって、内部トランザクションスコープにおけるrollback-onlyマーカーの設定は、外部トランザクションではコミットの結果となります。

ただし、内部トランザクションスコープがrollback-onlyマーカーを設定する場合、外部トランザクション自身ではロールバックを決定しないので、(内部トランザクションスコープが暗黙にトリガする)ロールバックは予測できません(unexpected)。この場合にはその状況を表現するUnexpectedRollbackExceptionがスローされます。この例外スローそのものは予期可能な振る舞い(expected behavior)です。この例外スローが無い場合はコミットされる、とトランザクションの呼び出し元が想定する*4、という意味で予想可能な振る舞いです。そうではなく、ロールバックされるよう明確に指示するには、外部呼出し元がUnexpectedRollbackExceptionを受け取る必要があります。

RequiresNew

RequiresNew

PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEWは、PROPAGATION_REQUIREDと対照的に、各トランザクションスコープごとに完全に(completely)独立したトランザクションを使用します。この場合、基底の物理トランザクションは別のものとなり、よって、内部トランザクションロールバックステータスに影響されずに、外部トランザクションで個別にコミットやロールバックが可能です。

Nested

PROPAGATION_NESTEDは、ロールバック可能な複数セーブポイント単一の(single)物理トランザクションを使います。部分ロールバックにより内部トランザクションスコープはそのスコープで(for its scope)ロールバックのトリガが可能となります。外部トランザクションの物理トランザクションは部分ロールバックに関わらず継続可能です。この設定は通常はJDBCセーブポイントマッピングされるので、JDBCリソーストランザクションでだけ動作します。SpringのDataSourceTransactionManagerを参照してください。

16.5.8 Advising transactional operations

いま、トランザクションと基本的なプロファイリングアスペクトの両方を実行したい、とします。<tx:annotation-driven/>コンテキストではどのように実行すればよいでしょうか?

updateFoo(Foo)メソッド実行時に以下のアクションを実行したいとします。

このチャプターはAOPの詳細に関する解説は主目的ではありません(トランザクションの適用に関する箇所は除く)。一般的なAOPとその設定の詳細についてはChapter 10, Aspect Oriented Programming with Springを参照してください。

まず、以下はシンプルな説明用のプロファイリングアスペクトのコードです。アドバイスの順序はOrderedインタフェースで制御します。アドバイスの順序の詳細についてはthe section called “Advice ordering”を参照してください。

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // this method is the around advice
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" __value="1"__/>
    </bean>

    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>

    <aop:config>
            <!-- this advice will execute around the transactional advice -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

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

</beans>

上記の設定の結果としてfooServiceビーンは指定順序で(in the desired order)プロファイリングとトランザクションのアスペクトが適用されます。同様な方法で複数のアスペクトを追加設定可能です。

以下の設定例は上述と同じセットアップとなりますが、純粋なXML宣言のみを使用しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        __<property name="order" value="1__"/>
    </bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- will execute after the profiling advice (c.f. the order attribute) -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__"/>
        <!-- order value is higher than the profiling aspect -->

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

上記の設定の結果としてfooServiceビーンはその順序で(in that order)*5プロファイリングとトランザクションのアスペクトが適用されます。

もし、トランザクションアドバイスの後にプロファイリングアドバイスを実行したい場合、プロファイルリングアスペクトビーンのorderプロパティ値を、トランザクションアドバイスの順序値よりも高くするために、変更するだけです。

同様な方法で別のアスペクトも設定できます。

16.5.9 Using @Transactional with AspectJ

AspectJのアスペクトを使うことにより、Springコンテナ外でSpring Framework@Transactionalサポートを使うことも可能です。これを使うには、まず、@Transactionalをクラス(オプションでクラスのメソッド)に付与し、spring-aspects.jarファイルに定義されているorg.springframework.transaction.aspectj.AnnotationTransactionAspectとアプリケーションをリンク(ウィービング)します。このアスペクトはトランザクションマネージャと共に設定することが必須です。当然ながら、アスペクトのDIを処理するSpring Framework IoCコンテナとして使うことも可能です。トランザクション管理アスペクトを設定する最も簡単な方法は<tx:annotation-driven/>要素を使用し、Section 16.5.6, “Using @Transactional”で解説したmode属性にaspectjを指定します。ここではSpringコンテナ外でアプリケーションを実行することに焦点を当てるので、プログラム的な設定方法を説明します。

以降を読み進める前に、Section 16.5.6, “Using @Transactional”Chapter 10, Aspect Oriented Programming with Springをそれぞれ読む事を推奨します。

// 適当なトランザクションマネージャを生成する
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
// トランザクションマネージャを使うためにAnnotationTransactionAspectを設定する
// 以下のコードはトランザクションメソッドを実行する前に設定が必須。
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

このアスペクトを使う場合、クラスを実装するインタフェースではなく実装クラス(および/またはその実装クラス内のメソッド)にアノテーションを付与することが必須です。インタフェースのアノテーション継承されないというJavaのルールにAspectJは従います。

クラスに付与する@Transactionalは、そのクラスのメソッド実行のデフォルトトランザクションセマンティクスとなります。

あるクラス内のメソッドに付与する@Transactionalは、クラス(にもし指定していれば)に指定したデフォルトのトランザクションセマンティクスをオーバーライドします。可視性に関わらず、任意のメソッドアノテーションを付与可能です。

AnnotationTransactionAspectをアプリケーションにウィービングするには、AspectJでアプリケーションをビルドするか、ロード時ウィービングか、どちらかの使用が必須です。AspectJによるロード時ウィービングの解説はSection 10.8.4, “Load-time weaving with AspectJ in the Spring Framework”を参照してください。

*1:If the DataSource, used by any non-JTA transaction manager, is looked up via JNDI and managed by a Java EE container, then it should be non-transactional because the Spring Framework, rather than the Java EE container, will manage the transactions.が原文。it should be non-transactionalが自信ない

*2:AOP concepts do not generally have to be understood to make effective use of this codeが原文。make effective use of this codeが上手く訳せない…

*3:the Spring Framework’s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. が原文。code only marks a transaction for rollback in the case of runtimeが良く分からん

*4:caller of a transaction can never be misled to assumeが原文。直訳では、呼び出し元が~と仮定しても決して間違いではない、だが日本語にすると二重否定でくどいのでちょっと意訳した。

*5:一つ上はin the desired orderでこちらはin that orderになっている。どっちもxmlで順序を指定する点ではdesiredだと思うのだが、ニュアンスの違いが分からない。