http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。
Part V. Data Access
リファレンスドキュメントの当パートでは、データアクセスおよびデータアクセスレイヤとビジネスもしくはサービスレイヤーとの相互作用の焦点をあてます。
Springの包括的トランザクション管理サポートは、Spring Frameworkにより統合される各種データアクセスフレームワークと関連技術によってカバーされています。
- Chapter 16, Transaction Management
- Chapter 17, DAO support
- Chapter 18, Data access with JDBC
- Chapter 19, Object Relational Mapping (ORM) Data Access
- Chapter 20, Marshalling XML using O/X Mappers
16. Transaction Management
16.1 Introduction to Spring Framework transaction management
包括的トランザクション管理サポートはSpring Frameworkを使う最も良くある理由の一つです。Spring Frameworkは以下のような利点を提供するトランザクション管理用の一貫性のある抽象化(consistent abstraction)を提供します。
- Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), Java Data Objects (JDO)など異なるトランザクションAPIを横断する一貫性のある抽象化プログラミングモデル。
- 宣言的トランザクション管理のサポート
- JTAのような複雑なトランザクションAPIではないシンプルなプログラムによるトランザクション管理API
- Springデータアクセス抽象化との優れた統合
以降のセクションではSpring Frameworkのトランザクションの付加価値とテクノロジについて解説します。(また、このチャプターには、ベストプラクティス・アプリケーションサーバ統合・良くある問題の解決策、についての解説も含みます)
- Spring Frameworkのトランザクションサポートモデルの利点では、EJB Container-Managed Transactions (CMT)やHibernateなどプロプライエタリなAPIでローカルトランザクションを管理する代わりに、なぜSpring Frameworkのトランザクション抽象化を使うのか、について解説します。
- Spring Frameworkのトランザクション抽象化の理解では、コアクラスの概要について触れ、各種のデータソースから
DataSourceインスタンスの取得して設定する方法について解説します。 - トランザクションによるリソース同期化では、どのようにアプリケーションコードでリソースの生成・再利用・適切なクリーンアップを保証するか、について解説します。
- 宣言的トランザクション管理では、宣言的トランザクション管理のサポートについて解説します。
- プログラムによるトランザクション管理では、プログラム的なトランザクション管理(コードで明示的に管理する、という意味)のサポートについて解説します。
- Transaction bound event では、トランザクション内のアプリケーションイベントを使う方法について解説します。
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(例外モデルに起因)経由でグローバルトランザクションを管理します。また、JTAのUserTransactionは通常は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接続などローカルトランザクションのコードを書くのにJTAやEJB 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インタフェースは以下を定義します。
- Isolation: あるトランザクションと別トランザクションとの分離レベルの度合い。例えば、あるトランザクションが別トランザクションの未コミット書き込みを参照可能か? など。
- Propagation: 通常、トランザクションスコープ内で実行されるすべてのコードはそのトランザクション下で実行されます。ただし、トランザクションコンテキストが既に存在する場合に実行されるトランザクションメソッド上のイベントの振る舞いを指定するオプションがあります。例えば、既存トランザクション上での実行を続行する(ほとんどの場合はこちら)もしくは、既存トランザクションがサスペンド可能であれば新規トランザクションを生成する、など。SpringはEJB CMTと似たトランザクション伝播オプションをすべて提供しています(Spring offers all of the transaction propagation options familiar from EJB CMT)。Springのトランザクション伝播のセマンティクスについてはSection 16.5.7, “Transaction propagation”を参照してください。
- Timeout: タイムアウトまでのトランザクション実行時間と基底トランザクションインフラによる自動ロールバック。
- Read-only status: read-onlyトランザクションを使うと、読み込み可能だがデータ更新は出来なくなります。read-onlyトランザクションは、Hibernate使用時など、ある場合においては最適化に使えます。
これらの設定は標準的なトランザクションの考え方を反映しています。必要に応じて、トランザクション分離レベルやその他の核となるトランザクションの考え方に関する資料を参考にしてください。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のローカルトランザクションは簡単に使えます。この場合、HibernateのLocalSessionFactoryBeanを定義する必要あり、これによりアプリケーションコードでHibernateのSessionインスタンスを得られるようになります。
DataSourceビーン定義は先に見たローカルJDBCサンプルと同様なので以下のサンプルでは省略しています。
非JTAトランザクションマネージャで使用するDataSourceをJNDI経由でルックアップしたりJava EEコンテナで管理する場合、Java EEコンテナではなくSpring Frameworkがトランザクションを管理するので非トランザクションになります*1。
この場合にtxManagerビーンはHibernateTransactionManager型になります。DataSourceTransactionManagerがDataSourceへの参照を必要とするように、HibernateTransactionManagerはSessionFactoryへの参照を必要とします。
<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>
HibernateとJava EEコンテナ管理JTAトランザクションを使う場合、前述のJDBCでのJTAサンプルと同じようにJtaTransactionManagerを使えます。
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
JTAを使う場合、トランザクションマネージャ定義はJDBCやHibernate JPAやその他のデータアクセス技術に関わらず同じになります。JTAトランザクションはグローバルトランザクションであり任意のトランザクションリソースを取得可能、という事実に基づいています。
すべてのケースにおいて、アプリケーションコードを変更する必要はありません。ただ単に設定変更をすることでトランザクション管理の方法が変更可能で、たとえローカルからグローバルないしその逆へトランザクションを変える場合でも同様です。
16.4 Synchronizing resources with transactions
異なるトランザクションマネージャを生成する方法と、同期化の必要がある関連リソースにトランザクションをリンクする方法(例:JDBC DataSourceにDataSourceTransactionManager, Hibernate SessionFactoryにHibernateTransactionManagerなど)について解説します。このセクションでは、リソースの適切な生成・再利用・クリーンアップを、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の場合、伝統的なDataSourceのgetConnection()メソッドを呼んで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のJdbcTemplateやjdbc.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()を呼び出せます。二つのトランザクション管理の違いは以下の通りです。
- JTAと結合しているEJB CMTと異なり、Spring Frameworkの宣言的トランザクション管理は任意の環境上で動作します。設定ファイルを調整することで、JTAトランザクションもしくはJDBC, JPA, Hibernate, JDOを使うローカルトランザクションで動かせます。
- EJBなど特別なクラスではなく、任意のクラスにSpring Frameworkの宣言的トランザクション管理を適用可能です。
- Spring Frameworkでは、EJBには相当する機能がない、宣言的rollback rulesを提供します。ロールバックルールは、宣言的・プログラム的両方をサポートしています。
- Spring FrameworkはAOPを使うことでトランザクションの振る舞いをカスタマイズ可能です。たとえば、トランザクションロールバック時にカスタムの振る舞いを挿入可能です。トランザクショナルなアドバイスに加えて、任意のアドバイスを追加可能です。EJB CMTでは、
setRollbackOnly()を除いてコンテナのトランザクション管理には影響を与えられません。 - Spring Frameworkは、ハイエンドアプリケーションサーバがするような、リモートコール越しのトランザクションコンテキストの伝播はサポートしません。この機能が必要な場合はEJBを使うことを推奨します。ただし、そうした機能の使用前熟考が必要です。なぜなら、ほとんどの場合、リモートコールを拡張するトランザクションは必要ないためです。
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プロキシ経由で有効化されるもので、トランザクショナルなアドバイスがmetadata(XMLもしくはアノテーション)によって駆動されます。トランザクショナルなメタデータとAOPの組み合わせはAOPプロキシを構成し、そのプロキシはaroundメソッド実行でトランザクションを駆動する適切なPlatformTransactionManager実装とTransactionInterceptorの組み合わせを使います。
Spring AOPについてはChapter 10, Aspect Oriented Programming with Springを参照してください。
概念的には、トランザクショナルなプロキシ上でのメソッド呼び出しは以下のようになります。

16.5.2 Example of declarative transaction implementation
以下のインタフェースとそれに付随する実装を考えてみます。以下のサンプルは、特定のドメインモデルに基づくことなくトランザクションの使用法に集中するため、プレースホルダとしてFooとBarクラスを使っています。サンプルの目的のために、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
いま、多数のサービスレイヤーオブジェクトがあるとして、それぞれに全く異なるトランザクション設定をしたい、とします。これの実現には、異なるpointcutとadvice-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/>設定は、
- Propagation settingは
REQUIRED - 分離レベルは
DEFAULT - トランザクションはread/write
- トランザクションタイムアウトのデフォルトは基底トランザクションシステムのデフォルトタイムアウトで、タイムアウトが未サポートの場合は無し。
RuntimeExceptionはロールバックをトリガし、チェックExceptionはロールバックしない。
これらのデフォルト設定は変更可能です。<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(TransactionManagementConfigurerのjavadocを参照) |
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-classがtrueの場合、クラスベースプロキシが生成されます。proxy-target-classがfalseもしくは属性を未指定の場合、標準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は以下の通りです。
- 伝播設定
PROPAGATION_REQUIRED - 分離レベル
ISOLATION_DEFAULT - トランザクション read/write
- トランザクションタイムアウトは基底トランザクションシステムのデフォルトタイムアウト、タイムアウトが未サポートの場合は無し。
RuntimeExceptionはロールバックをトリガし、チェックExceptionはトリガしない。
上記のデフォルト設定は変更可能で、@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

PROPAGATION_REQUIRED
伝播設定がPROPAGATION_REQUIREDの場合、設定の適用される各メソッドごとに論理トランザクションスコープが作成されます。内部トランザクションスコープとは論理的に独立した外部トランザクションスコープを使用して、各論理トランザクションスコープは個別にrollback-onlyを指定可能です。なお、標準的なPROPAGATION_REQUIREDの振る舞いの場合、今述べたスコープはすべて同一の物理トランザクションにマッピングされます。よって、内部トランザクションスコープにおけるrollback-onlyマーカーの設定は、外部トランザクションではコミットの結果となります。
ただし、内部トランザクションスコープがrollback-onlyマーカーを設定する場合、外部トランザクション自身ではロールバックを決定しないので、(内部トランザクションスコープが暗黙にトリガする)ロールバックは予測できません(unexpected)。この場合にはその状況を表現するUnexpectedRollbackExceptionがスローされます。この例外スローそのものは予期可能な振る舞い(expected behavior)です。この例外スローが無い場合はコミットされる、とトランザクションの呼び出し元が想定する*4、という意味で予想可能な振る舞いです。そうではなく、ロールバックされるよう明確に指示するには、外部呼出し元がUnexpectedRollbackExceptionを受け取る必要があります。
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だと思うのだが、ニュアンスの違いが分からない。