リファレンスドキュメントのこのパートは、データアクセスと、データアクセス層とビジネス層またはサービス層との間の相互作用に関係しています。

Spring の包括的なトランザクション管理サポートについてはある程度詳しく説明し、その後、Spring Framework が統合するさまざまなデータアクセスフレームワークとテクノロジーを徹底的に取り上げます。

1. トランザクション管理

包括的なトランザクションサポートは、Spring Framework を使用する最も説得力のある理由の 1 つです。Spring Framework は、以下の利点を提供するトランザクション管理の一貫した抽象化を提供します。

  • Java Transaction API(JTA)、JDBC、Hibernate、Java Persistence API(JPA)などのさまざまなトランザクション API にわたる一貫したプログラミングモデル。

  • 宣言的なトランザクション管理のサポート。

  • JTA などの複雑なトランザクション API よりも、プログラムによるトランザクション管理のためのシンプルな API。

  • Spring のデータアクセス抽象化との優れた統合。

以下のセクションでは、Spring Framework のトランザクション機能とテクノロジーについて説明します。

また、ベストプラクティス、アプリケーションサーバーの統合、および一般的な問題の解決策についても説明します。

1.1. Spring Framework のトランザクションサポートモデルの利点

従来、Java EE 開発者は、トランザクション管理に 2 つの選択肢がありました。グローバルトランザクションまたはローカルトランザクションで、どちらにも大きな制限があります。グローバルおよびローカルトランザクション管理については、次の 2 つのセクションでレビューし、その後、Spring Framework のトランザクション管理サポートがグローバルおよびローカルトランザクションモデルの制限にどのように対処するかについて説明します。

1.1.1. グローバルトランザクション

グローバルトランザクションを使用すると、複数のトランザクションリソース、通常はリレーショナルデータベースとメッセージキューを操作できます。アプリケーションサーバーは、面倒な API である JTA を介してグローバルトランザクションを管理します(一部は例外モデルのため)。さらに、JTA UserTransaction は通常 JNDI をソースとする必要があります。つまり、JTA を使用するには JNDI も使用する必要があります。JTA は通常アプリケーションサーバー環境でのみ使用できるため、グローバルトランザクションを使用すると、アプリケーションコードの再利用の可能性が制限されます。

以前は、グローバルトランザクションを使用する推奨する方法は、EJB CMT(コンテナー管理トランザクション)を使用することでした。CMT は宣言型トランザクション管理の形式です(プログラムによるトランザクション管理とは区別されます)。EJB CMT を使用すると、トランザクション関連の JNDI ルックアップが不要になりますが、EJB 自体を使用するには JNDI を使用する必要があります。トランザクションを制御する Java コードを記述する必要性のすべてではなく、ほとんどを取り除きます。重大な欠点は、CMT が JTA およびアプリケーションサーバー環境に結び付けられていることです。また、EJB(または少なくともトランザクション EJB ファサードの背後)にビジネスロジックを実装することを選択した場合にのみ使用できます。一般に EJB の欠点は非常に大きいため、特に宣言型トランザクション管理の強力な代替手段に直面して、これは魅力的な提案ではありません。

1.1.2. ローカルトランザクション

ローカルトランザクションは、JDBC 接続に関連付けられたトランザクションなど、リソース固有です。ローカルトランザクションは使いやすいかもしれませんが、重大な欠点があります。複数のトランザクションリソース間で機能しません。例:JDBC 接続を使用してトランザクションを管理するコードは、グローバル JTA トランザクション内で実行できません。アプリケーションサーバーはトランザクション管理に関与しないため、複数のリソースにわたって正確性を確保するのに役立ちません。(ほとんどのアプリケーションが単一のトランザクションリソースを使用することは注目に値します)別の欠点は、ローカルトランザクションがプログラミングモデルに侵入することです。

1.1.3. Spring Framework の一貫したプログラミングモデル

Spring は、グローバルおよびローカルトランザクションの欠点を解決します。アプリケーション開発者は、あらゆる環境で一貫したプログラミングモデルを使用できます。コードを一度書くだけで、さまざまな環境でさまざまなトランザクション管理戦略を活用できます。Spring Framework は、宣言的およびプログラム的なトランザクション管理の両方を提供します。ほとんどのユーザーは宣言的なトランザクション管理を好みますが、ほとんどの場合これをお勧めします。

プログラムによるトランザクション管理により、開発者は Spring Framework トランザクションの抽象化を使用します。これは、基盤となるトランザクションインフラストラクチャ上で実行できます。推奨される宣言モデルを使用すると、開発者は通常、トランザクション管理に関連するコードをほとんどまたはまったく記述しないため、Spring Framework トランザクション API やその他のトランザクション API に依存しません。

トランザクション管理にアプリケーションサーバーが必要ですか?

Spring Framework のトランザクション管理サポートは、エンタープライズ Java アプリケーションがアプリケーションサーバーを必要とする場合に関する従来のルールを変更します。

特に、EJB を介した宣言的なトランザクションのためだけにアプリケーションサーバーは必要ありません。実際、アプリケーションサーバーに強力な JTA 機能がある場合でも、Spring Framework の宣言型トランザクションが EJB CMT よりも強力で生産的なプログラミングモデルを提供すると判断する場合があります。

通常、アプリケーションサーバーが JTA 機能を必要とするのは、アプリケーションが複数のリソースにわたるトランザクションを処理する必要がある場合のみです。これは多くのアプリケーションの要件ではありません。多くのハイエンドアプリケーションは、代わりに単一の高度にスケーラブルなデータベース(Oracle RAC など)を使用します。スタンドアロンのトランザクションマネージャー(Atomikos トランザクション (英語) JOTM (英語) など)も他のオプションです。もちろん、Java Message Service(JMS)や Java EE Connector Architecture(JCA)など、他のアプリケーションサーバー機能が必要になる場合があります。

Spring Framework を使用すると、アプリケーションを完全にロードされたアプリケーションサーバーに拡張するタイミングを選択できます。EJB CMT または JTA を使用する唯一の代替手段は、ローカルトランザクション(JDBC 接続など)でコードを記述し、そのコードをコンテナー管理のグローバルトランザクション内で実行する必要がある場合に多額の手直しに直面することでした。Spring Framework では、構成ファイル内の Bean 定義の一部のみを(コードではなく)変更する必要があります。

1.2. Spring Framework トランザクション抽象化の理解

Spring トランザクション抽象化の鍵は、トランザクション戦略の概念です。トランザクション戦略は TransactionManager によって定義されます。具体的には、命令型トランザクション管理のための org.springframework.transaction.PlatformTransactionManager インターフェースとリアクティブトランザクション管理のための org.springframework.transaction.ReactiveTransactionManager インターフェースです。次のリストは、PlatformTransactionManager API の定義を示しています。

Java
public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}
Kotlin
interface PlatformTransactionManager : TransactionManager {

    @Throws(TransactionException::class)
    fun getTransaction(definition: TransactionDefinition): TransactionStatus

    @Throws(TransactionException::class)
    fun commit(status: TransactionStatus)

    @Throws(TransactionException::class)
    fun rollback(status: TransactionStatus)
}

これは主にサービスプロバイダーインターフェース(SPI)ですが、アプリケーションコードからプログラムで使用することもできます。PlatformTransactionManager はインターフェースであるため、必要に応じて簡単にモックまたはスタブ化できます。JNDI などのルックアップ戦略とは関係ありません。PlatformTransactionManager 実装は、Spring Framework IoC コンテナー内の他のオブジェクト(または Bean)と同様に定義されます。この利点だけで、JTA を使用する場合でも、Spring Framework トランザクションは価値のある抽象化になります。JTA を直接使用した場合よりもはるかに簡単にトランザクションコードをテストできます。

再び、Spring の哲学に沿って、PlatformTransactionManager インターフェースのメソッドのいずれかによってスローされる TransactionException はチェックされていません(つまり、java.lang.RuntimeException クラスを継承します)。トランザクションインフラストラクチャの障害は、ほぼ常に致命的です。アプリケーションコードがトランザクション障害から実際に回復できるまれなケースでは、アプリケーション開発者は TransactionException をキャッチして処理することを選択できます。重要な点は、開発者がそうすること強制されていないということです。

getTransaction(..) メソッドは、TransactionDefinition パラメーターに応じて、TransactionStatus オブジェクトを返します。返された TransactionStatus は、現在の呼び出しスタックに一致するトランザクションが存在する場合、新しいトランザクションを表すか、既存のトランザクションを表すことができます。後者の場合の意味は、Java EE トランザクションコンテキストと同様に、TransactionStatus は実行スレッドに関連付けられているということです。

Spring Framework 5.2, 以降、Spring は、リアクティブタイプまたは Kotlin コルーチンを利用するリアクティブアプリケーションのトランザクション管理の抽象化も提供します。次のリストは、org.springframework.transaction.ReactiveTransactionManager によって定義されたトランザクション戦略を示しています。

Java
public interface ReactiveTransactionManager extends TransactionManager {

    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;

    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;

    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}
Kotlin
interface ReactiveTransactionManager : TransactionManager {

    @Throws(TransactionException::class)
    fun getReactiveTransaction(definition: TransactionDefinition): Mono<ReactiveTransaction>

    @Throws(TransactionException::class)
    fun commit(status: ReactiveTransaction): Mono<Void>

    @Throws(TransactionException::class)
    fun rollback(status: ReactiveTransaction): Mono<Void>
}

リアクティブトランザクションマネージャーは、主にサービスプロバイダーインターフェース(SPI)ですが、アプリケーションコードからプログラムで使用することもできます。ReactiveTransactionManager はインターフェースであるため、必要に応じて簡単にモックまたはスタブできます。

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

  • 伝搬 : 通常、トランザクションスコープ内のすべてのコードは、そのトランザクションで実行されます。ただし、トランザクションコンテキストがすでに存在するときにトランザクションメソッドが実行される場合の動作を指定できます。例:コードは既存のトランザクションで実行を継続できます(一般的なケース)。または、既存のトランザクションを中断して新しいトランザクションを作成できます。Spring は、EJB CMT でよく知られているすべてのトランザクション伝播オプションを提供します。Spring でのトランザクション伝播のセマンティクスについては、トランザクションの伝播を参照してください。

  • 分離 : このトランザクションが他のトランザクションの作業から分離される程度。例:このトランザクションは、他のトランザクションからのコミットされていない書き込みを見ることができますか?

  • タイムアウト : タイムアウトし、基礎となるトランザクションインフラストラクチャによって自動的にロールバックされるまでに、このトランザクションが実行される時間。

  • 読み取り専用状態 : コードが読み取りを行うがデータを変更しない場合は、読み取り専用トランザクションを使用できます。読み取り専用トランザクションは、Hibernate を使用する場合など、場合によっては便利な最適化になります。

これらの設定は、標準的なトランザクションの概念を反映しています。必要に応じて、トランザクション分離レベルおよびその他のコアトランザクションの概念について説明しているリソースを参照してください。Spring Framework またはトランザクション管理ソリューションを使用するには、これらの概念を理解することが不可欠です。

TransactionStatus インターフェースは、トランザクションコードがトランザクションの実行を制御し、トランザクションステータスを照会する簡単な方法を提供します。すべてのトランザクション API に共通しているため、概念はよく知っている必要があります。次のリストは、TransactionStatus インターフェースを示しています。

Java
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

    @Override
    boolean isNewTransaction();

    boolean hasSavepoint();

    @Override
    void setRollbackOnly();

    @Override
    boolean isRollbackOnly();

    void flush();

    @Override
    boolean isCompleted();
}
Kotlin
interface TransactionStatus : TransactionExecution, SavepointManager, Flushable {

    override fun isNewTransaction(): Boolean

    fun hasSavepoint(): Boolean

    override fun setRollbackOnly()

    override fun isRollbackOnly(): Boolean

    fun flush()

    override fun isCompleted(): Boolean
}

Spring で宣言型トランザクション管理を選択するか、プログラムによるトランザクション管理を選択するかに関係なく、正しい TransactionManager 実装を定義することは絶対に不可欠です。通常、この実装は依存性注入を通じて定義します。

TransactionManager 実装では、通常、動作する環境(JDBC、JTA、Hibernate など)の知識が必要です。次の例は、ローカル PlatformTransactionManager 実装を定義する方法を示しています (この場合、プレーン JDBC を使用します。)

次のような Bean を作成することにより、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 Bean 定義には、DataSource 定義への参照が含まれます。次の例のようになります。

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

Java EE コンテナーで JTA を使用する場合、Spring の JtaTransactionManager とともに、JNDI から取得したコンテナー DataSource を使用します。次の例は、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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://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 Bean の定義では、jee 名前空間の <jndi-lookup/> タグを使用しています。詳細については、JEE スキーマを参照してください。
JTA を使用する場合、使用するデータアクセステクノロジー(JDBC、Hibernate JPA、またはその他のサポートされるテクノロジー)に関係なく、トランザクションマネージャーの定義は同じように見えるはずです。これは、JTA トランザクションがすべてのトランザクションリソースを登録できるグローバルトランザクションであるためです。

すべての Spring トランザクション設定で、アプリケーションコードを変更する必要はありません。構成を変更するだけでトランザクションの管理方法を変更できます。その変更がローカルトランザクションからグローバルトランザクションへの移行、またはその逆を意味する場合でも同様です。

1.2.1. Hibernate トランザクション設定

次の例に示すように、Hibernate ローカルトランザクションを簡単に使用することもできます。この場合、アプリケーションコードが Hibernate Session インスタンスを取得するために使用できる Hibernate LocalSessionFactoryBean を定義する必要があります。

DataSource Bean 定義は、前に示したローカル JDBC の例と似ているため、以下の例には示されていません。

DataSource (非 JTA トランザクションマネージャーによって使用される)が JNDI を通じてルックアップされ、Java EE コンテナーによって管理される場合、Spring Framework(Java EE コンテナーではなく)がトランザクションを管理するため、非トランザクションである必要があります。

この場合、txManager Bean は HibernateTransactionManager タイプです。DataSourceTransactionManager が DataSource への参照を必要とするのと同じ方法で、HibernateTransactionManager は SessionFactory への参照を必要とします。次の例では、sessionFactory および txManager Bean を宣言しています。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Hibernate および Java EE コンテナー管理 JTA トランザクションを使用する場合は、次の例に示すように、JDBC の前の JTA 例と同じ JtaTransactionManager を使用する必要があります。また、Hibernate にトランザクションコーディネーターおよび場合によっては接続解放モードの構成を通じて JTA を認識させることをお勧めします。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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}
            hibernate.transaction.coordinator_class=jta
            hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
        </value>
    </property>
</bean>

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

または、同じデフォルトを適用するために、JtaTransactionManager を LocalSessionFactoryBean に渡すこともできます。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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>
    <property name="jtaTransactionManager" ref="txManager"/>
</bean>

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

1.3. リソースとトランザクションの同期

さまざまなトランザクションマネージャーを作成する方法、およびトランザクションに同期する必要がある関連リソース(たとえば、DataSourceTransactionManager を JDBC DataSource に、HibernateTransactionManager を Hibernate SessionFactory などに)にリンクする方法が明確になりました。このセクションでは、アプリケーションコードが(JDBC、Hibernate、JPA などの永続 API を使用して直接的または間接的に)これらのリソースが適切に作成、再利用、クリーンアップされることを保証する方法について説明します。このセクションでは、関連する TransactionManager を介してトランザクション同期が(オプションで)トリガーされる方法についても説明します。

1.3.1. 高レベルの同期アプローチ

推奨されるアプローチは、Spring の最高レベルのテンプレートベースの永続化統合 API を使用するか、トランザクション対応のファクトリ Bean またはネイティブリソースファクトリを管理するためのプロキシでネイティブ ORM API を使用することです。これらのトランザクション対応ソリューションは、リソースの作成と再利用、クリーンアップ、オプションのリソースのトランザクション同期、および例外マッピングを内部的に処理します。ユーザーデータアクセスコードはこれらのタスクに対処する必要はありませんが、ボイラープレート以外の永続化ロジックのみに集中できます。通常、ネイティブ ORM API を使用するか、JdbcTemplate を使用して JDBC アクセスのテンプレートアプローチを採用します。これらのソリューションについては、このリファレンスドキュメントの後続のセクションで詳しく説明します。

1.3.2. 低レベル同期アプローチ

DataSourceUtils (JDBC の場合)、EntityManagerFactoryUtils (JPA の場合)、SessionFactoryUtils (Hibernate の場合)などのクラスが下位レベルに存在します。アプリケーションコードでネイティブ永続性 API のリソースタイプを直接処理する場合、これらのクラスを使用して、Spring フレームワークで管理された適切なインスタンスが取得され、トランザクションが(オプションで)同期され、プロセスで発生する例外が確実に発生するようにします。一貫性のある API に適切にマッピングされます。

例:JDBC の場合、DataSource で getConnection() メソッドを呼び出す従来の JDBC アプローチの代わりに、次のように Spring の org.springframework.jdbc.datasource.DataSourceUtils クラスを使用できます。

Connection conn = DataSourceUtils.getConnection(dataSource);

既存のトランザクションに既に同期(リンク)された接続がある場合、そのインスタンスが返されます。それ以外の場合、メソッド呼び出しは、新しい接続の作成をトリガーします。この接続は、(オプションで)既存のトランザクションと同期され、同じトランザクションで後続の再利用が可能になります。前述のように、SQLException は Spring Framework CannotGetJdbcConnectionException にラップされます。これは、Spring Framework の未チェック DataAccessException 型階層の 1 つです。このアプローチは、SQLException から簡単に取得できるよりも多くの情報を提供し、データベース間、さらには異なる永続化テクノロジー間でも移植性を保証します。

このアプローチは、Spring トランザクション管理なしでも機能するため(トランザクション同期はオプションです)、トランザクション管理に Spring を使用するかどうかに関係なく使用できます。

もちろん、Spring の JDBC サポート、JPA サポート、または Hibernate サポートを使用した後は、DataSourceUtils やその他のヘルパークラスを使用しない方が一般的です。これは、関連する API を直接使用するよりも Spring 抽象化を通じて作業する方がはるかに幸せです。例:Spring JdbcTemplate または jdbc.object パッケージを使用して JDBC の使用を簡素化する場合、バックグラウンドで正しい接続の取得が行われ、特別なコードを記述する必要はありません。

1.3.3. TransactionAwareDataSourceProxy

最下位レベルには TransactionAwareDataSourceProxy クラスが存在します。これは、ターゲット DataSource のプロキシであり、ターゲット DataSource をラップして、Spring 管理のトランザクションの認識を追加します。この点で、Java EE サーバーによって提供されるトランザクション JNDI DataSource に似ています。

既存のコードを呼び出して標準 JDBC DataSource インターフェース実装を渡す必要がある場合を除き、このクラスを使用する必要はほとんどありません。その場合、このコードは使用可能ですが、Spring 管理のトランザクションに参加している可能性があります。前述の高レベルの抽象化を使用して、新しいコードを作成できます。

1.4. 宣言的なトランザクション管理

ほとんどの Spring Framework ユーザーは、宣言的なトランザクション管理を選択します。このオプションは、アプリケーションコードへの影響が最も少ないため、非侵襲的な軽量コンテナーの理想と最も一致しています。

Spring Framework の宣言的なトランザクション管理は、Spring アスペクト指向プログラミング(AOP)によって可能になります。ただし、トランザクションアスペクトコードは Spring Framework ディストリビューションに付属しており、定型的な方法で使用できるため、このコードを効果的に使用するために AOP の概念を一般的に理解する必要はありません。

Spring Framework の宣言的なトランザクション管理は、EJB CMT に似ており、個々のメソッドレベルまでトランザクションの動作(またはその欠如)を指定できます。必要に応じて、トランザクションコンテキスト内で setRollbackOnly() 呼び出しを行うことができます。2 種類のトランザクション管理の違いは次のとおりです。

  • JTA に関連付けられている EJB CMT とは異なり、Spring Framework の宣言的トランザクション管理はどの環境でも機能します。JDBC、JPA、または Hibernate を使用して構成ファイルを調整することにより、JTA トランザクションまたはローカルトランザクションを処理できます。

  • Spring Framework 宣言型トランザクション管理は、EJB などの特別なクラスだけでなく、任意のクラスに適用できます。

  • Spring Framework は、EJB に相当する機能のない、宣言的なロールバックルールを提供します。ロールバックルールのプログラムと宣言の両方のサポートが提供されます。

  • Spring Framework では、AOP を使用してトランザクションの動作をカスタマイズできます。例:トランザクションのロールバックの場合にカスタム動作を挿入できます。トランザクションアドバイスとともに、任意のアドバイスを追加することもできます。EJB CMT では、setRollbackOnly() を除き、コンテナーのトランザクション管理に影響を与えることはできません。

  • Spring Framework は、ハイエンドアプリケーションサーバーのように、リモートコール間でのトランザクションコンテキストの伝播をサポートしません。この機能が必要な場合は、EJB を使用することをお勧めします。ただし、通常、トランザクションがリモートコールにまたがることを望まないため、このような機能を使用する前に慎重に検討してください。

ロールバックルールの概念は重要です。これらを使用すると、自動ロールバックを引き起こす例外(およびスロー可能オブジェクト)を指定できます。これは、Java コードではなく、構成で宣言的に指定できます。そのため、TransactionStatus オブジェクトで setRollbackOnly() を呼び出して現在のトランザクションをロールバックできますが、ほとんどの場合、MyApplicationException が常にロールバックする必要があるというルールを指定できます。このオプションの大きな利点は、ビジネスオブジェクトがトランザクションインフラストラクチャに依存しないことです。例:通常、Spring トランザクション API または他の Spring API をインポートする必要はありません。

EJB コンテナーのデフォルトの動作は、システム例外(通常はランタイム例外)でトランザクションを自動的にロールバックしますが、EJB CMT はアプリケーション例外(つまり、java.rmi.RemoteException 以外のチェック済み例外)でトランザクションを自動的にロールバックしません。宣言的トランザクション管理の Spring のデフォルトの動作は EJB の規則に従います(ロールバックは未チェックの例外に対してのみ自動化されます)が、この動作をカスタマイズすると便利な場合があります。

1.4.1. Spring Framework の宣言的トランザクションの実装を理解する

クラスに @Transactional アノテーションを付け、構成に @EnableTransactionManagement を追加し、すべてがどのように機能するかを理解するように指示するだけでは不十分です。より深い理解を提供するために、このセクションでは、トランザクション関連の課題のコンテキストで、Spring Framework の宣言型トランザクションインフラストラクチャの内部動作について説明します。

Spring Framework の宣言的なトランザクションサポートに関して把握する最も重要な概念は、このサポートが AOP プロキシを介して有効になり、トランザクションアドバイスがメタデータ(現在は XML またはアノテーションベース)によって駆動されることです。AOP とトランザクションメタデータの組み合わせにより、TransactionInterceptor を適切な TransactionManager 実装と組み合わせて使用して、メソッド呼び出しにトランザクションを駆動する AOP プロキシが生成されます。

Spring フレームワークの TransactionInterceptor は、命令型およびリアクティブ型プログラミングモデルのトランザクション管理を提供します。インターセプターは、メソッドの戻り値の型をインスペクションすることにより、トランザクション管理の望ましい種類を検出します。Publisher または Kotlin Flow (またはそれらのサブタイプ)などのリアクティブタイプを返すメソッドは、リアクティブトランザクション管理の対象となります。void を含む他のすべての戻り値の型は、命令型のトランザクション管理にコードパスを使用します。

トランザクション管理の種類は、必要なトランザクションマネージャーに影響を与えます。命令型トランザクションでは PlatformTransactionManager が必要ですが、リアクティブ型トランザクションでは ReactiveTransactionManager 実装を使用します。

次の図は、トランザクションプロキシでメソッドを呼び出す概念図を示しています。

tx

1.4.2. 宣言的トランザクション実装の例

以下のインターフェースとそれに付随する実装を検討してください。この例では、Foo クラスと Bar クラスをプレースホルダーとして使用しているため、特定のドメインモデルに焦点を当てずにトランザクションの使用に集中できます。この例の目的のために、DefaultFooService クラスが実装された各メソッドの本体で UnsupportedOperationException インスタンスをスローするという事実は良いことです。この動作により、トランザクションが作成され、UnsupportedOperationException インスタンスにレスポンスしてロールバックされるのを確認できます。次のリストは、FooService インターフェースを示しています。

Java
// 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);

}
Kotlin
// the service interface that we want to make transactional

package x.y.service

interface FooService {

    fun getFoo(fooName: String): Foo

    fun getFoo(fooName: String, barName: String): Foo

    fun insertFoo(foo: Foo)

    fun updateFoo(foo: Foo)
}

次の例は、前述のインターフェースの実装を示しています。

Java
package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
package x.y.service

class DefaultFooService : FooService {

    override fun getFoo(fooName: String): Foo {
        // ...
    }

    override fun getFoo(fooName: String, barName: String): Foo {
        // ...
    }

    override fun insertFoo(foo: Foo) {
        // ...
    }

    override fun updateFoo(foo: Foo) {
        // ...
    }
}

FooService インターフェースの最初の 2 つのメソッド getFoo(String) および getFoo(String, String) は、読み取り専用のセマンティクスを持つトランザクションのコンテキストで実行する必要があり、他のメソッド insertFoo(Foo) および updateFoo(Foo) は、読み取り / 書き込みを持つトランザクションのコンテキストで実行する必要があると仮定します。セマンティクス。次の構成について、次のいくつかの段落で詳しく説明します。

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

上記の構成を調べます。サービスオブジェクトである fooService Bean をトランザクションにしたいと想定しています。適用するトランザクションセマンティクスは、<tx:advice/> 定義にカプセル化されています。<tx:advice/> 定義は、「 get で始まるすべてのメソッドは読み取り専用トランザクションのコンテキストで実行され、他のすべてのメソッドはデフォルトのトランザクションセマンティクスで実行される」と解釈されます。<tx:advice/> タグの transaction-manager 属性は、トランザクションを駆動する TransactionManager Bean(この場合は txManager Bean)の名前に設定されます。

ワイヤリングする TransactionManager の Bean 名の名前が transactionManager である場合、トランザクションアドバイス(<tx:advice/>)の transaction-manager 属性を省略できます。接続する TransactionManager Bean に他の名前がある場合、前述の例のように、transaction-manager 属性を明示的に使用する必要があります。

<aop:config/> 定義は、txAdvice Bean によって定義されたトランザクションアドバイスがプログラムの適切なポイントで実行されるようにします。まず、FooService インターフェース(fooServiceOperation)で定義された操作の実行と一致するポイントカットを定義します。次に、アドバイザーを使用して、ポイントカットを txAdvice に関連付けます。結果は、fooServiceOperation の実行時に、txAdvice によって定義されたアドバイスが実行されることを示しています。

<aop:pointcut/> 要素内で定義された式は、AspectJ ポイントカット式です。Spring のポイントカット式の詳細については、AOP セクションを参照してください。

一般的な要件は、サービス層全体をトランザクション化することです。これを行う最善の方法は、ポイントカット式を変更して、サービスレイヤー内の任意の操作に一致させることです。次の例は、その方法を示しています。

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
上記の例では、すべてのサービスインターフェースが x.y.service パッケージで定義されていることを前提としています。詳細については、AOP セクションを参照してください。

これで構成を分析したため、「この構成はすべて実際に何をしているのですか?」

前に示した構成は、fooService Bean 定義から作成されたオブジェクトの周囲にトランザクションプロキシを作成するために使用されます。プロキシは、適切なメソッドがプロキシで呼び出されると、そのメソッドに関連付けられたトランザクション構成に応じて、トランザクションが開始、一時停止、読み取り専用などのように、トランザクションアドバイスで構成されます。前に示した構成をテスト駆動する次のプログラムを検討してください。

Java
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());
    }
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = ClassPathXmlApplicationContext("context.xml")
    val fooService = ctx.getBean<FooService>("fooService")
    fooService.insertFoo(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 [[email protected] (英語)  ] 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 [[email protected] (英語)  ]
[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)

リアクティブトランザクション管理を使用するには、コードでリアクティブタイプを使用する必要があります。

Spring Framework は ReactiveAdapterRegistry を使用して、メソッドの戻り値の型がリアクティブかどうかを判断します。

以下のリストは、以前に使用された FooService の変更バージョンを示していますが、今回はコードがリアクティブタイプを使用しています。

Java
// the reactive service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Flux<Foo> getFoo(String fooName);

    Publisher<Foo> getFoo(String fooName, String barName);

    Mono<Void> insertFoo(Foo foo);

    Mono<Void> updateFoo(Foo foo);

}
Kotlin
// the reactive service interface that we want to make transactional

package x.y.service

interface FooService {

    fun getFoo(fooName: String): Flow<Foo>

    fun getFoo(fooName: String, barName: String): Publisher<Foo>

    fun insertFoo(foo: Foo) : Mono<Void>

    fun updateFoo(foo: Foo) : Mono<Void>
}

次の例は、前述のインターフェースの実装を示しています。

Java
package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
package x.y.service

class DefaultFooService : FooService {

    override fun getFoo(fooName: String): Flow<Foo> {
        // ...
    }

    override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
        // ...
    }

    override fun insertFoo(foo: Foo): Mono<Void> {
        // ...
    }

    override fun updateFoo(foo: Foo): Mono<Void> {
        // ...
    }
}

命令型とリアクティブ型のトランザクション管理は、トランザクション境界とトランザクション属性の定義で同じセマンティクスを共有します。命令型トランザクションとリアクティブトランザクションの主な違いは、後者の遅延性質です。TransactionInterceptor は、返されたリアクティブタイプをトランザクションオペレーターで装飾して、トランザクションを開始およびクリーンアップします。トランザクションのリアクティブメソッドを呼び出すと、実際のトランザクション管理は、リアクティブタイプの処理をアクティブにするサブスクリプションタイプに委ねられます。

リアクティブトランザクション管理の別の側面は、プログラミングモデルの自然な結果であるデータエスケープに関連しています。

命令型トランザクションのメソッド戻り値は、メソッドの正常終了時にトランザクションメソッドから返されるため、部分的に計算された結果がメソッドクロージャをエスケープしません。

リアクティブトランザクションメソッドは、計算シーケンスを表すリアクティブラッパータイプと、計算を開始して完了するという約束を返します。

Publisher は、トランザクションの進行中にデータを送信できますが、必ずしも完了しているとは限りません。トランザクション全体の正常な補完に依存するメソッドは、完了を確実にし、呼び出しコードで結果をバッファリングする必要があります。

1.4.3. 宣言的トランザクションのロールバック

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

Spring Framework のトランザクションインフラストラクチャに、トランザクションの作業をロールバックすることを示す推奨方法は、トランザクションのコンテキストで現在実行されているコードから Exception をスローすることです。Spring Framework のトランザクションインフラストラクチャコードは、未処理の Exception をキャッチし、コールスタックをバブルアップさせ、トランザクションをロールバック用にマークするかどうかを決定します。

デフォルトの構成では、Spring Framework のトランザクションインフラストラクチャコードは、実行時の未チェックの例外の場合にのみ、ロールバックのトランザクションをマークします。つまり、スローされた例外が RuntimeException のインスタンスまたはサブクラスである場合。(デフォルトでは、Error インスタンスもロールバックになります)。トランザクションメソッドからスローされたチェック済み例外は、デフォルトの構成ではロールバックされません。

チェック済み例外を含め、どの Exception タイプがロールバックのトランザクションをマークするかを正確に構成できます。次の XML スニペットは、チェックされたアプリケーション固有の Exception タイプのロールバックを構成する方法を示しています。

<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>

例外がスローされたときにトランザクションをロールバックしたくない場合は、「ロールバックルールなし」を指定することもできます。次の例は、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 Framework のトランザクションインフラストラクチャに緊密に結合します。次の例は、必要なロールバックをプログラムで示す方法を示しています。

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

可能であれば、宣言的なアプローチを使用してロールバックすることを強くお勧めします。絶対に必要な場合はプログラムによるロールバックを使用できますが、その使用方法は POJO ベースのクリーンなアーキテクチャの実現に直面しています。

1.4.4. 異なる Bean の異なるトランザクションセマンティクスの構成

多数のサービスレイヤーオブジェクトがあり、それぞれにまったく異なるトランザクション構成を適用するシナリオを考えます。これは、異なる pointcut および advice-ref 属性値を持つ個別の <aop:advisor/> エレメントを定義することにより可能です。

比較のポイントとして、最初にすべてのサービス層クラスがルート x.y.service パッケージで定義されていると仮定します。そのパッケージ(またはサブパッケージ)で定義されたクラスのインスタンスであり、名前が Service で終わるすべての Bean にデフォルトのトランザクション構成を持たせるには、次のように記述できます。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager omitted... -->

</beans>

次の例は、まったく異なるトランザクション設定で 2 つの異なる Bean を構成する方法を示しています。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager omitted... -->

</beans>

1.4.5. <tx:advice/> 設定

このセクションでは、<tx:advice/> タグを使用して指定できるさまざまなトランザクション設定を要約します。デフォルトの <tx:advice/> 設定は次のとおりです。

  • 伝播設定は REQUIRED. です

  • 分離レベルは DEFAULT. です

  • トランザクションは読み書き可能です。

  • トランザクションタイムアウトは、デフォルトで基礎となるトランザクションシステムのデフォルトタイムアウトに設定されるか、タイムアウトがサポートされていない場合はなしに設定されます。

  • RuntimeException はロールバックをトリガーし、チェックされた Exception はトリガーしません。

これらのデフォルト設定を変更できます。次の表は、<tx:advice/> および <tx:attributes/> タグ内にネストされている <tx:method/> タグのさまざまな属性をまとめたものです。

表 1: <tx:method/> 設定
属性 必須 ? デフォルト 説明

name

はい

トランザクション属性が関連付けられるメソッド名。ワイルドカード(*)文字を使用して、同じトランザクション属性設定をいくつかのメソッド(たとえば、get*handle*on*Event など)に関連付けることができます。

propagation

いいえ

REQUIRED

トランザクション伝播動作。

isolation

いいえ

DEFAULT

トランザクション分離レベル。REQUIRED または REQUIRES_NEW の伝播設定にのみ適用可能。

timeout

いいえ

-1

トランザクションタイムアウト(秒)。伝播 REQUIRED または REQUIRES_NEW にのみ適用可能。

read-only

いいえ

false

読み取り / 書き込みトランザクションと読み取り専用トランザクション。REQUIRED または REQUIRES_NEW にのみ適用されます。

rollback-for

いいえ

ロールバックをトリガーする Exception インスタンスのコンマ区切りリスト。例: com.foo.MyBusinessException,ServletException

no-rollback-for

いいえ

ロールバックをトリガーしない Exception インスタンスのコンマ区切りリスト。例: com.foo.MyBusinessException,ServletException

1.4.6. @Transactional を使用する

トランザクション構成への XML ベースの宣言的アプローチに加えて、アノテーションベースのアプローチを使用できます。Java ソースコードでトランザクションセマンティクスを直接宣言すると、影響を受けるコードにより近い宣言になります。過度な結合の危険性はあまりありません。トランザクションで使用することを目的としたコードは、ほとんどの場合、そのようにデプロイされるためです。

標準の javax.transaction.Transactional アノテーションは、Spring 自身のアノテーションのドロップイン置換としてもサポートされています。詳細については、JTA 1.2 のドキュメントを参照してください。

@Transactional アノテーションを使用することで得られる使いやすさは、以下のテキストで説明されている例で最もよく説明されています。次のクラス定義を検討してください。

Java
// 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) {
        // ...
    }
}
Kotlin
// the service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {

    override fun getFoo(fooName: String): Foo {
        // ...
    }

    override fun getFoo(fooName: String, barName: String): Foo {
        // ...
    }

    override fun insertFoo(foo: Foo) {
        // ...
    }

    override fun updateFoo(foo: Foo) {
        // ...
    }
}

上記のようにクラスレベルで使用されるアノテーションは、宣言クラス(およびそのサブクラス)のすべてのメソッドのデフォルトを示します。または、各メソッドに個別にアノテーションを付けることができます。クラスレベルのアノテーションは、クラス階層の上位クラスに適用されないことに注意してください。このようなシナリオでは、サブクラスレベルのアノテーションに参加するには、メソッドをローカルで再宣言する必要があります。

上記のような POJO クラスが Spring コンテキストで Bean として定義されている場合、Bean インスタンスを @Configuration クラスの @EnableTransactionManagement アノテーションを介してトランザクション対応にすることができます。詳細については、javadoc を参照してください。

XML 構成では、<tx:annotation-driven/> タグは同様の利便性を提供します。

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager is still required --> (1)

    <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>
1Bean インスタンスをトランザクション化する行。
接続する TransactionManager の Bean 名に transactionManager という名前がある場合、<tx:annotation-driven/> タグの transaction-manager 属性を省略できます。依存関係を注入する TransactionManager Bean に他の名前がある場合、前の例のように transaction-manager 属性を使用する必要があります。

リアクティブトランザクションメソッドは、次のリストに示すように、命令型プログラミングの配置とは対照的に、リアクティブリターンタイプを使用します。

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

    Publisher<Foo> getFoo(String fooName) {
        // ...
    }

    Mono<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {

    override fun getFoo(fooName: String): Flow<Foo> {
        // ...
    }

    override fun getFoo(fooName: String, barName: String): Mono<Foo> {
        // ...
    }

    override fun insertFoo(foo: Foo): Mono<Void> {
        // ...
    }

    override fun updateFoo(foo: Foo): Mono<Void> {
        // ...
    }
}

Reactive Streams キャンセル信号に関して、返される Publisher には特別な考慮事項があることに注意してください。詳細については、「TransactionOperator の使用」のシグナルをキャンセルセクションを参照してください。

メソッドの可視性と @Transactional

プロキシを使用する場合は、@Transactional アノテーションを public 可視性を持つメソッドにのみ適用する必要があります。@Transactional アノテーションを使用して、protected メソッド、private メソッド、またはパッケージ private メソッドにアノテーションを付けた場合、エラーは発生しませんが、アノテーション付きメソッドは構成済みのトランザクション設定を示しません。非 public メソッドにアノテーションを付ける必要がある場合は、AspectJ(後述)の使用を検討してください。

@Transactional アノテーションは、インターフェース定義、インターフェースのメソッド、クラス定義、またはクラスの public メソッドに適用できます。ただし、@Transactional アノテーションが存在するだけでは、トランザクション動作をアクティブにするのに十分ではありません。@Transactional アノテーションは、@Transactional -aware であるランタイムインフラストラクチャによって消費され、メタデータを使用して適切な Bean にトランザクション動作を設定できるメタデータにすぎません。前の例では、<tx:annotation-driven/> 要素はトランザクション動作をオンにします。

Spring チームは、インターフェースにアノテーションを付けるのではなく、@Transactional アノテーションを使用して、具象クラス(および具象クラスのメソッド)のみにアノテーションを付けることをお勧めします。インターフェース(またはインターフェースメソッド)に @Transactional アノテーションを配置することは確かにできますが、これは、インターフェースベースのプロキシを使用する場合に期待どおりにのみ機能します。Java アノテーションがインターフェースから継承されないという事実は、クラスベースのプロキシ(proxy-target-class="true")またはウィービングベースのアスペクト(mode="aspectj")を使用する場合、トランザクション設定がプロキシおよびウィービングインフラストラクチャによって認識されず、オブジェクトがトランザクションプロキシにラップされていません。
プロキシモード(デフォルト)では、プロキシを介して受信する外部メソッド呼び出しのみがインターセプトされます。これは、呼び出されたメソッドが @Transactional でマークされていても、自己呼び出し(実際には、ターゲットオブジェクト内のメソッドがターゲットオブジェクトの別のメソッドを呼び出す)が実行時に実際のトランザクションにつながらないことを意味します。また、期待される動作を提供するには、プロキシを完全に初期化する必要があるため、初期化コード(つまり @PostConstruct)でこの機能に依存しないでください。

自己呼び出しもトランザクションでラップされることが予想される場合は、AspectJ モード(次の表の mode 属性を参照)の使用を検討してください。この場合、そもそもプロキシはありません。代わりに、@Transactional をあらゆる種類のメソッドのランタイム動作に変換するために、ターゲットクラスが織り込まれています(つまり、そのバイトコードが変更されています)。

テーブル 2: アノテーション駆動のトランザクション設定
XML 属性 アノテーション属性 デフォルト 説明

transaction-manager

なし (TransactionManagementConfigurer (Javadoc) javadoc を参照)

transactionManager

使用するトランザクションマネージャーの名前。前の例のように、トランザクションマネージャーの名前が transactionManager でない場合にのみ必要です。

mode

mode

proxy

デフォルトモード(proxy)は、Spring の AOP フレームワークを使用して、プロキシ化されるアノテーション付き Bean を処理します(前述のプロキシセマンティクスに従い、プロキシ経由で受信するメソッド呼び出しにのみ適用されます)。代替モード(aspectj)は、代わりに、影響を受けるクラスを Spring の AspectJ トランザクションアスペクトで織り込み、ターゲットクラスのバイトコードを変更して、あらゆる種類のメソッド呼び出しに適用します。AspectJ ウィービングでは、ロード時ウィービング(またはコンパイル時ウィービング)を有効にするとともに、クラスパスに spring-aspects.jar が必要です。(ロード時ウィービングを設定する方法の詳細については、Spring の構成を参照してください。)

proxy-target-class

proxyTargetClass

false

proxy モードのみに適用されます。@Transactional アノテーションが付けられたクラスに対して作成されるトランザクションプロキシのタイプを制御します。proxy-target-class 属性が true に設定されている場合、クラスベースのプロキシが作成されます。proxy-target-class が false の場合、または属性が省略された場合、標準の JDK インターフェースベースのプロキシが作成されます。(さまざまなプロキシタイプの詳細な調査については、プロキシメカニズムを参照してください。)

order

order

Ordered.LOWEST_PRECEDENCE

@Transactional アノテーションが付けられた Bean に適用されるトランザクションアドバイスの順序を定義します。(AOP アドバイスの順序に関するルールの詳細については、アドバイスのオーダーを参照してください)順序が指定されていない場合、AOP サブシステムがアドバイスの順序を決定することを意味します。

@Transactional アノテーションを処理するためのデフォルトのアドバイスモードは proxy です。これにより、プロキシのみを介した呼び出しの傍受が可能になります。同じクラス内のローカル呼び出しは、そのように傍受することはできません。より高度なインターセプトモードについては、コンパイル時またはロード時のウィービングと組み合わせて aspectj モードに切り替えることを検討してください。
proxy-target-class 属性は、@Transactional アノテーションが付けられたクラスに対して作成されるトランザクションプロキシのタイプを制御します。proxy-target-class が true に設定されている場合、クラスベースのプロキシが作成されます。proxy-target-class が false の場合、または属性が省略された場合、標準の JDK インターフェースベースのプロキシが作成されます。(さまざまなプロキシタイプの説明については、core.html を参照してください。)
@EnableTransactionManagement および <tx:annotation-driven/> は、定義されている同じアプリケーションコンテキスト内の Bean でのみ @Transactional を検索します。これは、DispatcherServlet の WebApplicationContext にアノテーション駆動の構成を配置した場合、コントローラーではなく @Transactional Bean のみをチェックし、サービスではないことを意味します。詳細については、MVC を参照してください。

メソッドのトランザクション設定を評価する場合、最も派生した場所が優先されます。次の例の場合、DefaultFooService クラスは読み取り専用トランザクションの設定でクラスレベルでアノテーションが付けられますが、同じクラスの updateFoo(Foo) メソッドの @Transactional アノテーションはクラスレベルで定義されたトランザクション設定より優先されます。

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

    public Foo getFoo(String fooName) {
        // ...
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
@Transactional(readOnly = true)
class DefaultFooService : FooService {

    override fun getFoo(fooName: String): Foo {
        // ...
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    override fun updateFoo(foo: Foo) {
        // ...
    }
}
@Transactional の設定

@Transactional アノテーションは、インターフェース、クラス、またはメソッドにトランザクションセマンティクスが必要であることを指定するメタデータです(たとえば、「このメソッドが呼び出されたときに新しい読み取り専用トランザクションを開始し、既存のトランザクションを中断する」)。デフォルトの @Transactional 設定は次のとおりです。

  • 伝播設定は PROPAGATION_REQUIRED. です

  • 分離レベルは ISOLATION_DEFAULT. です

  • トランザクションは読み書き可能です。

  • トランザクションタイムアウトは、基本となるトランザクションシステムのデフォルトタイムアウトにデフォルト設定されます。タイムアウトがサポートされていない場合は、なしに設定されます。

  • RuntimeException はロールバックをトリガーし、チェックされた Exception はトリガーしません。

これらのデフォルト設定を変更できます。次の表は、@Transactional アノテーションのさまざまなプロパティをまとめたものです。

表 3: @Transactional の設定
プロパティ タイプ 説明

value

String

使用するトランザクションマネージャーを指定するオプションの修飾子。

propagation

enumPropagation

オプションの伝播設定。

isolation

enumIsolation

オプションの分離レベル。REQUIRED または REQUIRES_NEW の伝播値にのみ適用されます。

timeout

int (粒度の秒)

オプションのトランザクションタイムアウト。REQUIRED または REQUIRES_NEW の伝播値にのみ適用されます。

readOnly

boolean

読み取り / 書き込みトランザクションと読み取り専用トランザクション。REQUIRED または REQUIRES_NEW の値にのみ適用可能。

rollbackFor

Class オブジェクトの配列。Throwable. から派生する必要があります

ロールバックを引き起こす必要がある例外クラスのオプションの配列。

rollbackForClassName

クラス名の配列。クラスは Throwable. から派生する必要があります

ロールバックを引き起こす必要がある例外クラスの名前のオプションの配列。

noRollbackFor

Class オブジェクトの配列。Throwable. から派生する必要があります

ロールバックを引き起こしてはならない例外クラスのオプションの配列。

noRollbackForClassName

String クラス名の配列。Throwable. から派生する必要があります

ロールバックを引き起こしてはならない例外クラスの名前のオプションの配列。

label

トランザクションに表現力豊かな説明を追加する String ラベルの配列。

ラベルは、実装固有の動作を実際のトランザクションに関連付けるためにトランザクションマネージャーによって評価される場合があります。

現在、トランザクションの名前を明示的に制御することはできません。「名前」は、トランザクションモニター(該当する場合)(WebLogic のトランザクションモニターなど)およびログ出力に表示されるトランザクション名を意味します。宣言型トランザクションの場合、トランザクション名は常に完全修飾クラス名 + . + トランザクションが推奨されるクラスのメソッド名です。例: BusinessService クラスの handlePayment(..) メソッドがトランザクションを開始した場合、トランザクションの名前は com.example.BusinessService.handlePayment になります。

@Transactional を使用した複数のトランザクションマネージャー

ほとんどの Spring アプリケーションには単一のトランザクションマネージャーのみが必要ですが、単一のアプリケーションに複数の独立したトランザクションマネージャーが必要な場合があります。@Transactional アノテーションの value または transactionManager 属性を使用して、オプションで、使用する TransactionManager の ID を指定できます。これは、Bean 名またはトランザクションマネージャー Bean の修飾子値のいずれかです。例:修飾子表記を使用して、次の Java コードをアプリケーションコンテキストで次のトランザクションマネージャー Bean 宣言と組み合わせることができます。

Java
public class TransactionalService {

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

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

    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}
Kotlin
class TransactionalService {

    @Transactional("order")
    fun setSomething(name: String) {
        // ...
    }

    @Transactional("account")
    fun doSomething() {
        // ...
    }

    @Transactional("reactive-account")
    fun doSomethingReactive(): Mono<Void> {
        // ...
    }
}

以下のリストは、Bean 宣言を示しています。

<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>

    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    </bean>

この場合、TransactionalService の個々のメソッドは、orderaccount 修飾子と reactive-account 修飾子によって区別される個別のトランザクションマネージャーで実行されます。特に限定された TransactionManager Bean が見つからない場合、デフォルトの <tx:annotation-driven> ターゲット Bean 名 transactionManager が引き続き使用されます。

カスタム構成アノテーション

多くの異なるメソッドで @Transactional で同じ属性を繰り返し使用していることがわかった場合、Spring のメタアノテーションサポートを使用すると、特定のユースケースに合わせてカスタム合成アノテーションを定義できます。例:次のアノテーション定義を検討します。

Java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
Kotlin
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx

前述のアノテーションにより、前のセクションの例を次のように記述できます。

Java
public class TransactionalService {

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

    @AccountTx
    public void doSomething() {
        // ...
    }
}
Kotlin
class TransactionalService {

    @OrderTx
    fun setSomething(name: String) {
        // ...
    }

    @AccountTx
    fun doSomething() {
        // ...
    }
}

前の例では、構文を使用してトランザクションマネージャー修飾子とトランザクションラベルを定義しましたが、伝播動作、ロールバックルール、タイムアウト、その他の機能を含めることもできます。

1.4.7. トランザクションの伝播

このセクションでは、Spring のトランザクション伝播のいくつかのセマンティクスについて説明します。このセクションは、適切なトランザクション伝播の概要ではないことに注意してください。むしろ、Spring でのトランザクション伝搬に関するセマンティクスの一部を詳しく説明しています。

Spring 管理のトランザクションでは、物理トランザクションと論理トランザクションの違い、および伝播設定がこの違いにどのように適用されるかに注意してください。

PROPAGATION_REQUIRED を理解する
tx prop required

PROPAGATION_REQUIRED は、トランザクションがまだ存在しない場合は現在のスコープに対してローカルに、またはより大きなスコープに対して定義された既存の「外部」トランザクションに参加して、物理トランザクションを実施します。これは、同じスレッド内の一般的なコールスタック配置(たとえば、基礎となるすべてのリソースがサービスレベルトランザクションに参加する必要がある複数のリポジトリメソッドに委譲するサービスファサード)のデフォルトです。

デフォルトでは、参加トランザクションは外部スコープの特性に参加し、ローカルの分離レベル、タイムアウト値、または読み取り専用フラグ(存在する場合)を静かに無視します。分離レベルが異なる既存のトランザクションに参加するときに分離レベルの宣言を拒否する場合は、トランザクションマネージャーで validateExistingTransactions フラグを true に切り替えることを検討してください。この非寛容なモードは、読み取り専用の不一致(つまり、読み取り専用の外部スコープに参加しようとする内部読み取り / 書き込みトランザクション)も拒否します。

伝播設定が PROPAGATION_REQUIRED の場合、設定が適用されるメソッドごとに論理トランザクションスコープが作成されます。このような各論理トランザクションスコープは、ロールバックのみのステータスを個別に決定できます。外側のトランザクションスコープは、内側のトランザクションスコープから論理的に独立しています。標準の PROPAGATION_REQUIRED 動作の場合、これらのスコープはすべて同じ物理トランザクションにマップされます。そのため、内部トランザクションスコープに設定されたロールバック専用マーカーは、外部トランザクションが実際にコミットする機会に影響します。

ただし、内部トランザクションスコープがロールバック専用マーカーを設定する場合、外部トランザクションはロールバック自体を決定していないため、ロールバック(内部トランザクションスコープによってサイレントにトリガーされる)は予期されていません。その時点で、対応する UnexpectedRollbackException がスローされます。これは予想される動作であるため、トランザクションの呼び出し元は、実際にはコミットが実行されていないときにコミットが実行されたと誤解することはありません。そのため、(外部の呼び出し元が認識していない)内部のトランザクションがトランザクションをロールバック専用としてサイレントにマークした場合、外部の呼び出し元はコミットを呼び出します。外側の呼び出し元は、UnexpectedRollbackException を受信して、代わりにロールバックが実行されたことを明確に示す必要があります。

PROPAGATION_REQUIRES_NEW を理解する
tx prop requires new

PROPAGATION_REQUIRED とは対照的に、PROPAGATION_REQUIRES_NEW は、影響を受けるトランザクションスコープごとに独立した物理トランザクションを常に使用し、外部スコープの既存のトランザクションには関与しません。このような配置では、基になるリソーストランザクションは異なり、独立したコミットまたはロールバックが可能です。外側のトランザクションは、内側のトランザクションのロールバックステータスの影響を受けず、内側のトランザクションのロックは完了直後に解放されます。このような独立した内部トランザクションは、独自の分離レベル、タイムアウト、読み取り専用設定も宣言でき、外部トランザクションの特性を継承しません。

PROPAGATION_NESTED を理解する

PROPAGATION_NESTED は、ロールバック可能な複数のセーブポイントを持つ単一の物理トランザクションを使用します。このような部分的なロールバックにより、内部トランザクションスコープはそのスコープのロールバックをトリガーできます。一部の操作がロールバックされても、外部トランザクションは物理トランザクションを続行できます。通常、この設定は JDBC セーブポイントにマップされるため、JDBC リソーストランザクションでのみ機能します。Spring の DataSourceTransactionManager (Javadoc) を参照してください。

1.4.8. トランザクション操作のアドバイス

トランザクション操作といくつかの基本的なプロファイリングアドバイスの両方を実行するとします。<tx:annotation-driven/> のコンテキストでこれをどのように実行しますか?

updateFoo(Foo) メソッドを呼び出すと、次のアクションが表示されます。

  • 設定されたプロファイリングアスペクトが開始されます。

  • トランザクションアドバイスが実行されます。

  • アドバイスされたオブジェクトのメソッドが実行されます。

  • トランザクションがコミットします。

  • プロファイリングアスペクトは、トランザクションメソッド呼び出し全体の正確な期間を報告します。

この章は、AOP の詳細な説明には関係ありません(トランザクションに適用される場合を除く)。AOP 設定と AOP 全般の詳細については、AOP を参照してください。

次のコードは、前述の単純なプロファイリングのアスペクトを示しています。

Java
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;
    }
}
Kotlin
class SimpleProfiler : Ordered {

    private var order: Int = 0

    // allows us to control the ordering of advice
    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    // this method is the around advice
    fun profile(call: ProceedingJoinPoint): Any {
        var returnValue: Any
        val clock = StopWatch(javaClass.name)
        try {
            clock.start(call.toShortString())
            returnValue = call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        }
        return returnValue
    }
}

アドバイスの順序は、Ordered インターフェースを介して制御されます。アドバイスのオーダーの詳細については、アドバイスのオーダーを参照してください。

次の構成では、プロファイリングおよびトランザクションのアスペクトが目的の順序で適用される fooService Bean を作成します。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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">
        <!-- run 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 runs 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>

同様の方法で、任意の数の追加のアスペクトを構成できます。

次の例では、前の 2 つの例と同じセットアップを作成しますが、純粋な 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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">
        <!-- run 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.*(..))"/>
        <!-- runs 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 TransactionManager here -->

</beans>

上記の構成の結果、fooService Bean が作成され、プロファイリングとトランザクションのアスペクトがこの順序で適用されます。プロファイリングアドバイスを、トランザクションアドバイスの後、トランザクションアドバイスの前に実行する場合は、プロファイリングアスペクト Bean の order プロパティの値を入れ替えて、トランザクションアドバイスのオーダー値よりも高くすることができます。

同様の方法で追加のアスペクトを構成できます。

1.4.9. AspectJ で @Transactional を使用する

AspectJ アスペクトを使用して、Spring コンテナーの外部で Spring Framework の @Transactional サポートを使用することもできます。そのためには、最初にクラス(およびオプションでクラスのメソッド)に @Transactional アノテーションを付け、次にアプリケーションを spring-aspects.jar ファイルで定義された org.springframework.transaction.aspectj.AnnotationTransactionAspect にリンク(ウィービング)します。また、トランザクションマネージャーでアスペクトを構成する必要があります。Spring Framework の IoC コンテナーを使用して、アスペクトの依存性注入を処理できます。トランザクション管理のアスペクトを構成する最も簡単な方法は、<tx:annotation-driven/> 要素を使用して、@Transactional の使用に従って aspectj に mode 属性を指定することです。ここでは、Spring コンテナーの外部で実行されるアプリケーションに焦点を当てているため、プログラムで実行する方法を示します。

続行する前に、@Transactional を使用するAOP をそれぞれ読むことをお勧めします。

次の例は、トランザクションマネージャーを作成し、それを使用するように AnnotationTransactionAspect を構成する方法を示しています。

Java
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
Kotlin
// construct an appropriate transaction manager
val txManager = DataSourceTransactionManager(getDataSource())

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().transactionManager = txManager
このアスペクトを使用するときは、クラスが実装するインターフェース(存在する場合)ではなく、実装クラス(またはそのクラス内のメソッド、あるいはその両方)にアノテーションを付ける必要があります。AspectJ は、インターフェースのアノテーションが継承されないという Java のルールに従います。

クラスの @Transactional アノテーションは、クラス内の public メソッドの実行に対するデフォルトのトランザクションセマンティクスを指定します。

クラス内のメソッドの @Transactional アノテーションは、クラスアノテーション(存在する場合)によって指定されたデフォルトのトランザクションセマンティクスをオーバーライドします。可視性に関係なく、任意のメソッドにアノテーションを付けることができます。

AnnotationTransactionAspect を使用してアプリケーションを編むには、AspectJ(AspectJ 開発ガイド (英語) を参照)を使用してアプリケーションをビルドするか、ロードタイムウィービングを使用する必要があります。AspectJ でのロード時間ウィービングの説明については、Spring Framework での AspectJ を使用したロードタイムウィービングを参照してください。

1.5. プログラムによるトランザクション管理

Spring Framework は、以下を使用することにより、プログラムによるトランザクション管理の 2 つの手段を提供します。

  • TransactionTemplate または TransactionalOperator

  • TransactionManager 実装。

Spring チームは通常、命令フローでのプログラムによるトランザクション管理には TransactionTemplate を、リアクティブコードには TransactionalOperator を推奨しています。2 番目のアプローチは、JTA UserTransaction API を使用する場合と似ていますが、例外処理はそれほど面倒ではありません。

1.5.1. TransactionTemplate を使用する

TransactionTemplate は、JdbcTemplate などの他の Spring テンプレートと同じアプローチを採用しています。これは、コールバックアプローチを使用して(アプリケーションコードをボイラープレートの取得やトランザクションリソースの解放から解放するため)、結果がインテンションドリブンのコードになります。つまり、コードは目的にのみ焦点を合わせます。

以下の例が示すように、TransactionTemplate を使用すると、Spring のトランザクションインフラストラクチャと API に完全に結合されます。プログラマティックトランザクション管理が開発ニーズに適しているかどうかは、あなた自身で決定する必要があります。

トランザクションコンテキストで実行する必要があり、TransactionTemplate を明示的に使用するアプリケーションコードは、次の例のようになります。アプリケーション開発者は、トランザクションのコンテキストで実行する必要があるコードを含む TransactionCallback 実装(通常は匿名の内部クラスとして表現)を作成できます。次に、カスタム TransactionCallback のインスタンスを TransactionTemplate で公開されている execute(..) メソッドに渡すことができます。次の例は、その方法を示しています。

Java
public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method runs in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}
Kotlin
// use constructor-injection to supply the PlatformTransactionManager
class SimpleService(transactionManager: PlatformTransactionManager) : Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private val transactionTemplate = TransactionTemplate(transactionManager)

    fun someServiceMethod() = transactionTemplate.execute<Any?> {
        updateOperation1()
        resultOfUpdateOperation2()
    }
}

戻り値がない場合、次のように匿名クラスで便利な TransactionCallbackWithoutResult クラスを使用できます。

Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});
Kotlin
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
    override fun doInTransactionWithoutResult(status: TransactionStatus) {
        updateOperation1()
        updateOperation2()
    }
})

コールバック内のコードは、次のように、提供された TransactionStatus オブジェクトで setRollbackOnly() メソッドを呼び出すことにより、トランザクションをロールバックできます。

Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessException ex) {
            status.setRollbackOnly();
        }
    }
});
Kotlin
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {

    override fun doInTransactionWithoutResult(status: TransactionStatus) {
        try {
            updateOperation1()
            updateOperation2()
        } catch (ex: SomeBusinessException) {
            status.setRollbackOnly()
        }
    }
})
トランザクション設定の指定

TransactionTemplate のトランザクション設定(伝播モード、分離レベル、タイムアウトなど)をプログラムまたは構成で指定できます。デフォルトでは、TransactionTemplate インスタンスにはデフォルトのトランザクション設定があります。次の例は、特定の TransactionTemplate: のトランザクション設定のプログラムによるカスタマイズを示しています

Java
public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}
Kotlin
class SimpleService(transactionManager: PlatformTransactionManager) : Service {

    private val transactionTemplate = TransactionTemplate(transactionManager).apply {
        // the transaction settings can be set here explicitly if so desired
        isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
        timeout = 30 // 30 seconds
        // and so forth...
    }
}

次の例では、Spring XML 構成を使用して、いくつかのカスタムトランザクション設定で TransactionTemplate を定義しています。

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
</bean>

その後、sharedTransactionTemplate を必要な数のサービスに挿入できます。

最後に、TransactionTemplate クラスのインスタンスはスレッドセーフであり、そのインスタンスは会話状態を維持しません。ただし、TransactionTemplate インスタンスは構成状態を維持します。そのため、多くのクラスが TransactionTemplate の単一のインスタンスを共有する場合がありますが、クラスが異なる設定(たとえば、異なる分離レベル)で TransactionTemplate を使用する必要がある場合、2 つの異なる TransactionTemplate インスタンスを作成する必要があります。

1.5.2. TransactionOperator を使用する

TransactionOperator は、他のリアクティブオペレーターと同様のオペレーター設計に従います。これは、コールバックアプローチを使用して(ボイラープレートの取得を実行してトランザクションリソースを解放する必要がないようにアプリケーションコードを解放し)、インテンション駆動のコードを生成します。つまり、コードは、やりたいことだけに集中します。

以下の例が示すように、TransactionOperator を使用すると、Spring のトランザクションインフラストラクチャと API に完全に結合されます。プログラマティックトランザクション管理が開発ニーズに適しているかどうかは、あなた自身で決定する必要があります。

トランザクションコンテキストで実行する必要があり、TransactionOperator を明示的に使用するアプリケーションコードは、次の例のようになります。

Java
public class SimpleService implements Service {

    // single TransactionOperator shared amongst all methods in this instance
    private final TransactionalOperator transactionalOperator;

    // use constructor-injection to supply the ReactiveTransactionManager
    public SimpleService(ReactiveTransactionManager transactionManager) {
        this.transactionOperator = TransactionalOperator.create(transactionManager);
    }

    public Mono<Object> someServiceMethod() {

        // the code in this method runs in a transactional context

        Mono<Object> update = updateOperation1();

        return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
    }
}
Kotlin
// use constructor-injection to supply the ReactiveTransactionManager
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {

    // single TransactionalOperator shared amongst all methods in this instance
    private val transactionalOperator = TransactionalOperator.create(transactionManager)

    suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> {
        updateOperation1()
        resultOfUpdateOperation2()
    }
}

TransactionalOperator は 2 つの方法で使用できます。

  • プロジェクト Reactor タイプを使用するオペレータースタイル (mono.as(transactionalOperator::transactional))

  • 他のすべての場合のコールバックスタイル (transactionalOperator.execute(TransactionCallback<T>))

コールバック内のコードは、次のように、提供された ReactiveTransaction オブジェクトで setRollbackOnly() メソッドを呼び出すことにより、トランザクションをロールバックできます。

Java
transactionalOperator.execute(new TransactionCallback<>() {

    public Mono<Object> doInTransaction(ReactiveTransaction status) {
        return updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
        }
    }
});
Kotlin
transactionalOperator.execute(object : TransactionCallback() {

    override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
        updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
    }
})
シグナルをキャンセル

Reactive Streams では、Subscriber は Subscription をキャンセルし、Publisher を停止できます。プロジェクト Reactor および next()take(long)timeout(Duration) などの他のライブラリのオペレーターは、キャンセルを発行できます。それがエラーによるものか、それ以上消費するだけの関心がないためか、キャンセルの理由を知る方法はありません。バージョン 5.3 以降のキャンセル信号はロールバックにつながります。その結果、トランザクション Publisher のダウンストリームで使用される演算子を考慮することが重要です。特に Flux または他の多値 Publisher の場合、トランザクションを完了するには、出力全体を消費する必要があります。

トランザクション設定の指定

TransactionalOperator のトランザクション設定(伝播モード、分離レベル、タイムアウトなど)を指定できます。デフォルトでは、TransactionalOperator インスタンスにはデフォルトのトランザクション設定があります。次の例は、特定の TransactionalOperator: のトランザクション設定のカスタマイズを示しています

Java
public class SimpleService implements Service {

    private final TransactionalOperator transactionalOperator;

    public SimpleService(ReactiveTransactionManager transactionManager) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

        // the transaction settings can be set here explicitly if so desired
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        definition.setTimeout(30); // 30 seconds
        // and so forth...

        this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
    }
}
Kotlin
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {

    private val definition = DefaultTransactionDefinition().apply {
        // the transaction settings can be set here explicitly if so desired
        isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
        timeout = 30 // 30 seconds
        // and so forth...
    }
    private val transactionalOperator = TransactionalOperator(transactionManager, definition)
}

1.5.3. TransactionManager を使用する

以下のセクションでは、命令型およびリアクティブ型トランザクションマネージャーのプログラムによる使用箇所について説明します。

PlatformTransactionManager を使用する

命令型トランザクションの場合、org.springframework.transaction.PlatformTransactionManager を直接使用してトランザクションを管理できます。そのためには、使用する PlatformTransactionManager の実装を Bean 参照を通じて Bean に渡します。次に、TransactionDefinition および TransactionStatus オブジェクトを使用して、トランザクションを開始し、ロールバックしてコミットできます。次の例は、その方法を示しています。

Java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // put your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);
Kotlin
val def = DefaultTransactionDefinition()
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED

val status = txManager.getTransaction(def)
try {
    // put your business logic here
} catch (ex: MyException) {
    txManager.rollback(status)
    throw ex
}

txManager.commit(status)
ReactiveTransactionManager を使用する

リアクティブトランザクションを使用する場合、org.springframework.transaction.ReactiveTransactionManager を直接使用してトランザクションを管理できます。そのためには、使用する ReactiveTransactionManager の実装を Bean 参照を通じて Bean に渡します。次に、TransactionDefinition および ReactiveTransaction オブジェクトを使用して、トランザクションを開始し、ロールバックしてコミットできます。次の例は、その方法を示しています。

Java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

    Mono<Object> tx = ...; // put your business logic here

    return tx.then(txManager.commit(status))
            .onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});
Kotlin
val def = DefaultTransactionDefinition()
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED

val reactiveTx = txManager.getReactiveTransaction(def)
reactiveTx.flatMap { status ->

    val tx = ... // put your business logic here

    tx.then(txManager.commit(status))
            .onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
}

1.6. プログラムによるトランザクション管理と宣言的なトランザクション管理の選択

通常、プログラムによるトランザクション管理は、トランザクション操作が少数の場合にのみ有効です。例:特定の更新操作にのみトランザクションを必要とする Web アプリケーションがある場合、Spring または他の技術を使用してトランザクションプロキシを設定する必要はありません。この場合、TransactionTemplate を使用することをお勧めします。トランザクション名を明示的に設定できることは、トランザクション管理へのプログラム的なアプローチを使用することによってのみ実行できることでもあります。

一方、アプリケーションに多数のトランザクション操作がある場合、通常、宣言的なトランザクション管理は価値があります。トランザクション管理をビジネスロジックから排除し、構成することは難しくありません。EJB CMT ではなく Spring Framework を使用すると、宣言型トランザクション管理の構成コストが大幅に削減されます。

1.7. トランザクションバウンドイベント

Spring 4.2, 以降、イベントのリスナーはトランザクションのフェーズにバインドできます。典型的な例は、トランザクションが正常に完了したときにイベントを処理することです。これにより、現在のトランザクションの結果が実際にリスナーにとって重要な場合に、イベントをより柔軟に使用できます。

@EventListener アノテーションを使用して、通常のイベントリスナーを登録できます。トランザクションにバインドする必要がある場合は、@TransactionalEventListener を使用します。これを行うと、リスナーはデフォルトでトランザクションのコミットフェーズにバインドされます。

次の例は、この概念を示しています。コンポーネントがオーダー作成イベントを発行し、発行されたトランザクションが正常にコミットされた場合にのみそのイベントを処理するリスナーを定義すると仮定します。次の例では、このようなイベントリスナーを設定します。

Java
@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        // ...
    }
}
Kotlin
@Component
class MyComponent {

    @TransactionalEventListener
    fun handleOrderCreatedEvent(creationEvent: CreationEvent<Order>) {
        // ...
    }
}

@TransactionalEventListener アノテーションは、リスナーがバインドされるトランザクションのフェーズをカスタマイズできる phase 属性を公開します。有効なフェーズは、BEFORE_COMMITAFTER_COMMIT (デフォルト)、AFTER_ROLLBACK、トランザクションの補完(コミットまたはロールバック)を集約する AFTER_COMPLETION です。

トランザクションが実行されていない場合、必要なセマンティクスを尊重できないため、リスナーはまったく呼び出されません。ただし、アノテーションの fallbackExecution 属性を true に設定することにより、この動作をオーバーライドできます。

@TransactionalEventListener は、PlatformTransactionManager によって管理されるスレッドバウンドトランザクションでのみ機能します。ReactiveTransactionManager によって管理されるリアクティブトランザクションは、スレッドローカル属性の代わりに Reactor コンテキストを使用するため、イベントリスナーの観点からは、参加できる互換性のあるアクティブトランザクションはありません。

1.8. アプリケーションサーバー固有の統合

Spring のトランザクション抽象化は、一般的にアプリケーションサーバーに依存しません。さらに、Spring の JtaTransactionManager クラス(オプションで JTA UserTransaction および TransactionManager オブジェクトの JNDI ルックアップを実行できる)は、アプリケーションサーバーによって異なる後者のオブジェクトの場所を自動検出します。JTA TransactionManager にアクセスすると、トランザクションのセマンティクスが強化され、特にトランザクションの一時停止がサポートされます。詳細については、JtaTransactionManager (Javadoc) javadoc を参照してください。

Spring の JtaTransactionManager は、Java EE アプリケーションサーバーで実行する標準的な選択肢であり、すべての一般的なサーバーで動作することが知られています。トランザクションの一時停止などの高度な機能は、特別な構成を必要とせずに、多くのサーバー(GlassFish、JBoss、Geronimo を含む)でも機能します。ただし、完全にサポートされているトランザクションの一時停止およびさらに高度な統合のために、Spring には WebLogic サーバーおよび WebSphere 用の特別なアダプターが含まれています。これらのアダプターについては、次のセクションで説明します。

WebLogic サーバーや WebSphere などの標準的なシナリオでは、便利な <tx:jta-transaction-manager/> 構成要素の使用を検討してください。構成すると、この要素は基になるサーバーを自動的に検出し、プラットフォームで使用可能な最適なトランザクションマネージャーを選択します。これは、サーバー固有のアダプタークラスを明示的に構成する必要がないことを意味します(次のセクションで説明します)。むしろ、標準の JtaTransactionManager がデフォルトのフォールバックとして、自動的に選択されます。

1.8.1. IBM WebSphere

WebSphere 6.1.0.9 以降では、推奨される Spring JTA トランザクションマネージャーは WebSphereUowTransactionManager です。この特別なアダプターは、WebSphere Application Server 6.1.0.9 以降で使用可能な IBM の UOWManager API を使用します。このアダプターを使用すると、Spring によるトランザクションの中断(PROPAGATION_REQUIRES_NEW によって開始された中断および再開)が IBM によって公式にサポートされます。

1.8.2. Oracle WebLogic サーバー

WebLogic サーバー 9.0 以上では、通常、ストック JtaTransactionManager クラスの代わりに WebLogicJtaTransactionManager を使用します。通常の JtaTransactionManager のこの特別な WebLogic 固有のサブクラスは、標準の JTA セマンティクスを超えて、WebLogic 管理のトランザクション環境で Spring のトランザクション定義の全機能をサポートします。機能には、トランザクション名、トランザクションごとの分離レベル、すべての場合のトランザクションの適切な再開が含まれます。

1.9. 一般的な問題の解決策

このセクションでは、いくつかの一般的な問題の解決策について説明します。

1.9.1. 特定の DataSource に間違ったトランザクションマネージャーを使用する

選択したトランザクションテクノロジーと要件に基づいて、正しい PlatformTransactionManager 実装を使用します。適切に使用すると、Spring Framework は単純で移植可能な抽象化を提供するだけです。グローバルトランザクションを使用する場合は、すべてのトランザクション操作に org.springframework.transaction.jta.JtaTransactionManager クラス(またはそのアプリケーションサーバー固有のサブクラス)を使用する必要があります。それ以外の場合、トランザクションインフラストラクチャは、コンテナー DataSource インスタンスなどのリソースでローカルトランザクションを実行しようとします。このようなローカルトランザクションは意味をなさないため、適切なアプリケーションサーバーはそれらをエラーとして扱います。

1.10. その他のリソース

Spring Framework のトランザクションサポートの詳細については、以下を参照してください。

2. DAO サポート

Spring のデータアクセスオブジェクト(DAO)サポートは、データアクセステクノロジ(JDBC、Hibernate、JPA など)を一貫した方法で簡単に操作できるようにすることを目的としています。これにより、前述の永続化テクノロジーを非常に簡単に切り替えることができ、各テクノロジーに固有の例外をキャッチすることを心配せずにコーディングすることもできます。

2.1. 一貫した例外階層

Spring は、SQLException などのテクノロジー固有の例外から、DataAccessException をルート例外として持つ独自の例外クラス階層への便利な変換を提供します。これらの例外は元の例外をラップするため、何が間違っていたのかについての情報を失うリスクはありません。

JDBC 例外に加えて、Spring は JPA および Hibernate 固有の例外をラップして、集中的なランタイム例外のセットに変換することもできます。これにより、DAO で煩わしい定型的なキャッチアンドスローブロックや例外宣言を行うことなく、適切なレイヤーでのみ、ほとんどの回復不可能な永続化例外を処理できます。(ただし、必要に応じて例外をトラップして処理できます)前述のように、JDBC 例外(データベース固有のダイアレクトを含む)も同じ階層に変換されます。つまり、一貫したプログラミングモデル内で JDBC を使用して一部の操作を実行できます。

上記の説明は、Spring のさまざまな ORM フレームワークのサポートにおけるさまざまなテンプレートクラスに当てはまります。インターセプターベースのクラスを使用する場合、アプリケーションは SessionFactoryUtils の convertHibernateAccessException(..) メソッドまたは convertJpaAccessException() メソッドに委譲することにより、HibernateExceptions および PersistenceExceptions 自体の処理に注意する必要があります。これらのメソッドは、例外を org.springframework.dao 例外階層の例外と互換性のある例外に変換します。PersistenceExceptions はチェックされていないため、スローされる可能性があります(ただし、例外に関して汎用 DAO 抽象化を犠牲にします)。

次の図は、Spring が提供する例外階層を示しています。(イメージで詳細に示されているクラス階層は、DataAccessException 階層全体のサブセットのみを示していることに注意してください。)

DataAccessException

2.2. DAO またはリポジトリクラスの構成に使用されるアノテーション

データアクセスオブジェクト(DAO)またはリポジトリが例外変換を提供することを保証する最良の方法は、@Repository アノテーションを使用することです。また、このアノテーションにより、コンポーネントスキャンサポートは、XML 構成エントリを提供することなく、DAO およびリポジトリを検索および構成できます。次の例は、@Repository アノテーションの使用方法を示しています。

Java
@Repository (1)
public class SomeMovieFinder implements MovieFinder {
    // ...
}
1@Repository アノテーション。
Kotlin
@Repository (1)
class SomeMovieFinder : MovieFinder {
    // ...
}
1@Repository アノテーション。

使用される永続化テクノロジーに応じて、DAO またはリポジトリの実装は永続化リソースにアクセスする必要があります。例:JDBC ベースのリポジトリは JDBC DataSource にアクセスする必要があり、JPA ベースのリポジトリは EntityManager にアクセスする必要があります。これを実現する最も簡単な方法は、@Autowired@Inject@Resource または @PersistenceContext アノテーションのいずれかを使用して、このリソース依存関係を注入することです。次の例は、JPA リポジトリに対して機能します。

Java
@Repository
public class JpaMovieFinder implements MovieFinder {

    @PersistenceContext
    private EntityManager entityManager;

    // ...
}
Kotlin
@Repository
class JpaMovieFinder : MovieFinder {

    @PersistenceContext
    private lateinit var entityManager: EntityManager

    // ...
}

従来の Hibernate API を使用する場合、次の例に示すように、SessionFactory を注入できます。

Java
@Repository
public class HibernateMovieFinder implements MovieFinder {

    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    // ...
}
Kotlin
@Repository
class HibernateMovieFinder(private val sessionFactory: SessionFactory) : MovieFinder {
    // ...
}

ここで示す最後の例は、一般的な JDBC サポート用です。DataSource を初期化メソッドまたはコンストラクターに挿入し、この DataSource を使用して JdbcTemplate およびその他のデータアクセスサポートクラス(SimpleJdbcCall など)を作成できます。次の例は、DataSource をオートワイヤーします。

Java
@Repository
public class JdbcMovieFinder implements MovieFinder {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void init(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    // ...
}
Kotlin
@Repository
class JdbcMovieFinder(dataSource: DataSource) : MovieFinder {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    // ...
}
これらのアノテーションを活用するためにアプリケーションコンテキストを構成する方法の詳細については、各永続化テクノロジの特定のカバレッジを参照してください。

3. JDBC を使用したデータアクセス

Spring Framework JDBC 抽象化によって提供される値は、おそらく次の表に概説されている一連のアクションによって最もよく示されます。この表は、Spring が処理するアクションと、ユーザーが責任を負うアクションを示しています。

表 4: Spring JDBC - 誰が何をしますか?
アクション Spring あなた

接続パラメーターを定義します。

X

接続を開きます。

X

SQL ステートメントを指定します。

X

パラメーターを宣言し、パラメーター値を提供する

X

ステートメントを準備して実行します。

X

ループを設定して、結果を繰り返し処理します(存在する場合)。

X

反復ごとに作業を行います。

X

例外を処理します。

X

トランザクションを処理します。

X

接続、ステートメント、結果セットを閉じます。

X

Spring Framework は、JDBC を退屈な API にしてしまうような低レベルの詳細な処理を代わりに行います。

3.1. JDBC データベースアクセスのアプローチの選択

JDBC データベースアクセスの基盤を形成するために、いくつかのアプローチから選択できます。JdbcTemplate の 3 つのフレーバーに加えて、新しい SimpleJdbcInsert および SimpleJdbcCall アプローチはデータベースメタデータを最適化し、RDBMS オブジェクトスタイルは JDO クエリデザインと同様のよりオブジェクト指向のアプローチを取ります。これらのアプローチのいずれかを使用し始めても、別のアプローチの機能を含めるために組み合わせて使用できます。すべてのアプローチには JDBC 2.0 準拠のドライバーが必要であり、一部の高度な機能には JDBC 3.0 ドライバーが必要です。

  • JdbcTemplate は、クラシックで最も人気のある Spring JDBC アプローチです。この「最低レベル」アプローチと他のすべてのアプローチは、カバーで JdbcTemplate を使用します。

  • NamedParameterJdbcTemplate は、従来の JDBC ? プレースホルダーの代わりに、JdbcTemplate をラップして名前付きパラメーターを提供します。このアプローチは、SQL ステートメントに複数のパラメーターがある場合に、より優れたドキュメントと使いやすさを提供します。

  • SimpleJdbcInsert および SimpleJdbcCall は、データベースメタデータを最適化して、必要な構成の量を制限します。このアプローチにより、コーディングが簡素化されるため、テーブルまたはプロシージャの名前のみを提供し、列名に一致するパラメーターのマップを提供するだけで済みます。これは、データベースが適切なメタデータを提供する場合にのみ機能します。データベースがこのメタデータを提供しない場合、パラメーターの明示的な構成を提供する必要があります。

  • RDBMS オブジェクト — MappingSqlQuerySqlUpdate および StoredProcedure を含む — データアクセス層の初期化中に、再利用可能でスレッドセーフなオブジェクトを作成する必要があります。このアプローチは、クエリ文字列を定義し、パラメーターを宣言し、クエリをコンパイルする JDO クエリをモデルにしています。これを行うと、execute(…​)update(…​) および findObject(…​) メソッドをさまざまなパラメーター値で複数回呼び出すことができます。

3.2. パッケージ階層

Spring Framework の JDBC 抽象化フレームワークは、4 つの異なるパッケージで構成されています。

  • coreorg.springframework.jdbc.core パッケージには、JdbcTemplate クラスとそのさまざまなコールバックインターフェースに加えて、さまざまな関連クラスが含まれています。org.springframework.jdbc.core.simple という名前のサブパッケージには、SimpleJdbcInsert および SimpleJdbcCall クラスが含まれています。org.springframework.jdbc.core.namedparam という名前の別のサブパッケージには、NamedParameterJdbcTemplate クラスと関連サポートクラスが含まれています。JDBC コアクラスを使用して基本的な JDBC 処理とエラー処理を制御するJDBC バッチ操作SimpleJdbc クラスを使用した JDBC 操作の簡素化を参照してください。

  • datasourceorg.springframework.jdbc.datasource パッケージには、DataSource アクセスの簡単なユーティリティクラスと、Java EE コンテナーの外部で未変更の JDBC コードをテストおよび実行するために使用できるさまざまなシンプルな DataSource 実装が含まれています。org.springfamework.jdbc.datasource.embedded という名前のサブパッケージは、HSQL、H2、Derby などの Java データベースエンジンを使用して埋め込みデータベースを作成するためのサポートを提供します。データベース接続の制御および組み込みデータベースのサポートを参照してください。

  • objectorg.springframework.jdbc.object パッケージには、RDBMS クエリ、更新、ストアドプロシージャをスレッドセーフで再利用可能なオブジェクトとして表すクラスが含まれています。JDBC 操作を Java オブジェクトとしてモデル化するを参照してください。このアプローチは JDO によってモデル化されますが、クエリによって返されるオブジェクトは自然にデータベースから切断されます。この高レベルの JDBC 抽象化は、org.springframework.jdbc.core パッケージの低レベル抽象化に依存しています。

  • supportorg.springframework.jdbc.support パッケージは、SQLException 変換機能といくつかのユーティリティクラスを提供します。JDBC 処理中にスローされた例外は、org.springframework.dao パッケージで定義された例外に変換されます。つまり、Spring JDBC 抽象化レイヤーを使用するコードでは、JDBC または RDBMS 固有のエラー処理を実装する必要はありません。すべての変換された例外はチェックされていないため、他の例外を呼び出し元に伝搬させながら、回復可能な例外をキャッチするオプションが提供されます。SQLExceptionTranslator を使用するを参照してください。

3.3. JDBC コアクラスを使用して基本的な JDBC 処理とエラー処理を制御する

このセクションでは、JDBC コアクラスを使用して、エラー処理を含む基本的な JDBC 処理を制御する方法について説明します。次のトピックが含まれます。

3.3.1. JdbcTemplate を使用する

JdbcTemplate は、JDBC コアパッケージの中心的なクラスです。リソースの作成と解放を処理するため、接続を閉じるのを忘れるなどの一般的なエラーを回避できます。コア JDBC ワークフローの基本的なタスク(ステートメントの作成や実行など)を実行し、アプリケーションコードを残して SQL を提供し、結果を抽出します。JdbcTemplate クラス:

  • SQL クエリを実行する

  • ステートメントとストアドプロシージャコールを更新する

  • ResultSet インスタンスの反復と、返されたパラメーター値の抽出を実行します。

  • JDBC 例外をキャッチし、org.springframework.dao パッケージで定義された汎用の、より有益な例外階層に変換します。( 一貫した例外階層を参照してください。)

コードに JdbcTemplate を使用する場合、コールバックインターフェースを実装するだけで、明確に定義された契約が提供されます。JdbcTemplate クラスによって提供される Connection が与えられると、PreparedStatementCreator コールバックインターフェースは準備されたステートメントを作成し、SQL および必要なパラメーターを提供します。同じことが、呼び出し可能ステートメントを作成する CallableStatementCreator インターフェースにも当てはまります。RowCallbackHandler インターフェースは、ResultSet の各行から値を抽出します。

DataSource 参照を使用して直接インスタンス化することにより、DAO 実装内で JdbcTemplate を使用するか、Spring IoC コンテナーで構成して Bean 参照として DAO に渡すことができます。

DataSource は、Spring IoC コンテナー内の Bean として常に構成する必要があります。最初のケースでは、Bean はサービスに直接与えられます。2 番目の場合、準備されたテンプレートに与えられます。

このクラスによって発行されるすべての SQL は、テンプレートインスタンスの完全修飾クラス名(通常は JdbcTemplate ですが、JdbcTemplate クラスのカスタムサブクラスを使用する場合は異なる場合があります)に対応するカテゴリの DEBUG レベルで記録されます。

以下のセクションでは、JdbcTemplate の使用例をいくつか示します。これらの例は、JdbcTemplate によって公開されたすべての機能の完全なリストではありません。詳細については、付随する javadoc を参照してください。

クエリ (SELECT)

次のクエリは、リレーションの行数を取得します。

Java
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
Kotlin
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!

次のクエリはバインド変数を使用します。

Java
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
        "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
Kotlin
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
        "select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!

次のクエリは、String を探します。

Java
String lastName = this.jdbcTemplate.queryForObject(
        "select last_name from t_actor where id = ?",
        String.class, 1212L);
Kotlin
val lastName = this.jdbcTemplate.queryForObject<String>(
        "select last_name from t_actor where id = ?",
        arrayOf(1212L))!!

次のクエリは、単一のドメインオブジェクトを見つけて入力します。

Java
Actor actor = jdbcTemplate.queryForObject(
        "select first_name, last_name from t_actor where id = ?",
        (resultSet, rowNum) -> {
            Actor newActor = new Actor();
            newActor.setFirstName(resultSet.getString("first_name"));
            newActor.setLastName(resultSet.getString("last_name"));
            return newActor;
        },
        1212L);
Kotlin
val actor = jdbcTemplate.queryForObject(
            "select first_name, last_name from t_actor where id = ?",
            arrayOf(1212L)) { rs, _ ->
        Actor(rs.getString("first_name"), rs.getString("last_name"))
    }

次のクエリは、ドメインオブジェクトのリストを見つけてデータを設定します。

Java
List<Actor> actors = this.jdbcTemplate.query(
        "select first_name, last_name from t_actor",
        (resultSet, rowNum) -> {
            Actor actor = new Actor();
            actor.setFirstName(resultSet.getString("first_name"));
            actor.setLastName(resultSet.getString("last_name"));
            return actor;
        });
Kotlin
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
        Actor(rs.getString("first_name"), rs.getString("last_name"))

コードの最後の 2 つのスニペットが実際に同じアプリケーションに存在する場合、2 つの RowMapper ラムダ式に存在する重複を削除し、単一のフィールドに抽出して、必要に応じて DAO メソッドで参照できるようにします。例:次のように上記のコードスニペットを記述する方がよい場合があります。

Java
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
    return actor;
};

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query( "select first_name, last_name from t_actor", actorRowMapper);
}
Kotlin
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
    Actor(rs.getString("first_name"), rs.getString("last_name"))
}

fun findAllActors(): List<Actor> {
    return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}
JdbcTemplate による更新(INSERTUPDATE および DELETE

update(..) メソッドを使用して、挿入、更新、削除の操作を実行できます。パラメーター値は通常、変数引数として、またはオブジェクト配列として提供されます。

次の例では、新しいエントリを挿入します。

Java
this.jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling");
Kotlin
jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling")

次の例は、既存のエントリを更新します。

Java
this.jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L);
Kotlin
jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L)

次の例では、エントリを削除します。

Java
this.jdbcTemplate.update(
        "delete from t_actor where id = ?",
        Long.valueOf(actorId));
Kotlin
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
その他の JdbcTemplate 操作

execute(..) メソッドを使用して、任意の SQL を実行できます。このメソッドは DDL ステートメントによく使用されます。コールバックインターフェース、変数配列のバインドなどをとるバリアントでかなりオーバーロードされています。次の例では、テーブルを作成します。

Java
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
Kotlin
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")

次の例では、ストアドプロシージャを呼び出します。

Java
this.jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        Long.valueOf(unionId));
Kotlin
jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        unionId.toLong())

より洗練されたストアドプロシージャのサポートについては、後で説明します。

JdbcTemplate ベストプラクティス

JdbcTemplate クラスのインスタンスは、一度設定されるとスレッドセーフです。これは、JdbcTemplate の単一インスタンスを構成し、この共有参照を複数の DAO(またはリポジトリ)に安全に挿入できることを意味するため、重要です。JdbcTemplate は、DataSource への参照を維持するという点でステートフルですが、この状態は会話状態ではありません。

JdbcTemplate クラス(および関連する NamedParameterJdbcTemplate クラス)を使用する際の一般的な方法は、Spring 構成ファイルで DataSource を構成し、その共有 DataSource Bean を DAO クラスに依存性注入することです。JdbcTemplate は、DataSource の setter で作成されます。これは、次のような DAO につながります。

Java
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
Kotlin
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

次の例は、対応する 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <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>

    <context:property-placeholder location="jdbc.properties"/>

</beans>

明示的な構成の代わりに、コンポーネントスキャンと依存性注入のアノテーションサポートを使用します。この場合、クラスに @Repository (コンポーネントスキャンの候補になります)でアノテーションを付け、DataSource setter メソッドに @Autowired でアノテーションを付けることができます。次の例は、その方法を示しています。

Java
@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired (2)
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource); (3)
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1@Repository でクラスにアノテーションを付けます。
2DataSource setter メソッドに @Autowired でアノテーションを付けます。
3DataSource で新しい JdbcTemplate を作成します。
Kotlin
@Repository (1)
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { (2)

    private val jdbcTemplate = JdbcTemplate(dataSource) (3)

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
1@Repository でクラスにアノテーションを付けます。
2DataSource のコンストラクター注入。
3DataSource で新しい JdbcTemplate を作成します。

次の例は、対応する 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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Scans within the base package of the application for @Component classes to configure as beans -->
    <context:component-scan base-package="org.springframework.docs.test" />

    <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>

    <context:property-placeholder location="jdbc.properties"/>

</beans>

Spring の JdbcDaoSupport クラスを使用し、さまざまな JDBC でバックアップされた DAO クラスがそこから拡張される場合、サブクラスは JdbcDaoSupport クラスから setDataSource(..) メソッドを継承します。このクラスから継承するかどうかを選択できます。JdbcDaoSupport クラスは、利便性のみを目的として提供されています。

上記のテンプレート初期化スタイルのどちらを使用する(またはしない)かに関係なく、SQL を実行するたびに JdbcTemplate クラスの新しいインスタンスを作成する必要はほとんどありません。一度構成すると、JdbcTemplate インスタンスはスレッドセーフになります。アプリケーションが複数のデータベースにアクセスする場合、複数の JdbcTemplate インスタンスが必要になる場合があります。これには、複数の DataSources が必要であり、その後、複数の異なる構成の JdbcTemplate インスタンスが必要です。

3.3.2. NamedParameterJdbcTemplate を使用する

NamedParameterJdbcTemplate クラスは、古典的なプレースホルダー('?')引数のみを使用した JDBC ステートメントのプログラミングとは対照的に、名前付きパラメーターを使用した JDBC ステートメントのプログラミングのサポートを追加します。NamedParameterJdbcTemplate クラスは、JdbcTemplate をラップし、ラップされた JdbcTemplate に委譲して、その作業の多くを行います。このセクションでは、JdbcTemplate 自体とは異なる NamedParameterJdbcTemplate クラスの領域、つまり、名前付きパラメーターを使用した JDBC ステートメントのプログラミングについてのみ説明します。次の例は、NamedParameterJdbcTemplate の使用方法を示しています。

Java
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

    String sql = "select count(*) from T_ACTOR where first_name = :first_name";

    SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
Kotlin
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

fun countOfActorsByFirstName(firstName: String): Int {
    val sql = "select count(*) from T_ACTOR where first_name = :first_name"
    val namedParameters = MapSqlParameterSource("first_name", firstName)
    return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}

sql 変数に割り当てられた値と、namedParameters 変数(タイプ MapSqlParameterSource の)にプラグインされる対応する値での名前付きパラメーター表記の使用に注意してください。

または、Map ベースのスタイルを使用して、名前付きパラメーターとそれに対応する値を NamedParameterJdbcTemplate インスタンスに渡すことができます。NamedParameterJdbcOperations によって公開され、NamedParameterJdbcTemplate クラスによって実装される残りのメソッドは同様のパターンに従い、ここでは説明しません。

次の例は、Map ベースのスタイルの使用を示しています。

Java
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

    String sql = "select count(*) from T_ACTOR where first_name = :first_name";

    Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters,  Integer.class);
}
Kotlin
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

fun countOfActorsByFirstName(firstName: String): Int {
    val sql = "select count(*) from T_ACTOR where first_name = :first_name"
    val namedParameters = mapOf("first_name" to firstName)
    return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}

NamedParameterJdbcTemplate (および同じ Java パッケージに存在する)に関連する優れた機能の 1 つは、SqlParameterSource インターフェースです。前のコードスニペットの 1 つ(MapSqlParameterSource クラス)で、このインターフェースの実装の例を既に見ました。SqlParameterSource は、NamedParameterJdbcTemplate への名前付きパラメーター値のソースです。MapSqlParameterSource クラスは、java.util.Map の周囲のアダプターである単純な実装です。ここで、キーはパラメーター名で、値はパラメーター値です。

別の SqlParameterSource 実装は BeanPropertySqlParameterSource クラスです。このクラスは、任意の JavaBean(つまり、JavaBean 規約: Oracle (英語) に準拠するクラスのインスタンス)をラップし、ラップされた JavaBean のプロパティを名前付きパラメーター値のソースとして使用します。

次の例は、典型的な JavaBean を示しています。

Java
public class Actor {

    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public Long getId() {
        return this.id;
    }

    // setters omitted...

}
Kotlin
data class Actor(val id: Long, val firstName: String, val lastName: String)

次の例では、NamedParameterJdbcTemplate を使用して、前の例に示したクラスのメンバーの数を返します。

Java
// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActors(Actor exampleActor) {

    // notice how the named parameters match the properties of the above 'Actor' class
    String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";

    SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
Kotlin
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

fun countOfActors(exampleActor: Actor): Int {
    // notice how the named parameters match the properties of the above 'Actor' class
    val sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"
    val namedParameters = BeanPropertySqlParameterSource(exampleActor)
    return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}

NamedParameterJdbcTemplate クラスは、古典的な JdbcTemplate テンプレートをラップしていることに注意してください。JdbcTemplate クラスにのみ存在する機能にアクセスするために、ラップされた JdbcTemplate インスタンスにアクセスする必要がある場合、getJdbcOperations() メソッドを使用して、JdbcOperations インターフェースを介してラップされた JdbcTemplate にアクセスできます。

アプリケーションのコンテキストで NamedParameterJdbcTemplate クラスを使用する際のガイドラインについては、JdbcTemplate ベストプラクティスも参照してください。

3.3.3. SQLExceptionTranslator を使用する

SQLExceptionTranslator は、SQLExceptions と Spring 自身の org.springframework.dao.DataAccessException の間で変換できるクラスによって実装されるインターフェースです。これは、データアクセス戦略に関して不可知です。実装は、精度を高めるために汎用(たとえば、JDBC の SQLState コードを使用)または独自(たとえば、Oracle エラーコードを使用)にすることができます。

SQLErrorCodeSQLExceptionTranslator は、デフォルトで使用される SQLExceptionTranslator の実装です。この実装では、特定のベンダーコードが使用されます。SQLState 実装よりも正確です。エラーコードの変換は、SQLErrorCodes と呼ばれる JavaBean タイプクラスに保持されているコードに基づいています。このクラスは、SQLErrorCodesFactory によって作成および設定されます。SQLErrorCodesFactory は、(名前が示すように) sql-error-codes.xml という名前の構成ファイルの内容に基づいて SQLErrorCodes を作成するためのファクトリです。このファイルにはベンダーコードが入力され、DatabaseMetaData から取得した DatabaseProductName に基づいています。使用している実際のデータベースのコードが使用されます。

SQLErrorCodeSQLExceptionTranslator は、次の順序で一致ルールを適用します。

  1. サブクラスによって実装されるカスタム変換。通常、提供された具体的な SQLErrorCodeSQLExceptionTranslator が使用されるため、この規則は適用されません。サブクラスの実装を実際に提供した場合にのみ適用されます。

  2. SQLErrorCodes クラスの customSqlExceptionTranslator プロパティとして提供される SQLExceptionTranslator インターフェースのカスタム実装。

  3. CustomSQLErrorCodesTranslation クラスのインスタンスのリスト(SQLErrorCodes クラスの customTranslations プロパティに提供)で一致が検索されます。

  4. エラーコードの一致が適用されます。

  5. フォールバックトランスレータを使用します。SQLExceptionSubclassTranslator はデフォルトのフォールバックトランスレータです。この翻訳が利用できない場合、次のフォールバックトランスレータは SQLStateSQLExceptionTranslator です。

SQLErrorCodesFactory は、デフォルトで Error コードとカスタム例外変換を定義するために使用されます。それらは、クラスパスから sql-error-codes.xml という名前のファイルで検索され、一致する SQLErrorCodes インスタンスは、使用中のデータベースのデータベースメタデータからのデータベース名に基づいて配置されます。

次の例に示すように、SQLErrorCodeSQLExceptionTranslator を継承できます。

Java
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {

    protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
        if (sqlEx.getErrorCode() == -12345) {
            return new DeadlockLoserDataAccessException(task, sqlEx);
        }
        return null;
    }
}
Kotlin
class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() {

    override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? {
        if (sqlEx.errorCode == -12345) {
                return DeadlockLoserDataAccessException(task, sqlEx)
            }
            return null;
    }
}

上記の例では、特定のエラーコード(-12345)が変換されますが、他のエラーはデフォルトのトランスレーター実装によって変換されずに残ります。このカスタムトランスレーターを使用するには、メソッド setExceptionTranslator を介して JdbcTemplate に渡す必要があり、このトランスレーターが必要なすべてのデータアクセス処理にこの JdbcTemplate を使用する必要があります。次の例は、このカスタムトランスレータの使用方法を示しています。

Java
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {

    // create a JdbcTemplate and set data source
    this.jdbcTemplate = new JdbcTemplate();
    this.jdbcTemplate.setDataSource(dataSource);

    // create a custom translator and set the DataSource for the default translation lookup
    CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
    tr.setDataSource(dataSource);
    this.jdbcTemplate.setExceptionTranslator(tr);

}

public void updateShippingCharge(long orderId, long pct) {
    // use the prepared JdbcTemplate for this update
    this.jdbcTemplate.update("update orders" +
        " set shipping_charge = shipping_charge * ? / 100" +
        " where id = ?", pct, orderId);
}
Kotlin
// create a JdbcTemplate and set data source
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
    // create a custom translator and set the DataSource for the default translation lookup
    exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
        this.dataSource = dataSource
    }
}

fun updateShippingCharge(orderId: Long, pct: Long) {
    // use the prepared JdbcTemplate for this update
    this.jdbcTemplate!!.update("update orders" +
            " set shipping_charge = shipping_charge * ? / 100" +
            " where id = ?", pct, orderId)
}

sql-error-codes.xml でエラーコードを検索するために、カスタムトランスレーターにデータソースが渡されます。

3.3.4. ステートメントの実行

SQL ステートメントの実行に必要なコードはごくわずかです。DataSource と JdbcTemplate が必要です(JdbcTemplate で提供される便利なメソッドを含む)。次の例は、新しいテーブルを作成する最小限であるが完全に機能するクラスに含める必要があるものを示しています。

Java
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAStatement {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void doExecute() {
        this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
    }
}
Kotlin
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate

class ExecuteAStatement(dataSource: DataSource) {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    fun doExecute() {
        jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
    }
}

3.3.5. クエリの実行

一部のクエリメソッドは、単一の値を返します。1 つの行からカウントまたは特定の値を取得するには、queryForObject(..) を使用します。後者は、返された JDBC Type を、引数として渡される Java クラスに変換します。型変換が無効な場合、InvalidDataAccessApiUsageException がスローされます。次の例には、int 用と String を照会する 2 つの照会メソッドが含まれています。

Java
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class RunAQuery {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int getCount() {
        return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
    }

    public String getName() {
        return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
    }
}
Kotlin
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate

class RunAQuery(dataSource: DataSource) {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    val count: Int
        get() = jdbcTemplate.queryForObject("select count(*) from mytable")!!

    val name: String?
        get() = jdbcTemplate.queryForObject("select name from mytable")
}

単一の結果クエリメソッドに加えて、いくつかのメソッドは、クエリが返した各行のエントリを含むリストを返します。最も一般的な方法は queryForList(..) で、List を返します。各要素は、列名をキーとして使用して、列ごとに 1 つのエントリを含む Map です。前の例にメソッドを追加してすべての行のリストを取得する場合、次のようになります。

Java
private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public List<Map<String, Object>> getList() {
    return this.jdbcTemplate.queryForList("select * from mytable");
}
Kotlin
private val jdbcTemplate = JdbcTemplate(dataSource)

fun getList(): List<Map<String, Any>> {
    return jdbcTemplate.queryForList("select * from mytable")
}

返されるリストは次のようになります。

[{name=Bob, id=1}, {name=Mary, id=2}]

3.3.6. データベースの更新

次の例では、特定の主キーの列を更新します。

Java
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void setName(int id, String name) {
        this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
    }
}
Kotlin
import javax.sql.DataSource
import org.springframework.jdbc.core.JdbcTemplate

class ExecuteAnUpdate(dataSource: DataSource) {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    fun setName(id: Int, name: String) {
        jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
    }
}

上記の例では、SQL ステートメントには行パラメーターのプレースホルダーがあります。パラメーター値を varargs として渡すことも、オブジェクトの配列として渡すこともできます。プリミティブラッパークラスでプリミティブを明示的にラップするか、オートボクシングを使用する必要があります。

3.3.7. 自動生成されたキーの取得

update() 簡易メソッドは、データベースによって生成された主キーの取得をサポートしています。このサポートは、JDBC 3.0 標準の一部です。詳細については、仕様の 13.6 章を参照してください。このメソッドは最初の引数として PreparedStatementCreator を受け取り、これが必要な挿入ステートメントの指定メソッドです。もう 1 つの引数は KeyHolder で、更新から正常に戻ったときに生成されたキーが含まれます。適切な PreparedStatement を作成するための標準的な単一のメソッドはありません(これにより、メソッドのシグネチャーがそのままである理由が説明されています)。次の例は Oracle で機能しますが、他のプラットフォームでは機能しない場合があります。

Java
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
    PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
    ps.setString(1, name);
    return ps;
}, keyHolder);

// keyHolder.getKey() now contains the generated key
Kotlin
val INSERT_SQL = "insert into my_test (name) values(?)"
val name = "Rob"

val keyHolder = GeneratedKeyHolder()
jdbcTemplate.update({
    it.prepareStatement (INSERT_SQL, arrayOf("id")).apply { setString(1, name) }
}, keyHolder)

// keyHolder.getKey() now contains the generated key

3.4. データベース接続の制御

このセクションでは以下について説明します。

3.4.1. DataSource を使用する

Spring は、DataSource を介してデータベースへの接続を取得します。DataSource は JDBC 仕様の一部であり、一般化された接続ファクトリです。コンテナーまたはフレームワークは、接続プールとトランザクション管理の課題をアプリケーションコードから隠すことができます。開発者として、データベースへの接続方法に関する詳細を知る必要はありません。それは、データソースを設定する管理者の責任です。ほとんどの場合、コードを開発およびテストするときに両方のロールを果たしますが、必ずしも本番データソースがどのように構成されているかを知る必要はありません。

Spring の JDBC レイヤーを使用する場合、JNDI からデータソースを取得するか、サードパーティが提供する接続プール実装を使用して独自のデータソースを構成できます。従来の選択肢は、Bean スタイルの DataSource クラスを備えた Apache Commons DBCP および C3P0 です。最新の JDBC 接続プールの場合は、代わりにビルダースタイルの API を備えた HikariCP を検討してください。

DriverManagerDataSource および SimpleDriverDataSource クラス(Spring ディストリビューションに含まれる)は、テスト目的でのみ使用してください!これらのバリアントはプーリングを提供せず、接続に対して複数のリクエストが行われたときにパフォーマンスが低下します。

次のセクションでは、Spring の DriverManagerDataSource 実装を使用します。他のいくつかの DataSource バリアントについては後で説明します。

DriverManagerDataSource を構成するには:

  1. 通常は JDBC 接続を取得するため、DriverManagerDataSource との接続を取得します。

  2. DriverManager がドライバークラスをロードできるように、JDBC ドライバーの完全修飾クラス名を指定します。

  3. JDBC ドライバー間で異なる URL を提供します。(正しい値については、ドライバーのドキュメントを参照してください。)

  4. データベースに接続するためのユーザー名とパスワードを入力します。

次の例は、Java で DriverManagerDataSource を構成する方法を示しています。

Java
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
Kotlin
val dataSource = DriverManagerDataSource().apply {
    setDriverClassName("org.hsqldb.jdbcDriver")
    url = "jdbc:hsqldb:hsql://localhost:"
    username = "sa"
    password = ""
}

次の例は、対応する XML 構成を示しています。

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <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>

<context:property-placeholder location="jdbc.properties"/>

次の 2 つの例は、DBCP および C3P0 の基本的な接続と構成を示しています。プーリング機能の制御に役立つその他のオプションについては、それぞれの接続プーリング実装の製品ドキュメントを参照してください。

次の例は、DBCP 構成を示しています。

<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>

<context:property-placeholder location="jdbc.properties"/>

次の例は、C3P0 構成を示しています。

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

3.4.2. DataSourceUtils を使用する

DataSourceUtils クラスは、JNDI から接続を取得し、必要に応じて接続を閉じるための static メソッドを提供する便利で強力なヘルパークラスです。DataSourceTransactionManager などのスレッドバインド接続をサポートします。

3.4.3. SmartDataSource の実装

SmartDataSource インターフェースは、リレーショナルデータベースへの接続を提供できるクラスによって実装する必要があります。DataSource インターフェースを継承し、それを使用するクラスが、特定の操作後に接続を閉じる必要があるかどうかを照会できるようにします。この使用方法は、接続を再利用する必要があることがわかっている場合に効率的です。

3.4.4. AbstractDataSource の拡張

AbstractDataSource は、Spring の DataSource 実装の abstract 基本クラスです。すべての DataSource 実装に共通のコードを実装します。独自の DataSource 実装を作成する場合は、AbstractDataSource クラスを継承する必要があります。

3.4.5. SingleConnectionDataSource を使用する

SingleConnectionDataSource クラスは、使用後に閉じられない単一の Connection をラップする SmartDataSource インターフェースの実装です。これはマルチスレッド対応ではありません。

プールされた接続を想定してクライアントコードが close を呼び出す場合(永続性ツールを使用する場合など)、suppressClose プロパティを true に設定する必要があります。この設定は、物理接続をラップする緊密なプロキシを返します。これをネイティブ Oracle Connection または同様のオブジェクトにキャストできないことに注意してください。

SingleConnectionDataSource は、主にテストクラスです。例:シンプルな JNDI 環境と組み合わせて、アプリケーションサーバーの外部でコードを簡単にテストできます。DriverManagerDataSource とは対照的に、物理接続の過剰な作成を回避するため、常に同じ接続を再利用します。

3.4.6. DriverManagerDataSource を使用する

DriverManagerDataSource クラスは、Bean プロパティを介してプレーン JDBC ドライバーを構成し、毎回新しい Connection を返す標準 DataSource インターフェースの実装です。

この実装は、Spring IoC コンテナー内の DataSource Bean として、または単純な JNDI 環境と組み合わせて、Java EE コンテナー外のテストおよびスタンドアロン環境に役立ちます。プールを想定した Connection.close() 呼び出しは接続を閉じるため、DataSource -aware 永続性コードはすべて機能するはずです。ただし、テスト環境であっても JavaBean スタイルの接続プール(commons-dbcp など)を使用するのは非常に簡単なので、そのような接続プールを DriverManagerDataSource よりも使用することがほぼ常に望ましいです。

3.4.7. TransactionAwareDataSourceProxy を使用する

TransactionAwareDataSourceProxy は、ターゲット DataSource のプロキシです。プロキシは、DataSource をターゲットとしてラップし、Spring が管理するトランザクションの認識を追加します。この点で、Java EE サーバーによって提供されるトランザクション JNDI DataSource に似ています。

既存のコードを呼び出して標準 JDBC DataSource インターフェース実装を渡す必要がある場合を除いて、このクラスを使用することはめったにありません。この場合、このコードを引き続き使用可能にし、同時にこのコードを Spring 管理トランザクションに参加させることができます。一般に、JdbcTemplate や DataSourceUtils など、リソース管理用のより高いレベルの抽象化を使用して、独自の新しいコードを作成することをお勧めします。

詳細については、TransactionAwareDataSourceProxy (Javadoc) javadoc を参照してください。

3.4.8. DataSourceTransactionManager を使用する

DataSourceTransactionManager クラスは、単一の JDBC データソース用の PlatformTransactionManager 実装です。指定されたデータソースから現在実行中のスレッドに JDBC 接続をバインドし、データソースごとに 1 つのスレッド接続を可能にします。

Java EE の標準 DataSource.getConnection ではなく、DataSourceUtils.getConnection(DataSource) を介して JDBC 接続を取得するには、アプリケーションコードが必要です。チェック済みの SQLExceptions ではなく、未チェックの org.springframework.dao 例外をスローします。すべてのフレームワーククラス(JdbcTemplate など)は、この戦略を暗黙的に使用します。このトランザクションマネージャーで使用しない場合、ルックアップ戦略は一般的なものとまったく同じように動作します。どのような場合でも使用できます。

DataSourceTransactionManager クラスは、適切な JDBC ステートメントクエリタイムアウトとして適用されるカスタム分離レベルとタイムアウトをサポートします。後者をサポートするには、アプリケーションコードで JdbcTemplate を使用するか、作成された各ステートメントに対して DataSourceUtils.applyTransactionTimeout(..) メソッドを呼び出す必要があります。

コンテナーが JTA をサポートする必要がないため、単一リソースの場合は JtaTransactionManager の代わりにこの実装を使用できます。必要な接続ルックアップパターンに固執する限り、両方の切り替えは設定の問題です。JTA はカスタム分離レベルをサポートしていません。

3.5. JDBC バッチ操作

ほとんどの JDBC ドライバーは、同じ準備済みステートメントへの複数の呼び出しをバッチ処理すると、パフォーマンスが向上します。更新をバッチにグループ化することにより、データベースへのラウンドトリップの回数を制限します。

3.5.1. JdbcTemplate を使用した基本的なバッチ操作

JdbcTemplate バッチ処理を実現するには、特別なインターフェース BatchPreparedStatementSetter の 2 つのメソッドを実装し、その実装を batchUpdate メソッド呼び出しの 2 番目のパラメーターとして渡します。getBatchSize メソッドを使用して、現在のバッチのサイズを提供できます。setValues メソッドを使用して、準備済みステートメントのパラメーターの値を設定できます。このメソッドは、getBatchSize 呼び出しで指定した回数と呼ばれます。次の例では、リストのエントリに基づいて t_actor テーブルを更新し、リスト全体がバッチとして使用されます。

Java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                new BatchPreparedStatementSetter() {
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        Actor actor = actors.get(i);
                        ps.setString(1, actor.getFirstName());
                        ps.setString(2, actor.getLastName());
                        ps.setLong(3, actor.getId().longValue());
                    }
                    public int getBatchSize() {
                        return actors.size();
                    }
                });
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    fun batchUpdate(actors: List<Actor>): IntArray {
        return jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                object: BatchPreparedStatementSetter {
                    override fun setValues(ps: PreparedStatement, i: Int) {
                        ps.setString(1, actors[i].firstName)
                        ps.setString(2, actors[i].lastName)
                        ps.setLong(3, actors[i].id)
                    }

                    override fun getBatchSize() = actors.size
                })
    }

    // ... additional methods
}

更新のストリームを処理したり、ファイルから読み込んだりする場合、希望するバッチサイズがありますが、最後のバッチにはその数のエントリがない場合があります。この場合、InterruptibleBatchPreparedStatementSetter インターフェースを使用できます。これにより、入力ソースが使い果たされると、バッチを中断できます。isBatchExhausted メソッドを使用すると、バッチの終了を通知できます。

3.5.2. オブジェクトのリストを使用したバッチ操作

JdbcTemplate と NamedParameterJdbcTemplate の両方が、バッチ更新を提供する代替メソッドを提供します。特別なバッチインターフェースを実装する代わりに、呼び出しのすべてのパラメーター値をリストとして提供します。フレームワークはこれらの値をループ処理し、内部準備済みステートメント setter を使用します。API は、名前付きパラメーターを使用するかどうかによって異なります。名前付きパラメーターに対して、SqlParameterSource の配列を提供します。これは、バッチのメンバーごとに 1 つのエントリです。SqlParameterSourceUtils.createBatch 簡易メソッドを使用してこの配列を作成し、Bean スタイルオブジェクトの配列(getter メソッドがパラメーターに対応する)、String -keyed Map インスタンス(対応するパラメーターを値として含む)、または両方の組み合わせを渡すことができます。

次の例は、名前付きパラメーターを使用したバッチ更新を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private NamedParameterTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int[] batchUpdate(List<Actor> actors) {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

    fun batchUpdate(actors: List<Actor>): IntArray {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }

        // ... additional methods
}

従来の ? プレースホルダーを使用する SQL ステートメントの場合、更新値を含むオブジェクト配列を含むリストを渡します。このオブジェクト配列には、SQL ステートメントのプレースホルダーごとに 1 つのエントリが必要であり、SQL ステートメントで定義されている順序と同じ順序である必要があります。

次の例は、従来の JDBC ? プレースホルダーを使用することを除いて、前述の例と同じです。

Java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        List<Object[]> batch = new ArrayList<Object[]>();
        for (Actor actor : actors) {
            Object[] values = new Object[] {
                    actor.getFirstName(), actor.getLastName(), actor.getId()};
            batch.add(values);
        }
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                batch);
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    fun batchUpdate(actors: List<Actor>): IntArray {
        val batch = mutableListOf<Array<Any>>()
        for (actor in actors) {
            batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
        }
        return jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?", batch)
    }

    // ... additional methods
}

前に説明したすべてのバッチ更新メソッドは、各バッチエントリの影響を受ける行の数を含む int 配列を返します。このカウントは JDBC ドライバーによって報告されます。カウントが利用できない場合、JDBC ドライバーは -2 の値を返します。

このようなシナリオでは、基になる PreparedStatement に値を自動的に設定するため、各値に対応する JDBC の型を、指定された Java の型から派生させる必要があります。これは通常はうまく機能しますが、問題が発生する可能性があります(たとえば、マップに含まれる null 値など)。このような場合、Spring はデフォルトで ParameterMetaData.getParameterType を呼び出しますが、JDBC ドライバーではコストが高くなる可能性があります。(Oracle 12c, JBoss および PostgreSQL で報告されているように)パフォーマンスの問題が発生した場合は、最新のドライバーバージョンを使用し、spring.jdbc.getParameterType.ignore プロパティを true (JVM システムプロパティとして、またはクラスパスのルートにある spring.properties ファイルで)に設定することを検討してください。

あるいは、「BatchPreparedStatementSetter」(前述)または「List」に指定された明示的な型配列を使用して、対応する JDBC 型を明示的に指定することを検討できます。<Object[]> 'registerSqlType' カスタム MapSqlParameterSource インスタンスの呼び出し、または NULL 値の場合でも Java で宣言されたプロパティタイプから SQL タイプを派生する 'BeanPropertySqlParameterSource' を介した ' ベースの呼び出し。

3.5.3. 複数のバッチを使用したバッチ操作

前述のバッチ更新の例では、非常に大きいバッチを処理するため、いくつかの小さなバッチに分割します。batchUpdate メソッドを複数回呼び出すことで、前述のメソッドでこれを行うことができますが、より便利なメソッドがあります。このメソッドは、SQL ステートメントに加えて、パラメーターを含むオブジェクトの Collection、各バッチに対して行う更新の数、および準備済みステートメントのパラメーターの値を設定する ParameterizedPreparedStatementSetter を取ります。フレームワークは提供された値をループし、更新呼び出しを指定されたサイズのバッチに分割します。

次の例は、バッチサイズ 100 を使用するバッチ更新を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[][] batchUpdate(final Collection<Actor> actors) {
        int[][] updateCounts = jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                actors,
                100,
                (PreparedStatement ps, Actor actor) -> {
                    ps.setString(1, actor.getFirstName());
                    ps.setString(2, actor.getLastName());
                    ps.setLong(3, actor.getId().longValue());
                });
        return updateCounts;
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val jdbcTemplate = JdbcTemplate(dataSource)

    fun batchUpdate(actors: List<Actor>): Array<IntArray> {
        return jdbcTemplate.batchUpdate(
                    "update t_actor set first_name = ?, last_name = ? where id = ?",
                    actors, 100) { ps, argument ->
            ps.setString(1, argument.firstName)
            ps.setString(2, argument.lastName)
            ps.setLong(3, argument.id)
        }
    }

    // ... additional methods
}

この呼び出しのバッチ更新メソッドは、各更新の影響を受ける行数の配列を含む各バッチの配列エントリを含む int 配列の配列を返します。最上位の配列の長さは実行されるバッチの数を示し、第 2 レベルの配列の長さはそのバッチの更新の数を示します。各バッチの更新の数は、提供される更新オブジェクトの総数に応じて、すべてのバッチに提供されるバッチサイズ(最後のバッチがより少ない場合がある場合を除く)である必要があります。各更新ステートメントの更新カウントは、JDBC ドライバーによって報告されるものです。カウントが使用できない場合、JDBC ドライバーは -2 の値を返します。

3.6. SimpleJdbc クラスを使用した JDBC 操作の簡素化

SimpleJdbcInsert および SimpleJdbcCall クラスは、JDBC ドライバーを介して取得できるデータベースメタデータを利用することにより、簡素化された構成を提供します。これは、コードのすべての詳細を提供することを希望する場合、メタデータ処理をオーバーライドまたはオフにできますが、事前に構成する必要が少ないことを意味します。

3.6.1. SimpleJdbcInsert を使用したデータの挿入

まず、最小限の構成オプションで SimpleJdbcInsert クラスを調べることから始めます。データアクセスレイヤーの初期化メソッドで SimpleJdbcInsert をインスタンス化する必要があります。この例では、初期化メソッドは setDataSource メソッドです。SimpleJdbcInsert クラスをサブクラス化する必要はありません。代わりに、withTableName メソッドを使用して、新しいインスタンスを作成し、テーブル名を設定できます。このクラスの構成メソッドは、SimpleJdbcInsert のインスタンスを返す fluid スタイルに従います。これにより、すべての構成メソッドをチェーンにできます。次の例では、1 つの構成方法のみを使用します(複数の方法の例を後で示します)。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(3);
        parameters.put("id", actor.getId());
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        insertActor.execute(parameters);
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")

    fun add(actor: Actor) {
        val parameters = mutableMapOf<String, Any>()
        parameters["id"] = actor.id
        parameters["first_name"] = actor.firstName
        parameters["last_name"] = actor.lastName
        insertActor.execute(parameters)
    }

    // ... additional methods
}

ここで使用される execute メソッドは、その唯一のパラメーターとしてプレーンな java.util.Map を取ります。ここで重要なことは、Map に使用されるキーは、データベースで定義されているテーブルの列名と一致する必要があるということです。これは、メタデータを読み取って実際の挿入ステートメントを作成するためです。

3.6.2. SimpleJdbcInsert を使用して自動生成されたキーを取得する

次の例では、前の例と同じ挿入を使用しますが、id を渡す代わりに、自動生成キーを取得して、新しい Actor オブジェクトに設定します。SimpleJdbcInsert を作成するとき、テーブル名の指定に加えて、usingGeneratedKeyColumns メソッドで生成されたキー列の名前を指定します。次のリストは、その仕組みを示しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor").usingGeneratedKeyColumns("id")

    fun add(actor: Actor): Actor {
        val parameters = mapOf(
                "first_name" to actor.firstName,
                "last_name" to actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters);
        return actor.copy(id = newId.toLong())
    }

    // ... additional methods
}

この 2 番目のアプローチを使用して挿入を実行する場合の主な違いは、id を Map に追加せず、executeAndReturnKey メソッドを呼び出すことです。これは、ドメインクラスで使用される数値型のインスタンスを作成できる java.lang.Number オブジェクトを返します。ここで特定の Java クラスを返すためにすべてのデータベースに依存することはできません。java.lang.Number は、信頼できる基本クラスです。複数の自動生成列がある場合、または生成された値が非数値の場合、executeAndReturnKeyHolder メソッドから返される KeyHolder を使用できます。

3.6.3. SimpleJdbcInsert の列の指定

次の例に示すように、usingColumns メソッドを使用して列名のリストを指定することにより、挿入の列を制限できます。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingColumns("first_name", "last_name")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingColumns("first_name", "last_name")
            .usingGeneratedKeyColumns("id")

    fun add(actor: Actor): Actor {
        val parameters = mapOf(
                "first_name" to actor.firstName,
                "last_name" to actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters);
        return actor.copy(id = newId.toLong())
    }

    // ... additional methods
}

挿入の実行は、使用する列を決定するためにメタデータに依存していた場合と同じです。

3.6.4. SqlParameterSource を使用してパラメーター値を提供する

Map を使用してパラメーター値を提供することは正常に機能しますが、使用するのに最も便利なクラスではありません。Spring は、代わりに使用できる SqlParameterSource インターフェースの実装をいくつか提供します。最初のものは BeanPropertySqlParameterSource です。これは、値を含む JavaBean 準拠のクラスがある場合に非常に便利なクラスです。対応する getter メソッドを使用して、パラメーター値を抽出します。次の例は、BeanPropertySqlParameterSource の使用方法を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingGeneratedKeyColumns("id")

    fun add(actor: Actor): Actor {
        val parameters = BeanPropertySqlParameterSource(actor)
        val newId = insertActor.executeAndReturnKey(parameters)
        return actor.copy(id = newId.toLong())
    }

    // ... additional methods
}

別のオプションは、Map に似ていますが、連鎖できるより便利な addValue メソッドを提供する MapSqlParameterSource です。次の例は、その使用方法を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        SqlParameterSource parameters = new MapSqlParameterSource()
                .addValue("first_name", actor.getFirstName())
                .addValue("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val insertActor = SimpleJdbcInsert(dataSource)
            .withTableName("t_actor")
            .usingGeneratedKeyColumns("id")

    fun add(actor: Actor): Actor {
        val parameters = MapSqlParameterSource()
                    .addValue("first_name", actor.firstName)
                    .addValue("last_name", actor.lastName)
        val newId = insertActor.executeAndReturnKey(parameters)
        return actor.copy(id = newId.toLong())
    }

    // ... additional methods
}

ご覧のとおり、構成は同じです。これらの代替入力クラスを使用するには、実行中のコードのみを変更する必要があります。

3.6.5. SimpleJdbcCall を使用したストアドプロシージャの呼び出し

SimpleJdbcCall クラスはデータベース内のメタデータを使用して in および out パラメーターの名前を検索するため、これらのパラメーターを明示的に宣言する必要はありません。必要に応じて、または Java クラスへの自動マッピングを持たないパラメーター ( ARRAY または STRUCT など) がある場合は、パラメーターを宣言できます。最初の例は、MySQL データベースから VARCHAR および DATE 形式のスカラー値のみを返す簡単なプロシージャを示しています。このサンプルプロシージャーは、指定されたアクターエントリを読み取り、first_namelast_name および birth_date 列を out パラメーターの形式で戻します。最初の例を次に示します。

CREATE PROCEDURE read_actor (
    IN in_id INTEGER,
    OUT out_first_name VARCHAR(100),
    OUT out_last_name VARCHAR(100),
    OUT out_birth_date DATE)
BEGIN
    SELECT first_name, last_name, birth_date
    INTO out_first_name, out_last_name, out_birth_date
    FROM t_actor where id = in_id;
END;

in_id パラメーターには、検索しているアクターの id が含まれています。out パラメーターは、テーブルから読み取ったデータを返します。

SimpleJdbcInsert の宣言と同様の方法で SimpleJdbcCall を宣言できます。データアクセスレイヤーの初期化メソッドでクラスをインスタンス化して構成する必要があります。StoredProcedure クラスと比較して、サブクラスを作成する必要はなく、データベースメタデータで検索できるパラメーターを宣言する必要もありません。次の SimpleJdbcCall 構成の例では、前述のストアドプロシージャを使用しています(DataSource に加えて唯一の構成オプションは、ストアドプロシージャの名前です)。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        this.procReadActor = new SimpleJdbcCall(dataSource)
                .withProcedureName("read_actor");
    }

    public Actor readActor(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        Map out = procReadActor.execute(in);
        Actor actor = new Actor();
        actor.setId(id);
        actor.setFirstName((String) out.get("out_first_name"));
        actor.setLastName((String) out.get("out_last_name"));
        actor.setBirthDate((Date) out.get("out_birth_date"));
        return actor;
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val procReadActor = SimpleJdbcCall(dataSource)
            .withProcedureName("read_actor")


    fun readActor(id: Long): Actor {
        val source = MapSqlParameterSource().addValue("in_id", id)
        val output = procReadActor.execute(source)
        return Actor(
                id,
                output["out_first_name"] as String,
                output["out_last_name"] as String,
                output["out_birth_date"] as Date)
    }

        // ... additional methods
}

呼び出しの実行用に記述するコードには、IN パラメーターを含む SqlParameterSource の作成が含まれます。入力値に指定された名前を、ストアドプロシージャで宣言されたパラメーター名と一致させる必要があります。メタデータを使用して、ストアドプロシージャでデータベースオブジェクトを参照する方法を決定するため、ケースを一致させる必要はありません。ストアドプロシージャのソースで指定されていることは、必ずしもデータベースに格納されている方法とは限りません。名前をすべて大文字に変換するデータベースもあれば、小文字を使用するか、指定されたとおりに大文字を使用するデータベースもあります。

execute メソッドは、IN パラメーターを受け取り、ストアドプロシージャで指定された名前でキー設定された out パラメーターを含む Map を返します。この場合、それらは out_first_nameout_last_name および out_birth_date です。

execute メソッドの最後の部分では、取得したデータを返すために使用する Actor インスタンスを作成します。ここでも、out パラメーターの名前をストアドプロシージャで宣言されているとおりに使用することが重要です。また、結果マップに格納された out パラメーターの名前の大文字小文字は、データベース内の out パラメーター名の大文字小文字と一致しますが、これはデータベース間で異なる場合があります。コードの移植性を高めるには、大文字小文字を区別しないルックアップを行うか、Spring に LinkedCaseInsensitiveMap を使用するように指示する必要があります。後者を実行するには、独自の JdbcTemplate を作成し、setResultsMapCaseInsensitive プロパティを true に設定します。その後、このカスタマイズされた JdbcTemplate インスタンスを SimpleJdbcCall のコンストラクターに渡すことができます。次の例は、この設定を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor");
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
        isResultsMapCaseInsensitive = true
    }).withProcedureName("read_actor")

    // ... additional methods
}

このアクションを実行することにより、返される out パラメーターの名前に使用される大文字小文字の競合を回避できます。

3.6.6. SimpleJdbcCall に使用するパラメーターを明示的に宣言する

この章の前半で、パラメーターがメタデータからどのように推測されるかを説明しましたが、必要に応じて明示的に宣言できます。これを行うには、declareParameters メソッドを使用して SimpleJdbcCall を作成および構成します。declareParameters メソッドは、可変数の SqlParameter オブジェクトを入力として受け取ります。SqlParameter を定義する方法の詳細については、次のセクションを参照してください。

使用するデータベースが Spring でサポートされているデータベースでない場合は、明示的な宣言が必要です。現在、Spring は、Apache Derby、DB2、MySQL、Microsoft SQL Server、Oracle、Sybase のデータベースのストアドプロシージャコールのメタデータ検索をサポートしています。MySQL、Microsoft SQL Server、Oracle のストアド関数のメタデータ検索もサポートしています。

1 つ、いくつか、またはすべてのパラメーターを明示的に宣言することを選択できます。パラメーターメタデータは、パラメーターを明示的に宣言しない場合でも引き続き使用されます。潜在的なパラメーターのメタデータ検索のすべての処理をバイパスし、宣言されたパラメーターのみを使用するには、宣言の一部としてメソッド withoutProcedureColumnMetaDataAccess を呼び出すことができます。データベース関数に対して 2 つ以上の異なる呼び出しシグネチャーが宣言されているとします。この場合、useInParameterNames を呼び出して、特定の署名に含める IN パラメーター名のリストを指定します。

次の例は、完全に宣言されたプロシージャ呼び出しを示し、前述の例の情報を使用しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor")
                .withoutProcedureColumnMetaDataAccess()
                .useInParameterNames("in_id")
                .declareParameters(
                        new SqlParameter("in_id", Types.NUMERIC),
                        new SqlOutParameter("out_first_name", Types.VARCHAR),
                        new SqlOutParameter("out_last_name", Types.VARCHAR),
                        new SqlOutParameter("out_birth_date", Types.DATE)
                );
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

        private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
            isResultsMapCaseInsensitive = true
        }).withProcedureName("read_actor")
                .withoutProcedureColumnMetaDataAccess()
                .useInParameterNames("in_id")
                .declareParameters(
                        SqlParameter("in_id", Types.NUMERIC),
                        SqlOutParameter("out_first_name", Types.VARCHAR),
                        SqlOutParameter("out_last_name", Types.VARCHAR),
                        SqlOutParameter("out_birth_date", Types.DATE)
    )

        // ... additional methods
}

2 つの例の実行結果と最終結果は同じです。2 番目の例では、メタデータに依存するのではなく、すべての詳細を明示的に指定します。

3.6.7. SqlParameters を定義する方法

SimpleJdbc クラスおよび RDBMS 操作クラス(JDBC 操作を Java オブジェクトとしてモデル化するでカバー)のパラメーターを定義するには、SqlParameter またはそのサブクラスの 1 つを使用できます。そのためには、通常、コンストラクターでパラメーター名と SQL タイプを指定します。SQL タイプは、java.sql.Types 定数を使用して指定されます。この章の前半で、次のような宣言を見ました。

Java
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
Kotlin
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),

SqlParameter のある最初の行は、IN パラメーターを宣言しています。SqlQuery とそのサブクラス(SqlQuery を理解するでカバー)を使用して、ストアドプロシージャコールとクエリの両方に IN パラメーターを使用できます。

2 行目(SqlOutParameter を使用)は、ストアドプロシージャコールで使用される out パラメーターを宣言しています。InOut パラメーター(プロシージャーに IN 値を提供し、値も返すパラメーター)用の SqlInOutParameter もあります。

SqlParameter および SqlInOutParameter として宣言されたパラメーターのみが、入力値を提供するために使用されます。これは、StoredProcedure クラスとは異なります。StoredProcedure クラスは(後方互換性の理由で) SqlOutParameter として宣言されたパラメーターに入力値を提供します。

IN パラメーターの場合、名前と SQL タイプに加えて、数値データのスケールまたはカスタムデータベースタイプのタイプ名を指定できます。out パラメーターの場合、RowMapper を提供して、REF カーソルから返された行のマッピングを処理できます。もう 1 つのオプションは、戻り値のカスタマイズされた処理を定義する機会を提供する SqlReturnType を指定することです。

3.6.8. SimpleJdbcCall を使用してストアド関数を呼び出す

ストアドプロシージャは、プロシージャ名ではなく関数名を指定することを除いて、ストアドプロシージャを呼び出す場合とほぼ同じメソッドで呼び出すことができます。withFunctionName メソッドを構成の一部として使用して、関数を呼び出すことを示し、関数呼び出しに対応する文字列が生成されます。特殊な呼び出し(executeFunction)を使用して関数を実行し、関数の戻り値を指定したタイプのオブジェクトとして返します。つまり、結果マップから戻り値を取得する必要はありません。同様の便利なメソッド(executeObject という名前)は、out パラメーターが 1 つだけのストアドプロシージャでも使用できます。次の例(MySQL の場合)は、アクターのフルネームを返す get_actor_name という名前のストアド関数に基づいています。

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
    DECLARE out_name VARCHAR(200);
    SELECT concat(first_name, ' ', last_name)
        INTO out_name
        FROM t_actor where id = in_id;
    RETURN out_name;
END;

この関数を呼び出すには、次の例に示すように、初期化メソッドで再度 SimpleJdbcCall を作成します。

Java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall funcGetActorName;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
                .withFunctionName("get_actor_name");
    }

    public String getActorName(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        String name = funcGetActorName.executeFunction(String.class, in);
        return name;
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

    private val jdbcTemplate = JdbcTemplate(dataSource).apply {
        isResultsMapCaseInsensitive = true
    }
    private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
            .withFunctionName("get_actor_name")

    fun getActorName(id: Long): String {
        val source = MapSqlParameterSource().addValue("in_id", id)
        return funcGetActorName.executeFunction(String::class.java, source)
    }

    // ... additional methods
}

使用される executeFunction メソッドは、関数呼び出しからの戻り値を含む String を返します。

3.6.9. SimpleJdbcCall から ResultSet または REF カーソルを返す

結果セットを返すストアドプロシージャまたは関数の呼び出しは、少し注意が必要です。JDBC 結果処理中に結果セットを返すデータベースもあれば、特定のタイプの明示的に登録された out パラメーターを必要とするデータベースもあります。どちらのアプローチでも、結果セットをループして返された行を処理するために追加の処理が必要です。SimpleJdbcCall では、returningResultSet メソッドを使用して、特定のパラメーターに使用する RowMapper 実装を宣言できます。結果処理中に結果セットが返される場合、名前は定義されていないため、返される結果は、RowMapper 実装を宣言する順序と一致する必要があります。指定された名前は、execute ステートメントから返される結果マップに結果の処理済みリストを保存するために引き続き使用されます。

次の例(MySQL の場合)では、IN パラメーターを受け取らず、t_actor テーブルからすべての行を返すストアドプロシージャを使用しています。

CREATE PROCEDURE read_all_actors()
BEGIN
 SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

このプロシージャを呼び出すには、RowMapper を宣言できます。マッピングするクラスは JavaBean ルールに従うため、newInstance メソッドでマッピングするために必要なクラスを渡すことにより作成される BeanPropertyRowMapper を使用できます。次の例は、その方法を示しています。

Java
public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadAllActors;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_all_actors")
                .returningResultSet("actors",
                BeanPropertyRowMapper.newInstance(Actor.class));
    }

    public List getActorsList() {
        Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
        return (List) m.get("actors");
    }

    // ... additional methods
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {

        private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
            isResultsMapCaseInsensitive = true
        }).withProcedureName("read_all_actors")
                .returningResultSet("actors",
                        BeanPropertyRowMapper.newInstance(Actor::class.java))

    fun getActorsList(): List<Actor> {
        val m = procReadAllActors.execute(mapOf<String, Any>())
        return m["actors"] as List<Actor>
    }

    // ... additional methods
}

execute 呼び出しは、空の Map を渡します。これは、この呼び出しがパラメーターを受け取らないためです。次に、アクターのリストが結果マップから取得され、呼び出し元に返されます。

3.7. JDBC 操作を Java オブジェクトとしてモデル化する

org.springframework.jdbc.object パッケージには、よりオブジェクト指向の方法でデータベースにアクセスできるクラスが含まれています。例として、クエリを実行して、ビジネスオブジェクトのプロパティにマッピングされたリレーショナル列データを含むビジネスオブジェクトを含むリストとして結果を取得できます。ストアドプロシージャを実行し、更新、削除、挿入ステートメントを実行することもできます。

多くの Spring 開発者は、以下で説明するさまざまな RDBMS 操作クラス(StoredProcedure クラスを除く)は、多くの場合、ストレート JdbcTemplate 呼び出しで置き換えることができると考えています。多くの場合、JdbcTemplate のメソッドを直接呼び出す DAO メソッドを記述する方が簡単です(クエリを本格的なクラスとしてカプセル化するのとは対照的です)。

ただし、RDBMS 操作クラスを使用して測定可能な値を取得している場合は、これらのクラスを引き続き使用する必要があります。

3.7.1. SqlQuery を理解する

SqlQuery は、SQL クエリをカプセル化する再利用可能なスレッドセーフクラスです。サブクラスは、newRowMapper(..) メソッドを実装して、クエリの実行中に作成された ResultSet を反復処理して取得した行ごとに 1 つのオブジェクトを作成できる RowMapper インスタンスを提供する必要があります。MappingSqlQuery サブクラスは、行を Java クラスにマッピングするためのはるかに便利な実装を提供するため、SqlQuery クラスが直接使用されることはほとんどありません。SqlQuery を継承する他の実装は、MappingSqlQueryWithParameters および UpdatableSqlQuery です。

3.7.2. MappingSqlQuery を使用する

MappingSqlQuery は再利用可能なクエリで、具象サブクラスは抽象 mapRow(..) メソッドを実装して、指定された ResultSet の各行を指定されたタイプのオブジェクトに変換する必要があります。次の例は、t_actor リレーションのデータを Actor クラスのインスタンスにマッピングするカスタムクエリを示しています。

Java
public class ActorMappingQuery extends MappingSqlQuery<Actor> {

    public ActorMappingQuery(DataSource ds) {
        super(ds, "select id, first_name, last_name from t_actor where id = ?");
        declareParameter(new SqlParameter("id", Types.INTEGER));
        compile();
    }

    @Override
    protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
        Actor actor = new Actor();
        actor.setId(rs.getLong("id"));
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }
}
Kotlin
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {

    init {
        declareParameter(SqlParameter("id", Types.INTEGER))
        compile()
    }

    override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
            rs.getLong("id"),
            rs.getString("first_name"),
            rs.getString("last_name")
    )
}

このクラスは、Actor タイプでパラメーター化された MappingSqlQuery を継承します。この顧客クエリのコンストラクターは、DataSource を唯一のパラメーターとして受け取ります。このコンストラクターでは、DataSource と、このクエリの行を取得するために実行する必要がある SQL を使用して、スーパークラスのコンストラクターを呼び出すことができます。この SQL は PreparedStatement を作成するために使用されるため、実行中に渡されるパラメーターのプレースホルダーを含めることができます。SqlParameter に渡す declareParameter メソッドを使用して、各パラメーターを宣言する必要があります。SqlParameter は、java.sql.Types で定義されている名前と JDBC タイプを受け取ります。すべてのパラメーターを定義した後、ステートメントを準備して後で実行できるように、compile() メソッドを呼び出すことができます。このクラスはコンパイル後にスレッドセーフであるため、DAO の初期化時にこれらのインスタンスが作成される限り、インスタンス変数として保持して再利用できます。次の例は、そのようなクラスを定義する方法を示しています。

Java
private ActorMappingQuery actorMappingQuery;

@Autowired
public void setDataSource(DataSource dataSource) {
    this.actorMappingQuery = new ActorMappingQuery(dataSource);
}

public Customer getCustomer(Long id) {
    return actorMappingQuery.findObject(id);
}
Kotlin
private val actorMappingQuery = ActorMappingQuery(dataSource)

fun getCustomer(id: Long) = actorMappingQuery.findObject(id)

上記の例のメソッドは、唯一のパラメーターとして渡される id を持つ顧客を取得します。返されるオブジェクトは 1 つだけなので、id をパラメーターとして使用して findObject コンビニエンスメソッドを呼び出します。オブジェクトのリストを返し、追加のパラメーターを取得するクエリが代わりにあった場合、可変引数として渡されたパラメーター値の配列を取得する execute メソッドの 1 つを使用します。次の例は、このようなメソッドを示しています。

Java
public List<Actor> searchForActors(int age, String namePattern) {
    List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
    return actors;
}
Kotlin
fun searchForActors(age: Int, namePattern: String) =
            actorSearchMappingQuery.execute(age, namePattern)

3.7.3. SqlUpdate を使用する

SqlUpdate クラスは、SQL 更新をカプセル化します。クエリと同様に、更新オブジェクトは再利用可能であり、すべての RdbmsOperation クラスと同様に、更新にはパラメーターを含めることができ、SQL で定義されます。このクラスは、クエリオブジェクトの execute(..) メソッドに類似した多くの update(..) メソッドを提供します。SQLUpdate クラスは具体的です。たとえば、カスタム更新メソッドを追加するために、サブクラス化できます。ただし、SqlUpdate クラスをサブクラス化する必要はありません。これは、SQL の設定とパラメーターの宣言によって簡単にパラメーター化できるためです。次の例では、execute という名前のカスタム更新メソッドを作成します。

Java
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating extends SqlUpdate {

    public UpdateCreditRating(DataSource ds) {
        setDataSource(ds);
        setSql("update customer set credit_rating = ? where id = ?");
        declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
        declareParameter(new SqlParameter("id", Types.NUMERIC));
        compile();
    }

    /**
     * @param id for the Customer to be updated
     * @param rating the new value for credit rating
     * @return number of rows updated
     */
    public int execute(int id, int rating) {
        return update(rating, id);
    }
}
Kotlin
import java.sql.Types
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object.SqlUpdate

class UpdateCreditRating(ds: DataSource) : SqlUpdate() {

    init {
        setDataSource(ds)
        sql = "update customer set credit_rating = ? where id = ?"
        declareParameter(SqlParameter("creditRating", Types.NUMERIC))
        declareParameter(SqlParameter("id", Types.NUMERIC))
        compile()
    }

    /**
    * @param id for the Customer to be updated
    * @param rating the new value for credit rating
    * @return number of rows updated
    */
    fun execute(id: Int, rating: Int): Int {
        return update(rating, id)
    }
}

3.7.4. StoredProcedure を使用する

StoredProcedure クラスは、RDBMS ストアドプロシージャのオブジェクト抽象化のためのスーパークラスです。このクラスは abstract であり、そのさまざまな execute(..) メソッドには protected アクセスがあり、より厳密なタイピングを提供するサブクラス以外の使用を防ぎます。

継承された sql プロパティは、RDBMS のストアドプロシージャの名前です。

StoredProcedure クラスのパラメーターを定義するには、SqlParameter またはそのサブクラスの 1 つを使用できます。次のコードスニペットに示すように、コンストラクターでパラメーター名と SQL タイプを指定する必要があります。

Java
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
Kotlin
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),

SQL タイプは、java.sql.Types 定数を使用して指定されます。

最初の行(SqlParameter を使用)は、IN パラメーターを宣言しています。IN パラメーターは、ストアドプロシージャコールと、SqlQuery およびそのサブクラス(SqlQuery を理解するでカバー)を使用したクエリの両方に使用できます。

2 行目(SqlOutParameter を使用)は、ストアドプロシージャコールで使用される out パラメーターを宣言しています。InOut パラメーター(プロシージャに in 値を提供し、値を返すパラメーター)の SqlInOutParameter もあります。

in パラメーターの場合、名前と SQL タイプに加えて、数値データのスケールまたはカスタムデータベースタイプのタイプ名を指定できます。out パラメーターの場合、RowMapper を提供して、REF カーソルから返された行のマッピングを処理できます。別のオプションは、戻り値のカスタマイズされた処理を定義できる SqlReturnType を指定することです。

次の単純な DAO の例では、StoredProcedure を使用して、Oracle データベースに付属している関数(sysdate())を呼び出します。ストアドプロシージャ機能を使用するには、StoredProcedure を継承するクラスを作成する必要があります。この例では、StoredProcedure クラスは内部クラスです。ただし、StoredProcedure を再利用する必要がある場合は、最上位クラスとして宣言できます。この例には入力パラメーターはありませんが、SqlOutParameter クラスを使用して出力パラメーターが日付型として宣言されています。execute() メソッドはプロシージャを実行し、結果 Map から返された日付を抽出します。結果 Map には、パラメーター名をキーとして使用して、宣言された各出力パラメーター(この場合は 1 つのみ)のエントリがあります。次のリストは、カスタム StoredProcedure クラスを示しています。

Java
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class StoredProcedureDao {

    private GetSysdateProcedure getSysdate;

    @Autowired
    public void init(DataSource dataSource) {
        this.getSysdate = new GetSysdateProcedure(dataSource);
    }

    public Date getSysdate() {
        return getSysdate.execute();
    }

    private class GetSysdateProcedure extends StoredProcedure {

        private static final String SQL = "sysdate";

        public GetSysdateProcedure(DataSource dataSource) {
            setDataSource(dataSource);
            setFunction(true);
            setSql(SQL);
            declareParameter(new SqlOutParameter("date", Types.DATE));
            compile();
        }

        public Date execute() {
            // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
            Map<String, Object> results = execute(new HashMap<String, Object>());
            Date sysdate = (Date) results.get("date");
            return sysdate;
        }
    }

}
Kotlin
import java.sql.Types
import java.util.Date
import java.util.Map
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure

class StoredProcedureDao(dataSource: DataSource) {

    private val SQL = "sysdate"

    private val getSysdate = GetSysdateProcedure(dataSource)

    val sysdate: Date
        get() = getSysdate.execute()

    private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {

        init {
            setDataSource(dataSource)
            isFunction = true
            sql = SQL
            declareParameter(SqlOutParameter("date", Types.DATE))
            compile()
        }

        fun execute(): Date {
            // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
            val results = execute(mutableMapOf<String, Any>())
            return results["date"] as Date
        }
    }
}

次の StoredProcedure の例には、2 つの出力パラメーターがあります(この場合、Oracle REF カーソル)。

Java
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAndGenresStoredProcedure extends StoredProcedure {

    private static final String SPROC_NAME = "AllTitlesAndGenres";

    public TitlesAndGenresStoredProcedure(DataSource dataSource) {
        super(dataSource, SPROC_NAME);
        declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
        declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
        compile();
    }

    public Map<String, Object> execute() {
        // again, this sproc has no input parameters, so an empty Map is supplied
        return super.execute(new HashMap<String, Object>());
    }
}
Kotlin
import java.util.HashMap
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure

class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {

    companion object {
        private const val SPROC_NAME = "AllTitlesAndGenres"
    }

    init {
        declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
        declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
        compile()
    }

    fun execute(): Map<String, Any> {
        // again, this sproc has no input parameters, so an empty Map is supplied
        return super.execute(HashMap<String, Any>())
    }
}

TitlesAndGenresStoredProcedure コンストラクターで使用されていた declareParameter(..) メソッドのオーバーロードされたバリアントが RowMapper 実装インスタンスに渡される方法に注意してください。これは、既存の機能を再利用するための非常に便利で強力な方法です。次の 2 つの例は、2 つの RowMapper 実装のコードを提供します。

TitleMapper クラスは、次のように、指定された ResultSet の各行の ResultSet を Title ドメインオブジェクトにマップします。

Java
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;

public final class TitleMapper implements RowMapper<Title> {

    public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
        Title title = new Title();
        title.setId(rs.getLong("id"));
        title.setName(rs.getString("name"));
        return title;
    }
}
Kotlin
import java.sql.ResultSet
import com.foo.domain.Title
import org.springframework.jdbc.core.RowMapper

class TitleMapper : RowMapper<Title> {

    override fun mapRow(rs: ResultSet, rowNum: Int) =
            Title(rs.getLong("id"), rs.getString("name"))
}

GenreMapper クラスは、次のように、指定された ResultSet の各行の ResultSet を Genre ドメインオブジェクトにマップします。

Java
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;

public final class GenreMapper implements RowMapper<Genre> {

    public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new Genre(rs.getString("name"));
    }
}
Kotlin
import java.sql.ResultSet
import com.foo.domain.Genre
import org.springframework.jdbc.core.RowMapper

class GenreMapper : RowMapper<Genre> {

    override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
        return Genre(rs.getString("name"))
    }
}

RDBMS の定義に 1 つ以上の入力パラメーターがあるストアドプロシージャにパラメーターを渡すには、次の例に示すように、スーパークラスの型なし execute(Map) メソッドに委譲する厳密に型指定された execute(..) メソッドをコーディングできます。

Java
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAfterDateStoredProcedure extends StoredProcedure {

    private static final String SPROC_NAME = "TitlesAfterDate";
    private static final String CUTOFF_DATE_PARAM = "cutoffDate";

    public TitlesAfterDateStoredProcedure(DataSource dataSource) {
        super(dataSource, SPROC_NAME);
        declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
        declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
        compile();
    }

    public Map<String, Object> execute(Date cutoffDate) {
        Map<String, Object> inputs = new HashMap<String, Object>();
        inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
        return super.execute(inputs);
    }
}
Kotlin
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object.StoredProcedure

class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {

    companion object {
        private const val SPROC_NAME = "TitlesAfterDate"
        private const val CUTOFF_DATE_PARAM = "cutoffDate"
    }

    init {
        declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
        declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
        compile()
    }

    fun execute(cutoffDate: Date) = super.execute(
            mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
}

3.8. パラメーターとデータ値の処理に関する一般的な問題

Spring Framework の JDBC サポートによって提供されるさまざまなアプローチには、パラメーターとデータ値に関する一般的な問題が存在します。このセクションでは、それらに対処する方法について説明します。

3.8.1. パラメーターの SQL 型情報の提供

通常、Spring は、渡されたパラメーターのタイプに基づいてパラメーターの SQL タイプを決定します。パラメーター値の設定時に使用される SQL タイプを明示的に提供することができます。これは、NULL 値を正しく設定するために必要な場合があります。

SQL 型情報は、いくつかの方法で提供できます。

  • JdbcTemplate の多くの更新およびクエリメソッドは、int 配列の形式で追加のパラメーターを取ります。この配列は、java.sql.Types クラスの定数値を使用して、対応するパラメーターの SQL タイプを示すために使用されます。パラメーターごとに 1 つのエントリを提供します。

  • SqlParameterValue クラスを使用して、この追加情報を必要とするパラメーター値をラップできます。そのためには、値ごとに新しいインスタンスを作成し、コンストラクターで SQL 型とパラメーター値を渡します。数値にオプションのスケールパラメーターを指定することもできます。

  • 名前付きパラメーターで機能するメソッドの場合、SqlParameterSource クラス、BeanPropertySqlParameterSource または MapSqlParameterSource を使用できます。どちらにも、名前付きパラメーター値のいずれかに SQL タイプを登録するためのメソッドがあります。

3.8.2. BLOB および CLOB オブジェクトの処理

イメージ、その他のバイナリデータ、および大量のテキストをデータベースに保存できます。これらのラージオブジェクトは、バイナリデータでは BLOB(バイナリラージ OBject)、文字データでは CLOB(キャラクターラージ OBject)と呼ばれます。Spring では、JdbcTemplate を直接使用することにより、また RDBMS オブジェクトおよび SimpleJdbc クラスにより提供されるより高い抽象化を使用することにより、これらの大きなオブジェクトを処理できます。これらのアプローチはすべて、LOB(Large OBject)データの実際の管理に LobHandler インターフェースの実装を使用します。LobHandler は、getLobCreator メソッドを介して LobCreator クラスへのアクセスを提供します。このメソッドは、挿入される新しい LOB オブジェクトの作成に使用されます。

LobCreator および LobHandler は、LOB 入力および出力に対して以下のサポートを提供します。

  • BLOB

    • byte[]getBlobAsBytes および setBlobAsBytes

    • InputStreamgetBlobAsBinaryStream および setBlobAsBinaryStream

  • CLOB

    • StringgetClobAsString および setClobAsString

    • InputStreamgetClobAsAsciiStream および setClobAsAsciiStream

    • ReadergetClobAsCharacterStream および setClobAsCharacterStream

次の例は、BLOB を作成および挿入する方法を示しています。後で、データベースからそれを読み戻す方法を示します。

この例では、JdbcTemplate と AbstractLobCreatingPreparedStatementCallback の実装を使用しています。setValues という 1 つのメソッドを実装します。このメソッドは、SQL insert ステートメントの LOB 列の値を設定するために使用する LobCreator を提供します。

この例では、DefaultLobHandler のインスタンスに既に設定されている変数 lobHandler があると想定しています。通常、この値は依存性注入を通じて設定します。

次の例は、BLOB を作成および挿入する方法を示しています。

Java
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
    "INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
    new AbstractLobCreatingPreparedStatementCallback(lobHandler) {  (1)
        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
            ps.setLong(1, 1L);
            lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());  (2)
            lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());  (3)
        }
    }
);

blobIs.close();
clobReader.close();
1lobHandler を渡します(この例では)プレーン DefaultLobHandler です。
2 メソッド setClobAsCharacterStream を使用して、CLOB のコンテンツを渡します。
3 メソッド setBlobAsBinaryStream を使用して、BLOB のコンテンツを渡します。
Kotlin
val blobIn = File("spring2004.jpg")
val blobIs = FileInputStream(blobIn)
val clobIn = File("large.txt")
val clobIs = FileInputStream(clobIn)
val clobReader = InputStreamReader(clobIs)

jdbcTemplate.execute(
        "INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
        object: AbstractLobCreatingPreparedStatementCallback(lobHandler) {  (1)
            override fun setValues(ps: PreparedStatement, lobCreator: LobCreator) {
                ps.setLong(1, 1L)
                lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt())  (2)
                lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn.length().toInt())  (3)
            }
        }
)
blobIs.close()
clobReader.close()
1lobHandler を渡します(この例では)プレーン DefaultLobHandler です。
2 メソッド setClobAsCharacterStream を使用して、CLOB のコンテンツを渡します。
3 メソッド setBlobAsBinaryStream を使用して、BLOB のコンテンツを渡します。

DefaultLobHandler.getLobCreator() から返された LobCreator で setBlobAsBinaryStreamsetClobAsAsciiStream または setClobAsCharacterStream メソッドを呼び出す場合、オプションで contentLength 引数に負の値を指定できます。指定されたコンテンツの長さが負の場合、DefaultLobHandler は、長さパラメーターなしで set-stream メソッドの JDBC 4.0 バリアントを使用します。それ以外の場合は、指定された長さをドライバーに渡します。

使用する JDBC ドライバーのドキュメントを参照して、コンテンツの長さを指定せずに LOB のストリーミングをサポートしていることを確認してください。

次に、データベースから LOB データを読み取ります。再び、同じインスタンス変数 lobHandler と DefaultLobHandler への参照を持つ JdbcTemplate を使用します。次の例は、その方法を示しています。

Java
List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
    new RowMapper<Map<String, Object>>() {
        public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
            Map<String, Object> results = new HashMap<String, Object>();
            String clobText = lobHandler.getClobAsString(rs, "a_clob");  (1)
            results.put("CLOB", clobText);
            byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");  (2)
            results.put("BLOB", blobBytes);
            return results;
        }
    });
1 メソッド getClobAsString を使用して、CLOB の内容を取得します。
2 メソッド getBlobAsBytes を使用して、BLOB のコンテンツを取得します。
Kotlin
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ ->
    val clobText = lobHandler.getClobAsString(rs, "a_clob")  (1)
    val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob")  (2)
    mapOf("CLOB" to clobText, "BLOB" to blobBytes)
}
1 メソッド getClobAsString を使用して、CLOB の内容を取得します。
2 メソッド getBlobAsBytes を使用して、BLOB のコンテンツを取得します。

3.8.3. IN 句の値のリストを渡す

標準 SQL では、変数の値リストを含む式に基づいて行を選択できます。典型的な例は select * from T_ACTOR where id in (1, 2, 3) です。この変数リストは、JDBC 標準によって準備済みステートメントに対して直接サポートされていません。可変数のプレースホルダーを宣言することはできません。必要な数のプレースホルダーを用意したさまざまなバリエーションが必要です。または、必要なプレースホルダーの数がわかったら、SQL 文字列を動的に生成する必要があります。NamedParameterJdbcTemplate および JdbcTemplate で提供される名前付きパラメーターサポートは、後者のアプローチを取ります。値をプリミティブオブジェクトの java.util.List として渡すことができます。このリストは、ステートメントの実行中に必要なプレースホルダーを挿入し、値を渡すために使用されます。

多くの値を渡すときは注意してください。JDBC 標準では、in 式リストに 100 を超える値を使用できることを保証していません。さまざまなデータベースがこの数を超えていますが、通常、許可される値の数には厳しい制限があります。例:Oracle の制限は 1000 です。

値リストのプリミティブ値に加えて、オブジェクト配列の java.util.List を作成できます。このリストは、select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'\)) など、in 節に対して定義されている複数の式をサポートできます。もちろん、これにはデータベースがこの構文をサポートしている必要があります。

3.8.4. ストアドプロシージャコールの複合型の処理

ストアドプロシージャを呼び出すときに、データベース固有の複合型を使用できる場合があります。これらのタイプに対応するために、Spring は、ストアドプロシージャコールから返されるときに処理する SqlReturnType と、ストアドプロシージャにパラメーターとして渡されるときに SqlTypeValue を提供します。

SqlReturnType インターフェースには、実装が必要な単一のメソッド(getTypeValue という名前)があります。このインターフェースは、SqlOutParameter の宣言の一部として使用されます。次の例は、ユーザーが宣言したタイプ ITEM_TYPE の Oracle STRUCT オブジェクトの値を返すことを示しています。

Java
public class TestItemStoredProcedure extends StoredProcedure {

    public TestItemStoredProcedure(DataSource dataSource) {
        // ...
        declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
            (CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
                STRUCT struct = (STRUCT) cs.getObject(colIndx);
                Object[] attr = struct.getAttributes();
                TestItem item = new TestItem();
                item.setId(((Number) attr[0]).longValue());
                item.setDescription((String) attr[1]);
                item.setExpirationDate((java.util.Date) attr[2]);
                return item;
            }));
        // ...
    }
Kotlin
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {

    init {
        // ...
        declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
            val struct = cs.getObject(colIndx) as STRUCT
            val attr = struct.getAttributes()
            TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
        })
        // ...
    }
}

SqlTypeValue を使用して、Java オブジェクト(TestItem など)の値をストアドプロシージャに渡すことができます。SqlTypeValue インターフェースには、実装が必要な単一のメソッド(createTypeValue という名前)があります。アクティブな接続が渡され、それを使用して、StructDescriptor インスタンスや ArrayDescriptor インスタンスなどのデータベース固有のオブジェクトを作成できます。次の例では、StructDescriptor インスタンスを作成します。

Java
final TestItem testItem = new TestItem(123L, "A test item",
        new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
        Struct item = new STRUCT(itemDescriptor, conn,
        new Object[] {
            testItem.getId(),
            testItem.getDescription(),
            new java.sql.Date(testItem.getExpirationDate().getTime())
        });
        return item;
    }
};
Kotlin
val (id, description, expirationDate) = TestItem(123L, "A test item",
        SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))

val value = object : AbstractSqlTypeValue() {
    override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
        val itemDescriptor = StructDescriptor(typeName, conn)
        return STRUCT(itemDescriptor, conn,
                arrayOf(id, description, java.sql.Date(expirationDate.time)))
    }
}

これで、この SqlTypeValue を、ストアドプロシージャの execute 呼び出しの入力パラメーターを含む Map に追加できます。

SqlTypeValue のもう 1 つの用途は、値の配列を Oracle ストアドプロシージャに渡すことです。Oracle にはこの場合に使用する必要のある独自の内部 ARRAY クラスがあり、SqlTypeValue を使用して Oracle ARRAY のインスタンスを作成し、次の例に示すように Java ARRAY から値を取り込むことができます。

Java
final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
        ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
        return idArray;
    }
};
Kotlin
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {

    init {
        val ids = arrayOf(1L, 2L)
        val value = object : AbstractSqlTypeValue() {
            override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
                val arrayDescriptor = ArrayDescriptor(typeName, conn)
                return ARRAY(arrayDescriptor, conn, ids)
            }
        }
    }
}

3.9. 組み込みデータベースのサポート

org.springframework.jdbc.datasource.embedded パッケージは、組み込み Java データベースエンジンのサポートを提供します。HSQL (英語) H2 (英語) Derby: Apache (英語) のサポートはネイティブで提供されます。拡張可能な API を使用して、新しい組み込みデータベースタイプと DataSource 実装をプラグインすることもできます。

3.9.1. 埋め込みデータベースを使用する理由

組み込みデータベースは、軽量であるため、プロジェクトの開発段階で役立ちます。利点には、構成の容易さ、起動時間の短縮、テスト容易性、開発中に SQL を迅速に進化させる機能が含まれます。

3.9.2. Spring XML を使用した埋め込みデータベースの作成

組み込みデータベースインスタンスを Spring ApplicationContext の Bean として公開する場合、spring-jdbc 名前空間で embedded-database タグを使用できます。

<jdbc:embedded-database id="dataSource" generate-name="true">
    <jdbc:script location="classpath:schema.sql"/>
    <jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

上記の構成では、クラスパスのルートにある schema.sql および test-data.sql リソースからの SQL が取り込まれた組み込み HSQL データベースが作成されます。さらに、ベストプラクティスとして、組み込みデータベースには一意に生成された名前が割り当てられます。組み込みデータベースは、Spring コンテナーでタイプ javax.sql.DataSource の Bean として使用可能になり、必要に応じてデータアクセスオブジェクトに挿入できます。

3.9.3. プログラムによる埋め込みデータベースの作成

EmbeddedDatabaseBuilder クラスは、組み込みデータベースをプログラムで構築するための流れるような API を提供します。次の例のように、スタンドアロン環境またはスタンドアロン統合テストで埋め込みデータベースを作成する必要がある場合、これを使用できます。

Java
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
        .generateUniqueName(true)
        .setType(H2)
        .setScriptEncoding("UTF-8")
        .ignoreFailedDrops(true)
        .addScript("schema.sql")
        .addScripts("user_data.sql", "country_data.sql")
        .build();

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()
Kotlin
val db = EmbeddedDatabaseBuilder()
        .generateUniqueName(true)
        .setType(H2)
        .setScriptEncoding("UTF-8")
        .ignoreFailedDrops(true)
        .addScript("schema.sql")
        .addScripts("user_data.sql", "country_data.sql")
        .build()

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()

サポートされているすべてのオプションの詳細については、EmbeddedDatabaseBuilder の javadoc を参照してください。

次の例に示すように、EmbeddedDatabaseBuilder を使用して、Java 構成を使用して組み込みデータベースを作成することもできます。

Java
@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(H2)
                .setScriptEncoding("UTF-8")
                .ignoreFailedDrops(true)
                .addScript("schema.sql")
                .addScripts("user_data.sql", "country_data.sql")
                .build();
    }
}
Kotlin
@Configuration
class DataSourceConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(H2)
                .setScriptEncoding("UTF-8")
                .ignoreFailedDrops(true)
                .addScript("schema.sql")
                .addScripts("user_data.sql", "country_data.sql")
                .build()
    }
}

3.9.4. 組み込みデータベースタイプの選択

このセクションでは、Spring がサポートする 3 つの組み込みデータベースのいずれかを選択する方法について説明します。次のトピックが含まれます。

HSQL を使用する

Spring は、HSQL 1.8.0 以上をサポートしています。HSQL は、タイプが明示的に指定されていない場合のデフォルトの組み込みデータベースです。HSQL を明示的に指定するには、embedded-database タグの type 属性を HSQL に設定します。ビルダー API を使用する場合は、EmbeddedDatabaseType.HSQL を使用して setType(EmbeddedDatabaseType) メソッドを呼び出します。

H2 を使用する

Spring は H2 データベースをサポートしています。H2 を有効にするには、embedded-database タグの type 属性を H2 に設定します。ビルダー API を使用する場合は、EmbeddedDatabaseType.H2 を使用して setType(EmbeddedDatabaseType) メソッドを呼び出します。

Derby を使用する

Spring は、Apache Derby 10.5 以降をサポートしています。Derby を有効にするには、embedded-database タグの type 属性を DERBY に設定します。ビルダー API を使用する場合は、EmbeddedDatabaseType.DERBY を指定して setType(EmbeddedDatabaseType) メソッドを呼び出します。

3.9.5. 組み込みデータベースを使用したデータアクセスロジックのテスト

組み込みデータベースは、データアクセスコードを簡単にテストする方法を提供します。次の例は、組み込みデータベースを使用するデータアクセス統合テストテンプレートです。このようなテンプレートを使用すると、組み込みデータベースをテストクラス全体で再利用する必要がない場合に、1 回限りの使用に役立ちます。ただし、テストスイート内で共有される組み込みデータベースを作成する場合は、Spring TestContext フレームワークを使用し、Spring XML を使用した埋め込みデータベースの作成およびプログラムによる埋め込みデータベースの作成の説明に従って、Spring ApplicationContext で Bean として組み込みデータベースを構成することを検討してください。次のリストは、テストテンプレートを示しています。

Java
public class DataAccessIntegrationTestTemplate {

    private EmbeddedDatabase db;

    @BeforeEach
    public void setUp() {
        // creates an HSQL in-memory database populated from default scripts
        // classpath:schema.sql and classpath:data.sql
        db = new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .addDefaultScripts()
                .build();
    }

    @Test
    public void testDataAccess() {
        JdbcTemplate template = new JdbcTemplate(db);
        template.query( /* ... */ );
    }

    @AfterEach
    public void tearDown() {
        db.shutdown();
    }

}
Kotlin
class DataAccessIntegrationTestTemplate {

    private lateinit var db: EmbeddedDatabase

    @BeforeEach
    fun setUp() {
        // creates an HSQL in-memory database populated from default scripts
        // classpath:schema.sql and classpath:data.sql
        db = EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .addDefaultScripts()
                .build()
    }

    @Test
    fun testDataAccess() {
        val template = JdbcTemplate(db)
        template.query( /* ... */)
    }

    @AfterEach
    fun tearDown() {
        db.shutdown()
    }
}

3.9.6. 組み込みデータベースの一意の名前の生成

開発チームは、テストスイートが誤って同じデータベースの追加インスタンスを再作成しようとすると、組み込みデータベースでエラーが発生することがよくあります。これは、XML 構成ファイルまたは @Configuration クラスが組み込みデータベースの作成を担当し、対応する構成が同じテストスイート内(つまり、同じ JVM プロセス内)の複数のテストシナリオで再利用される場合、非常に簡単に発生します。ApplicationContext 構成がアクティブである Bean 定義プロファイルに関してのみ異なる組み込みデータベースに対する統合テスト。

このようなエラーの根本的な原因は、Spring の EmbeddedDatabaseFactory (<jdbc:embedded-database> XML 名前空間要素と EmbeddedDatabaseBuilder for Java 構成の両方で内部的に使用される)が、特に指定がない限り、組み込みデータベースの名前を testdb に設定することです。<jdbc:embedded-database> の場合、組み込みデータベースには通常、Bean の id と同じ名前(多くの場合、dataSource のようなもの)が割り当てられます。埋め込みデータベースを作成しようとしても、新しいデータベースは作成されません。代わりに、同じ JDBC 接続 URL が再利用され、新しい組み込みデータベースを作成しようとすると、実際には同じ構成から作成された既存の組み込みデータベースを指します。

この一般的な課題に対処するために、Spring Framework 4.2 は組み込みデータベースの一意の名前を生成するためのサポートを提供します。生成された名前の使用を有効にするには、次のいずれかのオプションを使用します。

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()

  • EmbeddedDatabaseBuilder.generateUniqueName()

  • <jdbc:embedded-database generate-name="true" …​ >

3.9.7. 組み込みデータベースサポートの拡張

Spring JDBC 組み込みデータベースのサポートは、次の 2 つの方法で拡張できます。

  • EmbeddedDatabaseConfigurer を実装して、新しい組み込みデータベースタイプをサポートします。

  • DataSourceFactory を実装して、組み込みデータベース接続を管理する接続プールなどの新しい DataSource 実装をサポートします。

GitHub の課題 (英語) の Spring コミュニティに拡張機能を提供することをお勧めします。

3.10. DataSource の初期化

org.springframework.jdbc.datasource.init パッケージは、既存の DataSource の初期化をサポートします。組み込みデータベースのサポートは、アプリケーションの DataSource を作成および初期化するための 1 つのオプションを提供します。ただし、サーバー上のどこかで実行されるインスタンスを初期化する必要がある場合があります。

3.10.1. Spring XML を使用したデータベースの初期化

データベースを初期化し、DataSource Bean への参照を提供できる場合、spring-jdbc 名前空間で initialize-database タグを使用できます。

<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
    <jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
</jdbc:initialize-database>

上記の例は、データベースに対して指定された 2 つのスクリプトを実行します。最初のスクリプトはスキーマを作成し、2 番目のスクリプトはテーブルにテストデータセットを取り込みます。スクリプトの場所は、Spring のリソース(classpath*:/com/foo/**/sql/*-data.sql など)に使用される通常の Ant スタイルのワイルドカードを使用したパターンにすることもできます。パターンを使用する場合、スクリプトは URL またはファイル名の字句順に実行されます。

データベース初期化子のデフォルトの動作は、提供されたスクリプトを無条件に実行することです。これは、必ずしも希望するものとは限りません。たとえば、既にテストデータが含まれているデータベースに対してスクリプトを実行する場合です。データを誤って削除する可能性は、最初にテーブルを作成してからデータを挿入するという一般的なパターン(前述)に従うことで低減されます。テーブルが既に存在する場合、最初のステップは失敗します。

ただし、既存のデータの作成と削除をさらに制御するために、XML 名前空間にはいくつかの追加オプションがあります。最初のフラグは、初期化のオンとオフを切り替えるフラグです。これは、環境に応じて設定できます(システムプロパティまたは環境 Bean からブール値を取得するなど)。次の例では、システムプロパティから値を取得します。

<jdbc:initialize-database data-source="dataSource"
    enabled="#{systemProperties.INITIALIZE_DATABASE}"> (1)
    <jdbc:script location="..."/>
</jdbc:initialize-database>
1INITIALIZE_DATABASE というシステムプロパティから enabled の値を取得します。

既存のデータで発生することを制御する 2 番目のオプションは、障害に対する耐性を高めることです。このために、次の例に示すように、イニシャライザがスクリプトから実行する SQL の特定のエラーを無視する機能を制御できます。

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
    <jdbc:script location="..."/>
</jdbc:initialize-database>

前の例では、空のデータベースに対してスクリプトが実行されることがあり、スクリプト内に DROP ステートメントがいくつかあるために失敗することを期待していると言っています。そのため、失敗した SQL DROP ステートメントは無視されますが、他の失敗は例外を引き起こします。これは、SQL ダイアレクトが DROP …​ IF EXISTS (または同様のもの)をサポートしていないが、すべてのテストデータを無条件で削除してから再作成する場合に便利です。その場合、通常、最初のスクリプトは DROP ステートメントのセットであり、その後に CREATE ステートメントのセットが続きます。

ignore-failures オプションは、NONE (デフォルト)、DROPS (失敗したドロップを無視)、または ALL (すべての失敗を無視)に設定できます。

; 文字がスクリプトにまったく存在しない場合は、各ステートメントを ; または改行で区切る必要があります。次の例に示すように、グローバルに制御することも、スクリプトごとにスクリプトを制御することもできます。

<jdbc:initialize-database data-source="dataSource" separator="@@"> (1)
    <jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> (2)
    <jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
    <jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>
1 区切りスクリプトを @@ に設定します。
2db-schema.sql のセパレーターを ; に設定します。

この例では、2 つの test-data スクリプトは @@ をステートメント区切り文字として使用し、db-schema.sql のみが ; を使用します。この構成は、デフォルトの区切り文字が @@ であることを指定し、db-schema スクリプトのデフォルトをオーバーライドします。

XML 名前空間から取得するよりも多くの制御が必要な場合は、DataSourceInitializer を直接使用して、アプリケーションのコンポーネントとして定義できます。

データベースに依存する他のコンポーネントの初期化

大規模なクラスのアプリケーション(Spring コンテキストが開始されるまでデータベースを使用しないアプリケーション)は、データベースイニシャライザーをさらに複雑にすることなく使用できます。アプリケーションがそれらのいずれでもない場合、このセクションの残りを読む必要があるかもしれません。

データベース初期化子は DataSource インスタンスに依存し、その初期化コールバックで提供されるスクリプトを実行します(XML Bean 定義の init-method、コンポーネントの @PostConstruct メソッド、または InitializingBean を実装するコンポーネントの afterPropertiesSet() メソッドに類似)。他の Bean が同じデータソースに依存し、初期化コールバックでデータソースを使用する場合、データがまだ初期化されていないために問題が発生する可能性があります。これの一般的な例は、熱心に初期化され、アプリケーションの起動時にデータベースからデータをロードするキャッシュです。

この課題を回避するには、2 つのオプションがあります。キャッシュ初期化戦略を後のフェーズに変更するか、データベース初期化子が最初に初期化されるようにします。

キャッシュの初期化戦略を変更するのは、アプリケーションが制御下にある場合で、そうでない場合は簡単です。これを実装する方法についてのいくつかの提案は次のとおりです。

  • 最初の使用時にキャッシュを遅延初期化して、アプリケーションの起動時間を改善します。

  • キャッシュまたはキャッシュを初期化する別のコンポーネントに Lifecycle または SmartLifecycle を実装させます。アプリケーションコンテキストが起動すると、autoStartup フラグを設定して SmartLifecycle を自動的に起動できます。また、囲んでいるコンテキストで ConfigurableApplicationContext.start() を呼び出して Lifecycle を手動で起動できます。

  • Spring ApplicationEvent または同様のカスタムオブザーバーメカニズムを使用して、キャッシュの初期化をトリガーします。ContextRefreshedEvent は(すべての Bean が初期化された後)使用準備ができたときに常にコンテキストによって公開されるため、多くの場合、これは便利なフックです(これが SmartLifecycle のデフォルトの動作です)。

データベース初期化子が最初に初期化されるようにすることも簡単です。これを実装する方法に関するいくつかの提案は次のとおりです。

  • Spring BeanFactory のデフォルトの動作に依存します。つまり、Bean は登録順に初期化されます。XML 構成の一連の <import/> 要素の一般的なプラクティスを採用してアプリケーションモジュールを並べ、データベースとデータベースの初期化が最初にリストされるようにすることで、簡単に調整できます。

  • DataSource とそれを使用するビジネスコンポーネントを分離し、個別の ApplicationContext インスタンスに配置して起動順序を制御します(たとえば、親コンテキストには DataSource が含まれ、子コンテキストにはビジネスコンポーネントが含まれます)。この構造は Spring Web アプリケーションでは一般的ですが、より一般的に適用できます。

4. R2DBC によるデータアクセス

R2DBC (英語) ("Reactive Relational Database Connectivity")は、リアクティブパターンを使用して SQL データベースへのアクセスを標準化するためのコミュニティ主導の仕様です。

4.1. パッケージ階層

Spring Framework の R2DBC 抽象化フレームワークは、2 つの異なるパッケージで構成されています。

  • coreorg.springframework.r2dbc.core パッケージには、DatabaseClient クラスとさまざまな関連クラスが含まれています。R2DBC コアクラスを使用した基本的な R2DBC 処理とエラー処理の制御を参照してください。

  • connectionorg.springframework.r2dbc.connection パッケージには、ConnectionFactory に簡単にアクセスできるユーティリティクラスと、変更されていない R2DBC のテストと実行に使用できるさまざまなシンプルな ConnectionFactory 実装が含まれています。データベース接続の制御を参照してください。

4.2. R2DBC コアクラスを使用した基本的な R2DBC 処理とエラー処理の制御

このセクションでは、R2DBC コアクラスを使用して、エラー処理を含む基本的な R2DBC 処理を制御する方法について説明します。次のトピックが含まれます。

4.2.1. DatabaseClient を使用する

DatabaseClient は、R2DBC コアパッケージの中心的なクラスです。リソースの作成と解放を処理します。これにより、接続を閉じるのを忘れるなどの一般的なエラーを回避できます。SQL を提供して結果を抽出するアプリケーションコードを残して、コア R2DBC ワークフローの基本的なタスク(ステートメントの作成や実行など)を実行します。DatabaseClient クラス:

  • SQL クエリを実行する

  • ステートメントとストアドプロシージャの呼び出しを更新する

  • Result インスタンスに対して反復を実行する

  • R2DBC 例外をキャッチし、org.springframework.dao パッケージで定義された一般的でより有益な例外階層に変換します。( 一貫した例外階層を参照してください。)

クライアントには、宣言型構成にリアクティブ型を使用する関数で流暢な API があります。

コードに DatabaseClient を使用する場合は、java.util.function インターフェースを実装するだけでよく、明確に定義された契約が提供されます。DatabaseClient クラスによって提供される Connection を指定すると、Function コールバックは Publisher を作成します。Row の結果を抽出するマッピング関数についても同様です。

ConnectionFactory 参照を使用して直接インスタンス化することにより、DAO 実装内で DatabaseClient を使用するか、Spring IoC コンテナーで構成して Bean 参照として DAO に渡すことができます。

DatabaseClient オブジェクトを作成する最も簡単な方法は、次のように静的ファクトリメソッドを使用することです。

Java
DatabaseClient client = DatabaseClient.create(connectionFactory);
Kotlin
val client = DatabaseClient.create(connectionFactory)
ConnectionFactory は、常に Spring IoC コンテナーで Bean として構成する必要があります。

上記の方法は、デフォルト設定で DatabaseClient を作成します。

DatabaseClient.builder() から Builder インスタンスを取得することもできます。次のメソッドを呼び出すことにより、クライアントをカスタマイズできます。

  • … .bindMarkers(…): 特定の BindMarkersFactory を指定して、名前付きパラメーターからデータベースバインドマーカーへの変換を構成します。

  • … .executeFunction(…)Statement オブジェクトの実行方法を ExecuteFunction に設定します。

  • … .namedParameters(false): 名前付きパラメーターの展開を無効にします。デフォルトで有効になっています。

ダイアレクトは、通常 ConnectionFactoryMetadata をインスペクションすることにより、ConnectionFactory から BindMarkersFactoryResolver (Javadoc) によって解決されます。
org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider 〜 META-INF/spring.factories を実装するクラスを登録することにより、Spring に BindMarkersFactory を自動検出させることができます。BindMarkersFactoryResolver は、Spring の SpringFactoriesLoader を使用して、クラスパスからバインドマーカープロバイダーの実装を検出します。

現在サポートされているデータベースは次のとおりです。

  • H2

  • MariaDB

  • Microsoft SQL Server

  • MySQL

  • Postgres

このクラスによって発行されたすべての SQL は、クライアントインスタンスの完全修飾クラス名(通常 DefaultDatabaseClient)に対応するカテゴリの DEBUG レベルでログに記録されます。さらに、各実行は、デバッグを支援するために反応シーケンスにチェックポイントを登録します。

以下のセクションでは、DatabaseClient の使用例をいくつか示します。これらの例は、DatabaseClient によって公開されたすべての機能の完全なリストではありません。詳細については、付随する javadoc を参照してください。

ステートメントの実行

DatabaseClient は、ステートメントを実行する基本機能を提供します。次の例は、新しいテーブルを作成する最小限で完全に機能するコードに含める必要があるものを示しています。

Java
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
        .then();
Kotlin
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
        .await()

DatabaseClient は、便利で流れるように使用できるように設計されています。実行仕様の各段階で中間、継続、終了メソッドを公開します。上記の例では、then() を使用して、クエリ(または SQL クエリに複数のステートメントが含まれる場合はクエリ)が完了するとすぐに完了する完了 Publisher を返します。

execute(…) は、SQL クエリ文字列またはクエリ Supplier<String> のいずれかを受け入れて、実際のクエリの作成を実行まで延期します。
クエリ (SELECT)

SQL クエリは、Row オブジェクトまたは影響を受ける行の数を通じて値を返すことができます。DatabaseClient は、発行されたクエリに応じて、更新された行の数または行自体を返すことができます。

次のクエリは、テーブルから id 列と name 列を取得します。

Java
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
        .fetch().first();
Kotlin
val first = client.sql("SELECT id, name FROM person")
        .fetch().awaitSingle()

次のクエリはバインド変数を使用します。

Java
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
        .bind("fn", "Joe")
        .fetch().first();
Kotlin
val first = client.sql("SELECT id, name FROM person WHERE WHERE first_name = :fn")
        .bind("fn", "Joe")
        .fetch().awaitSingle()

上記の例で fetch() の使用に気づいたかもしれません。fetch() は、消費するデータ量を指定できる継続演算子です。

first() を呼び出すと、結果から最初の行が返され、残りの行が破棄されます。次の演算子を使用してデータを使用できます。

  • first() は、結果全体の最初の行を返します。その Kotlin コルーチンバリアントは、null 許容でない戻り値の場合は awaitSingle() と呼ばれ、値がオプションの場合は awaitSingleOrNull() と呼ばれます。

  • one() は正確に 1 つの結果を返し、結果にさらに行が含まれている場合は失敗します。Kotlin コルーチンを使用して、正確に 1 つの値に awaitOne() を使用するか、値が null の場合は awaitOneOrNull() を使用します。

  • all() は、結果のすべての行を返します。Kotlin コルーチンを使用する場合は、flow() を使用してください。

  • rowsUpdated() は、影響を受ける行の数(INSERT/UPDATE/DELETE カウント)を返します。その Kotlin コルーチンバリアントは awaitRowsUpdated() という名前です。

さらにマッピングの詳細を指定せずに、クエリは表形式の結果を Map として返します。そのキーは、列の値にマップされる大文字と小文字を区別しない列名です。

Row ごとに呼び出される Function<Row, T> を指定することにより、結果のマッピングを制御できるため、任意の値(特異値、コレクションとマップ、オブジェクト)を返すことができます。

次の例では、id 列を抽出し、その値を出力します。

Java
Flux<String> names = client.sql("SELECT name FROM person")
        .map(row -> row.get("id", String.class))
        .all();
Kotlin
val names = client.sql("SELECT name FROM person")
        .map{ row: Row -> row.get("id", String.class) }
        .flow()
null は ?

リレーショナルデータベースの結果には、null 値を含めることができます。Reactive Streams 仕様は、null 値の発行を禁止しています。この要件により、抽出機能での適切な null 処理が義務付けられています。Row から null 値を取得できますが、null 値を発行しないでください。null 値をオブジェクトにラップして(たとえば、特異値の場合は Optional)、抽出機能によって null 値が直接戻されないようにする必要があります。

DatabaseClient による更新(INSERTUPDATE および DELETE

ステートメントの変更の唯一の違いは、これらのステートメントは通常、表形式のデータを返さないため、rowsUpdated() を使用して結果を消費することです。

次の例は、更新された行の数を返す UPDATE ステートメントを示しています。

Java
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
        .bind("fn", "Joe")
        .fetch().rowsUpdated();
Kotlin
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
        .bind("fn", "Joe")
        .fetch().awaitRowsUpdated()
クエリへの値のバインド

一般的なアプリケーションでは、何らかの入力に従って行を選択または更新するために、パラメーター化された SQL ステートメントが必要です。これらは通常、WHERE 句によって制約された SELECT ステートメント、または入力パラメーターを受け入れる INSERT および UPDATE ステートメントです。パラメーター化されたステートメントは、パラメーターが適切にエスケープされない場合、SQL インジェクションのリスクを負います。DatabaseClient は R2DBC の bind API を利用して、クエリパラメーターの SQL インジェクションのリスクを排除します。execute(…) 演算子を使用してパラメーター化された SQL ステートメントを提供し、パラメーターを実際の Statement にバインドできます。次に、R2DBC ドライバーは、準備されたステートメントとパラメーター置換を使用してステートメントを実行します。

パラメーターバインディングは、2 つのバインディング戦略をサポートしています。

  • インデックスによる、ゼロベースのパラメーターインデックスの使用。

  • 名前別、プレースホルダー名を使用。

次の例は、クエリのパラメーターバインドを示しています。

db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
    .bind("id", "joe")
    .bind("name", "Joe")
    .bind("age", 34);
R2DBC ネイティブバインドマーカー

R2DBC は、実際のデータベースベンダーに依存するデータベースネイティブバインドマーカーを使用します。例として、Postgres は $1$2$n などのインデックス付きマーカーを使用します。別の例は SQL Server で、これは @ で始まる名前付きバインドマーカーを使用します。

これは、バインドマーカーとして ? を必要とする JDBC とは異なります。JDBC では、実際のドライバーは ? バインドマーカーをステートメント実行の一部としてデータベースネイティブマーカーに変換します。

Spring Framework の R2DBC サポートにより、:name 構文でネイティブバインドマーカーまたは名前付きバインドマーカーを使用できます。

名前付きパラメーターのサポートでは、BindMarkersFactory インスタンスを活用して、クエリ実行時に名前付きパラメーターをネイティブバインドマーカーに展開します。これにより、さまざまなデータベースベンダー間である程度のクエリの移植性が得られます。

クエリプリプロセッサは、名前の Collection パラメーターを一連のバインドマーカーに展開し、引数の数に基づいて動的なクエリを作成する必要をなくします。ネストされたオブジェクト配列は、(たとえば)選択リストを使用できるように拡張されています。

次のクエリを検討してください。

SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))

上記のクエリはパラメーター化して、次のように実行できます。

Java
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann",  50});

client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
    .bind("tuples", tuples);
Kotlin
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))

client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
    .bind("tuples", tuples)
選択リストの使用はベンダーに依存します。

次の例は、IN 述語を使用したより単純なバリアントを示しています。

Java
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
    .bind("ages", Arrays.asList(35, 50));
Kotlin
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))

client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
    .bind("tuples", arrayOf(35, 50))
R2DBC 自体は、コレクションのような値をサポートしていません。それでも、上記の例で特定の List を展開すると、Spring の R2DBC サポートの名前付きパラメーターに対して機能します。上記の IN 句で使用します。ただし、配列型の列を挿入または更新するには(Postgres など)、基盤となる R2DBC ドライバーでサポートされている配列型が必要です。通常は Java 配列です。text[] 列を更新するための String[]Collection<String> などを配列パラメーターとして渡さないでください。
ステートメントフィルター

実際の Statement を実行する前に、オプションを微調整する必要がある場合があります。次の例に示すように、DatabaseClient を介して Statement フィルター(StatementFilterFunction)を登録し、実行中のステートメントをインターセプトして変更します。

Java
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
    .bind("name",  …)
    .bind("state",  …);
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
            .filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
            .bind("name",  …)
            .bind("state",  …)

DatabaseClient は、Function<Statement, Statement> を受け入れる単純化された filter(…) オーバーロードも公開します。

Java
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter(statement -> s.returnGeneratedValues("id"));

client.sql("SELECT id, name, state FROM table")
    .filter(statement -> s.fetchSize(25));
Kotlin
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter { statement -> s.returnGeneratedValues("id") }

client.sql("SELECT id, name, state FROM table")
    .filter { statement -> s.fetchSize(25) }

StatementFilterFunction の実装により、Statement のフィルタリングと Result オブジェクトのフィルタリングが可能になります。

DatabaseClient ベストプラクティス

DatabaseClient クラスのインスタンスは、一度設定されるとスレッドセーフです。これは、DatabaseClient の単一インスタンスを構成し、この共有参照を複数の DAO(またはリポジトリ)に安全に挿入できることを意味するため、重要です。DatabaseClient は、ConnectionFactory への参照を維持するという点でステートフルですが、この状態は会話状態ではありません。

DatabaseClient クラスを使用する場合の一般的な方法は、Spring 構成ファイルで ConnectionFactory を構成してから、その共有 ConnectionFactory Bean を DAO クラスに依存性注入することです。DatabaseClient は、ConnectionFactory の setter で作成されます。これにより、次のような DAO が発生します。

Java
public class R2dbcCorporateEventDao implements CorporateEventDao {

    private DatabaseClient databaseClient;

    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.databaseClient = DatabaseClient.create(connectionFactory);
    }

    // R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
Kotlin
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {

    private val databaseClient = DatabaseClient.create(connectionFactory)

    // R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}

明示的な構成の代わりに、コンポーネントスキャンと依存性注入のアノテーションサポートを使用します。この場合、クラスに @Component (コンポーネントスキャンの候補になります)でアノテーションを付け、ConnectionFactory setter メソッドに @Autowired でアノテーションを付けることができます。次の例は、その方法を示しています。

Java
@Component (1)
public class R2dbcCorporateEventDao implements CorporateEventDao {

    private DatabaseClient databaseClient;

    @Autowired (2)
    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.databaseClient = DatabaseClient.create(connectionFactory); (3)
    }

    // R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1@Component でクラスにアノテーションを付けます。
2ConnectionFactory setter メソッドに @Autowired でアノテーションを付けます。
3ConnectionFactory で新しい DatabaseClient を作成します。
Kotlin
@Component (1)
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { (2)

    private val databaseClient = DatabaseClient(connectionFactory) (3)

    // R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
1@Component でクラスにアノテーションを付けます。
2ConnectionFactory のコンストラクター注入。
3ConnectionFactory で新しい DatabaseClient を作成します。

上記のテンプレート初期化スタイルのどちらを使用する(またはしない)かに関係なく、SQL を実行するたびに DatabaseClient クラスの新しいインスタンスを作成する必要はほとんどありません。一度構成すると、DatabaseClient インスタンスはスレッドセーフになります。アプリケーションが複数のデータベースにアクセスする場合、複数の DatabaseClient インスタンスが必要になる場合があります。これには、複数の ConnectionFactory が必要であり、その後、複数の異なる構成の DatabaseClient インスタンスが必要です。

5. 自動生成されたキーの取得

INSERT ステートメントは、自動インクリメント列または ID 列を定義するテーブルに行を挿入するときにキーを生成する場合があります。生成する列名を完全に制御するには、目的の列に対して生成されたキーをリクエストする StatementFilterFunction を登録するだけです。

Java
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter(statement -> s.returnGeneratedValues("id"))
        .map(row -> row.get("id", Integer.class))
        .first();

// generatedId emits the generated key once the INSERT statement has finished
Kotlin
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
    .filter { statement -> s.returnGeneratedValues("id") }
        .map { row -> row.get("id", Integer.class) }
        .awaitOne()

// generatedId emits the generated key once the INSERT statement has finished

5.1. データベース接続の制御

このセクションでは以下について説明します。

5.1.1. ConnectionFactory を使用する

Spring は、ConnectionFactory を介してデータベースへの R2DBC 接続を取得します。ConnectionFactory は R2DBC 仕様の一部であり、ドライバーの一般的なエントリポイントです。コンテナーまたはフレームワークで、接続プールとトランザクション管理の課題をアプリケーションコードから隠すことができます。開発者は、データベースへの接続方法に関する詳細を知る必要はありません。これは、ConnectionFactory を設定する管理者の責任です。コードの開発とテストを行うときに両方のロールを果たす可能性がありますが、本番データソースがどのように構成されているかを必ずしも知っている必要はありません。

Spring の R2DBC レイヤーを使用する場合、サードパーティが提供する接続プールの実装を使用して独自のレイヤーを構成できます。一般的な実装は R2DBC プール(r2dbc-pool)です。Spring ディストリビューションの実装は、テストのみを目的としており、プーリングは提供していません。

ConnectionFactory を構成するには:

  1. 通常は R2DBC ConnectionFactory を取得するため、ConnectionFactory との接続を取得します。

  2. R2DBC URL を提供します(正しい値については、ドライバーのドキュメントを参照してください)。

次の例は、ConnectionFactory を構成する方法を示しています。

Java
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
Kotlin
val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");

5.1.2. ConnectionFactoryUtils を使用する

ConnectionFactoryUtils クラスは、ConnectionFactory から接続を取得し、(必要に応じて)接続を閉じるための static メソッドを提供する便利で強力なヘルパークラスです。

サブスクライバー Context-bound 接続、たとえば R2dbcTransactionManager をサポートします。

5.1.3. SingleConnectionFactory を使用する

SingleConnectionFactory クラスは、使用するたびに閉じられない単一の Connection をラップする DelegatingConnectionFactory インターフェースの実装です。

(永続化ツールを使用する場合のように)プールされた接続を想定してクライアントコードが close を呼び出す場合は、suppressClose プロパティを true に設定する必要があります。この設定は、物理接続をラップするクローズ抑制プロキシを返します。これをネイティブ Connection または同様のオブジェクトにキャストすることはできなくなったことに注意してください。

SingleConnectionFactory は主にテストクラスであり、R2DBC ドライバーで使用が許可されている場合は、パイプライン処理などの特定の要件に使用できます。プールされた ConnectionFactory とは対照的に、常に同じ接続を再利用し、物理接続の過度の作成を回避します。

5.1.4. TransactionAwareConnectionFactoryProxy を使用する

TransactionAwareConnectionFactoryProxy は、ターゲット ConnectionFactory のプロキシです。プロキシは、ConnectionFactory をターゲットとしてラップし、Spring が管理するトランザクションの認識を追加します。

Spring の R2DBC サポートと統合されていない R2DBC クライアントを使用する場合は、このクラスを使用する必要があります。この場合でも、このクライアントを使用すると同時に、このクライアントを Spring 管理対象トランザクションに参加させることができます。リソース管理のために、R2DBC クライアントを ConnectionFactoryUtils への適切なアクセスと統合することが一般的に望ましいです。

詳細については、TransactionAwareConnectionFactoryProxy (Javadoc) javadoc を参照してください。

5.1.5. R2dbcTransactionManager を使用する

R2dbcTransactionManager クラスは、単一の R2DBC データソース用の ReactiveTransactionManager 実装です。これは、指定された接続ファクトリからサブスクライバ Context への R2DBC 接続をバインドし、接続ファクトリごとに 1 つのサブスクライバ接続を可能にする可能性があります。

R2DBC の標準 ConnectionFactory.create() ではなく、ConnectionFactoryUtils.getConnection(ConnectionFactory) を介して R2DBC 接続を取得するには、アプリケーションコードが必要です。

すべてのフレームワーククラス(DatabaseClient など)は、この戦略を暗黙的に使用します。このトランザクションマネージャーで使用しない場合、ルックアップ戦略は一般的な戦略とまったく同じように動作します。どのような場合でも使用できます。

R2dbcTransactionManager クラスは、接続に適用されるカスタム分離レベルをサポートします。

6. オブジェクトリレーショナルマッピング(ORM)データアクセス

このセクションでは、オブジェクトリレーショナルマッピング(ORM)を使用する場合のデータアクセスについて説明します。

6.1. Spring を使用した ORM の概要

Spring Framework は、Java Persistence API(JPA)との統合をサポートし、リソース管理、データアクセスオブジェクト(DAO)の実装、およびトランザクション戦略のためのネイティブ Hibernate をサポートします。例:Hibernate には、多くの典型的な Hibernate 統合の課題に対処するいくつかの便利な IoC 機能を備えた一流のサポートがあります。依存性注入を介して、OR(オブジェクトリレーショナル)マッピングツールでサポートされているすべての機能を構成できます。Spring のリソースおよびトランザクション管理に参加でき、Spring の一般的なトランザクションおよび DAO 例外階層に準拠しています。推奨される統合スタイルは、プレーンな Hibernate または JPA API に対して DAO をコーディングすることです。

Spring は、データアクセスアプリケーションを作成するときに、選択した ORM レイヤーに大幅な機能強化を追加します。必要なだけ統合サポートを活用できます。また、この統合の取り組みを、同様のインフラストラクチャを社内で構築するコストとリスクと比較する必要があります。すべてが再利用可能な JavaBeans のセットとして設計されているため、テクノロジーに関係なく、ライブラリと同じように ORM サポートの多くを使用できます。Spring IoC コンテナーの ORM は、構成とデプロイを容易にします。このセクションのほとんどの例は、Spring コンテナー内の構成を示しています。

Spring Framework を使用して ORM DAO を作成する利点は次のとおりです。

  • より簡単なテスト。Spring の IoC アプローチにより、Hibernate SessionFactory インスタンス、JDBC DataSource インスタンス、トランザクションマネージャー、およびマッピングされたオブジェクトの実装(必要な場合)の実装と構成場所を簡単に交換できます。これにより、永続性に関連する各コードを個別にテストすることがはるかに簡単になります。

  • 一般的なデータアクセス例外。Spring は、ORM ツールからの例外をラップし、独自の(潜在的にチェックされる)例外を共通のランタイム DataAccessException 階層に変換できます。この機能を使用すると、適切なレイヤーでのみ、回復不可能なほとんどの永続性例外を処理できます。煩わしい定型的なキャッチ、スロー、例外宣言はありません。必要に応じて、例外をトラップして処理できます。JDBC 例外(DB 固有のダイアレクトを含む)も同じ階層に変換されることに注意してください。つまり、一貫したプログラミングモデル内で JDBC を使用していくつかの操作を実行できます。

  • 一般的なリソース管理。Spring アプリケーションコンテキストは、Hibernate SessionFactory インスタンス、JPA EntityManagerFactory インスタンス、JDBC DataSource インスタンス、およびその他の関連リソースの場所と構成を処理できます。これにより、これらの値の管理と変更が容易になります。Spring は、永続化リソースの効率的、簡単、安全な処理を提供します。例:Hibernate を使用する関連コードは、通常、同じ Hibernate Session を使用して、効率と適切なトランザクション処理を保証する必要があります。Spring では、Hibernate SessionFactory を介して現在の Session を公開することにより、Session を作成し、現在のスレッドに透過的に簡単にバインドできます。Spring は、ローカルまたは JTA トランザクション環境での典型的な Hibernate の使用に関する多くの慢性的な問題を解決します。

  • 統合トランザクション管理@Transactional アノテーションを使用するか、XML 構成ファイルでトランザクション AOP アドバイスを明示的に構成することにより、ORM コードを宣言型アスペクト指向プログラミング(AOP)スタイルのメソッドインターセプターでラップできます。どちらの場合も、トランザクションセマンティクスと例外処理(ロールバックなど)が処理されます。リソースおよびトランザクション管理で説明したように、ORM 関連のコードに影響を与えることなく、さまざまなトランザクションマネージャーを交換することもできます。例:両方のシナリオで利用可能な同じ完全なサービス(宣言トランザクションなど)を使用して、ローカルトランザクションと JTA を交換できます。さらに、JDBC 関連のコードは、ORM の実行に使用するコードとトランザクション的に完全に統合できます。これは、ORM(バッチ処理や BLOB ストリーミングなど)には適していないが、ORM 操作と共通のトランザクションを共有する必要があるデータアクセスに役立ちます。

MongoDB などの代替データベーステクノロジのサポートを含む、より包括的な ORM サポートについては、Spring Data プロジェクトスイートをチェックアウトすることをお勧めします。JPA ユーザーの場合、https://spring.ioJPA を使用したデータへのアクセスの開始ガイドが優れた導入を提供します。

6.2. ORM 統合の一般的な考慮事項

このセクションでは、すべての ORM テクノロジーに適用される考慮事項について説明します。Hibernate セクションでは、詳細を提供し、これらの機能と構成を具体的なコンテキストで示します。

Spring の ORM 統合の主なゴールは、明確なアプリケーションレイヤーリング(データアクセスおよびトランザクションテクノロジを使用)およびアプリケーションオブジェクトの疎結合です。データアクセスまたはトランザクション戦略に対するビジネスサービスの依存関係、ハードコードされたリソースルックアップ、交換が困難なシングルトンが増え、カスタムサービスレジストリがなくなりました。ゴールは、アプリケーションオブジェクトをワイヤリングするための 1 つのシンプルで一貫したアプローチを持ち、可能な限りコンテナーの依存関係がないように再利用可能な状態に保つことです。個々のデータアクセス機能はすべて単独で使用できますが、Spring のアプリケーションコンテキストコンセプトとうまく統合し、Spring に対応する必要のないプレーンな JavaBean インスタンスの XML ベースの構成と相互参照を提供します。典型的な Spring アプリケーションでは、データアクセステンプレート、データアクセスオブジェクト、トランザクションマネージャー、データアクセスオブジェクトとトランザクションマネージャーを使用するビジネスサービス、Web ビューリゾルバー、ビジネスサービスを使用する Web コントローラーなど、多くの重要なオブジェクトが JavaBeans です。

6.2.1. リソースおよびトランザクション管理

典型的なビジネスアプリケーションは、反復的なリソース管理コードで散らかっています。多くのプロジェクトは独自のソリューションを発明しようとしますが、プログラミングの利便性のために障害の適切な処理を犠牲にすることもあります。Spring は、適切なリソース処理、つまり JDBC の場合はテンプレートを使用し、ORM テクノロジーに AOP インターセプターを適用する IoC の簡単なソリューションを提唱しています。

インフラストラクチャは、適切なリソース処理と、特定の API 例外の未チェックのインフラストラクチャ例外階層への適切な変換を提供します。Spring は、あらゆるデータアクセス戦略に適用可能な DAO 例外階層を導入します。直接 JDBC の場合、前のセクションで述べた JdbcTemplate クラスは、データベース固有の SQL エラーコードの意味のある例外クラスへの変換を含め、接続処理と SQLException から DataAccessException 階層への適切な変換を提供します。ORM テクノロジーについては、同じ例外変換のメリットを得る方法については、次のセクションを参照してください。

トランザクション管理に関しては、JdbcTemplate クラスは Spring トランザクションサポートに接続し、それぞれの Spring トランザクションマネージャーを介して JTA および JDBC トランザクションの両方をサポートします。サポートされている ORM テクノロジーについて、Spring は、Hibernate および JPA トランザクションマネージャーを介した Hibernate および JPA サポート、および JTA サポートを提供します。トランザクションサポートの詳細については、トランザクション管理の章を参照してください。

6.2.2. 例外変換

DAO で Hibernate または JPA を使用する場合、永続化テクノロジーのネイティブ例外クラスの処理方法を決定する必要があります。DAO は、テクノロジーに応じて HibernateException または PersistenceException のサブクラスをスローします。これらの例外はすべてランタイム例外であり、宣言またはキャッチする必要はありません。IllegalArgumentException および IllegalStateException を処理する必要がある場合もあります。つまり、呼び出し元は、永続化テクノロジの独自の例外構造に依存する場合を除き、例外を一般に致命的なものとしてしか処理できないことを意味します。特定の原因(楽観的ロックの失敗など)をキャッチすることは、呼び出し元を実装戦略に結び付けない限り不可能です。このトレードオフは、強力な ORM ベースであるか、特別な例外処理を必要としない(またはその両方)アプリケーションに受け入れられる場合があります。ただし、Spring では、@Repository アノテーションを介して透過的に例外変換を適用できます。次の例(Java 構成用と XML 構成用)は、その方法を示しています。

Java
@Repository
public class ProductDaoImpl implements ProductDao {

    // class body here...

}
Kotlin
@Repository
class ProductDaoImpl : ProductDao {
    // class body here...
}
<beans>

    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

ポストプロセッサーは、すべての例外トランスレーター(PersistenceExceptionTranslator インターフェースの実装)を自動的に検索し、@Repository アノテーションでマークされたすべての Bean に通知して、検出されたトランスレーターがスローされた例外に対して適切な変換をインターセプトおよび適用できるようにします。

要約すると、Spring で管理されたトランザクション、依存性注入、Spring のカスタム例外階層への透過的な例外変換(必要に応じて)の恩恵を受けながら、プレーンな永続化テクノロジーの API とアノテーションに基づいて DAO を実装できます。

6.3. Hibernate

Spring 環境での Hibernate 5 (英語) のカバレッジから始め、それを使用して、Spring が OR マッパーの統合に向けて取っているアプローチを示します。このセクションでは、多くの課題を詳細にカバーし、DAO 実装のさまざまなバリエーションとトランザクション境界を示します。これらのパターンのほとんどは、サポートされている他のすべての ORM ツールに直接変換できます。この章の後半のセクションでは、他の ORM テクノロジーについて説明し、簡単な例を示します。

Spring Framework 5.3, の時点で、Spring には、Spring の HibernateJpaVendorAdapter およびネイティブ Hibernate SessionFactory セットアップ用の HibernateORM 5.2 + が必要です。新しく開始したアプリケーションには、Hibernate ORM5.4 を使用することを強くお勧めします。

6.3.1. Spring コンテナーでの SessionFactory セットアップ

アプリケーションオブジェクトをハードコードされたリソースルックアップに結び付けないようにするには、リソース(JDBC DataSource または Hibernate SessionFactory など)を Spring コンテナー内の Bean として定義できます。リソースにアクセスする必要があるアプリケーションオブジェクトは、次のセクションの DAO 定義に示すように、Bean 参照を介してそのような事前定義されたインスタンスへの参照を受け取ります。

XML アプリケーションコンテキスト定義からの次の抜粋は、その上に JDBC DataSource および Hibernate SessionFactory をセットアップする方法を示しています。

<beans>

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>

</beans>

次の例に示すように、ローカル Jakarta Commons DBCP BasicDataSource から JNDI に配置された DataSource (通常はアプリケーションサーバーによって管理される)への切り替えは、構成の問題にすぎません。

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

Spring の JndiObjectFactoryBean / <jee:jndi-lookup> を使用して JNDI にある SessionFactory にアクセスし、それを取得して公開することもできます。ただし、通常、EJB コンテキスト以外では一般的ではありません。

Spring は LocalSessionFactoryBuilder バリアントも提供し、@Bean スタイルの構成およびプログラムによるセットアップとシームレスに統合します(FactoryBean は含まれません)。

LocalSessionFactoryBean と LocalSessionFactoryBuilder は両方ともバックグラウンドブートストラップをサポートし、Hibernate の初期化は特定のブートストラップエグゼキューター(SimpleAsyncTaskExecutor など)のアプリケーションブートストラップスレッドと並行して実行されます。LocalSessionFactoryBean では、これは bootstrapExecutor プロパティを介して利用できます。プログラムによる LocalSessionFactoryBuilder には、ブートストラップエグゼキューター引数を取るオーバーロード buildSessionFactory メソッドがあります。

Spring Framework 5.1, 以降、このようなネイティブ Hibernate セットアップでは、ネイティブ Hibernate アクセスの横にある標準の JPA 対話用に JPA EntityManagerFactory を公開することもできます。詳細については、JPA のネイティブ Hibernate セットアップを参照してください。

6.3.2. プレーン Hibernate API に基づいた DAO の実装

Hibernate にはコンテキストセッションと呼ばれる機能があり、Hibernate 自体がトランザクションごとに 1 つの現在の Session を管理します。これは、トランザクションごとに 1 つの Hibernate Session の Spring の同期とほぼ同等です。対応する DAO 実装は、プレーンな Hibernate API に基づいた次の例に似ています。

Java
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}
Kotlin
class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {

    fun loadProductsByCategory(category: String): Collection<*> {
        return sessionFactory.currentSession
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list()
    }
}

このスタイルは、SessionFactory をインスタンス変数に保持することを除いて、Hibernate のリファレンスドキュメントと例のスタイルに似ています。Hibernate の CaveatEmptor サンプルアプリケーションの従来の staticHibernateUtil クラスよりも、このようなインスタンスベースのセットアップを強くお勧めします。(一般的に、どうしても必要な場合を除き、static 変数にリソースを保持しないでください。)

上記の DAO の例は、依存性注入パターンに従います。Spring の HibernateTemplate に対してコーディングされている場合のように、Spring IoC コンテナーにうまく収まります。このような DAO をプレーン Java で(たとえば、単体テストで)設定することもできます。これを行うには、インスタンス化して、目的のファクトリリファレンスを使用して setSessionFactory(..) を呼び出します。Spring Bean の定義として、DAO は次のようになります。

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

</beans>

この DAO スタイルの主な利点は、Hibernate API のみに依存することです。Spring クラスのインポートは必要ありません。これは非侵襲性の観点から魅力的であり、Hibernate 開発者にとってより自然に感じるかもしれません。

ただし、DAO はプレーンな HibernateException をスローします(チェックされていないため、宣言またはキャッチする必要はありません)。つまり、呼び出し元は、Hibernate の例外階層に依存する場合を除き、例外を一般に致命的なものとしてのみ処理できます。特定の原因(楽観的ロックの失敗など)をキャッチすることは、呼び出し元を実装戦略に結び付けない限り不可能です。このトレードオフは、強力な Hibernate ベースのアプリケーション、特別な例外処理を必要としない、またはその両方のアプリケーションに受け入れられる場合があります。

幸い、Spring の LocalSessionFactoryBean は、Hibernate の SessionFactory.getCurrentSession() メソッドを Spring トランザクション戦略でサポートし、現在の Spring 管理のトランザクション Session を HibernateTransactionManager でも返します。そのメソッドの標準的な動作は、進行中の JTA トランザクションに関連付けられている現在の Session を返します(存在する場合)。この動作は、Spring の JtaTransactionManager、EJB コンテナー管理トランザクション(CMT)、または JTA のいずれを使用するかに関係なく適用されます。

要約すると、Spring が管理するトランザクションに参加しながら、プレーンな Hibernate API に基づいて DAO を実装できます。

6.3.3. 宣言的な取引区分

Spring の宣言的なトランザクションサポートを使用することをお勧めします。これにより、Java コードの明示的なトランザクション境界設定 API 呼び出しを AOP トランザクションインターセプターに置き換えることができます。Java アノテーションまたは XML を使用して、Spring コンテナーでこのトランザクションインターセプターを構成できます。この宣言的なトランザクション機能により、ビジネスサービスを繰り返しトランザクション境界コードから解放し、アプリケーションの真の価値であるビジネスロジックの追加に集中できます。

続行する前に、宣言的なトランザクション管理をまだ読んでいない場合は読むことを強くお勧めします。

サービスレイヤーに @Transactional アノテーションを付けて、Spring コンテナーにこれらのアノテーションを見つけ、これらのアノテーション付きメソッドにトランザクションセマンティクスを提供するように指示できます。次の例は、その方法を示しています。

Java
public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }

    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }
}
Kotlin
class ProductServiceImpl(private val productDao: ProductDao) : ProductService {

    @Transactional
    fun increasePriceOfAllProductsInCategory(category: String) {
        val productsToChange = productDao.loadProductsByCategory(category)
        // ...
    }

    @Transactional(readOnly = true)
    fun findAllProducts() = productDao.findAllProducts()
}

コンテナーで、PlatformTransactionManager 実装(Bean として)および <tx:annotation-driven/> エントリをセットアップし、実行時に @Transactional 処理を選択する必要があります。次の例は、その方法を示しています。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->

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

    <tx:annotation-driven/>

    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>

6.3.4. プログラムによるトランザクション区分

任意の数の操作にまたがる低レベルのデータアクセスサービスに加えて、アプリケーションの高レベルでトランザクションの境界を定めることができます。周囲のビジネスサービスの実装にも制限はありません。Spring PlatformTransactionManager のみが必要です。ここでも、後者はどこからでも取得できますが、setTransactionManager(..) メソッドを介した Bean 参照として取得することが望ましいです。また、productDAO は setProductDao(..) メソッドで設定する必要があります。次のスニペットのペアは、Spring アプリケーションコンテキストでのトランザクションマネージャーとビジネスサービス定義、およびビジネスメソッド実装の例を示しています。

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>
Java
public class ProductServiceImpl implements ProductService {

    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // do the price increase...
            }
        });
    }
}
Kotlin
class ProductServiceImpl(transactionManager: PlatformTransactionManager,
                        private val productDao: ProductDao) : ProductService {

    private val transactionTemplate = TransactionTemplate(transactionManager)

    fun increasePriceOfAllProductsInCategory(category: String) {
        transactionTemplate.execute {
            val productsToChange = productDao.loadProductsByCategory(category)
            // do the price increase...
        }
    }
}

Spring の TransactionInterceptor では、チェックされたアプリケーションの例外をコールバックコードでスローできますが、TransactionTemplate はコールバック内の未チェックの例外に制限されています。TransactionTemplate は、未チェックのアプリケーション例外の場合、またはトランザクションがアプリケーションによってロールバック専用としてマークされている場合(TransactionStatus を設定することにより)、ロールバックをトリガーします。デフォルトでは、TransactionInterceptor は同じように動作しますが、メソッドごとに構成可能なロールバックポリシーを許可します。

6.3.5. トランザクション管理戦略

TransactionTemplate と TransactionInterceptor の両方は、Hibernate アプリケーションの実際のトランザクション処理を PlatformTransactionManager インスタンス(内部で ThreadLocalSession を使用して HibernateTransactionManager (単一の Hibernate SessionFactory の場合)または JtaTransactionManager (コンテナーの JTA サブシステムに委譲)に委譲します。カスタム PlatformTransactionManager 実装を使用することもできます。ネイティブ Hibernate トランザクション管理から JTA への切り替え(アプリケーションの特定のデプロイの分散トランザクション要件に直面する場合など)は、構成の問題です。Hibernate トランザクションマネージャーを Spring の JTA トランザクション実装に置き換えることができます。トランザクション境界とデータアクセスコードは、どちらも汎用のトランザクション管理 API を使用するため、変更なしで機能します。

複数の Hibernate セッションファクトリにわたる分散トランザクションの場合、JtaTransactionManager をトランザクション戦略として複数の LocalSessionFactoryBean 定義と組み合わせることができます。各 DAO は、対応する Bean プロパティに渡される 1 つの特定の SessionFactory 参照を取得します。基礎となるすべての JDBC データソースがトランザクションコンテナーのものである場合、ビジネスサービスは、JtaTransactionManager を戦略として使用している限り、特に考慮せずに、任意の数の DAO と任意の数のセッションファクトリにまたがってトランザクションを区別できます

HibernateTransactionManager と JtaTransactionManager はどちらも、コンテナー固有のトランザクションマネージャールックアップまたは JCA コネクター(EJB を使用してトランザクションを開始しない場合)を使用せずに、Hibernate で適切な JVM レベルのキャッシュ処理を可能にします。

HibernateTransactionManager は、Hibernate JDBC Connection を特定の DataSource のプレーンな JDBC アクセスコードにエクスポートできます。この機能により、Hibernate と JDBC データが混在し、JTA なしで完全にアクセスできる高レベルのトランザクション境界設定が可能になります。ただし、1 つのデータベースにのみアクセスできます。LocalSessionFactoryBean クラスの dataSource プロパティを介して DataSource で渡された SessionFactory をセットアップした場合、HibernateTransactionManager は Hibernate トランザクションを JDBC トランザクションとして自動的に公開します。または、HibernateTransactionManager クラスの dataSource プロパティを介して、トランザクションが公開されることになっている DataSource を明示的に指定できます。

6.3.6. コンテナー管理のリソースとローカルで定義されたリソースの比較

アプリケーションコードの 1 行を変更することなく、コンテナー管理の JNDI SessionFactory とローカルで定義された JNDI SessionFactory を切り替えることができます。リソース定義をコンテナー内に保持するか、アプリケーション内でローカルに保持するかは、主に使用するトランザクション戦略の問題です。Spring で定義されたローカル SessionFactory と比較して、手動で登録された JNDI SessionFactory は利点を提供しません。Hibernate の JCA コネクターを介して SessionFactory をデプロイすると、Java EE サーバーの管理インフラストラクチャに参加する付加価値が提供されますが、それ以上の実際の価値は追加されません。

Spring のトランザクションサポートはコンテナーにバインドされていません。JTA 以外の戦略で構成されている場合、トランザクションサポートはスタンドアロン環境またはテスト環境でも機能します。特に、単一データベーストランザクションの典型的な場合、Spring の単一リソースのローカルトランザクションサポートは、JTA に代わる軽量で強力な代替手段です。ローカル EJB ステートレスセッション Bean を使用してトランザクションを駆動する場合、単一のデータベースにのみアクセスし、ステートレスセッション Bean のみを使用してコンテナー管理トランザクションを介して宣言トランザクションを提供する場合でも、EJB コンテナーと JTA の両方に依存します。プログラムで JTA を直接使用するには、Java EE 環境も必要です。JTA は、JTA 自体および JNDI DataSource インスタンスに関して、コンテナーの依存関係のみを含みません。Spring 以外の JTA 駆動型 Hibernate トランザクションの場合、Hibernate JCA コネクターまたは追加の Hibernate トランザクションコードを、適切な JVM レベルのキャッシュ用に設定された TransactionManagerLookup とともに使用する必要があります。

単一のデータベースにアクセスする場合、ローカル駆動の Hibernate SessionFactory と同様に、ローカル JDBC DataSource と同様に、Spring-driven トランザクションは機能します。トランザクション要件を分散している場合にのみ、Spring の JTA トランザクション戦略を使用する必要があります。JCA コネクターには、コンテナー固有のデプロイステップ、および(明らかに)最初の JCA サポートが必要です。この構成では、ローカルリソース定義と Spring 駆動型トランザクションを使用した単純な Web アプリケーションをデプロイするよりも多くの作業が必要です。また、たとえば、JCA を提供しない WebLogic Express を使用する場合は、コンテナーの Enterprise エディションが必要になることがよくあります。1 つのデータベースにまたがるローカルリソースとトランザクションを備えた Spring アプリケーションは、Tomcat、Resin、またはプレーン Jetty などの Java EE Web コンテナー(JTA、JCA、または EJB なし)で動作します。さらに、デスクトップアプリケーションまたはテストスイートでこのような中間層を簡単に再利用できます。

EJB を使用しない場合は、すべてを考慮して、ローカル SessionFactory セットアップと Spring の HibernateTransactionManager または JtaTransactionManager を使用してください。コンテナーデプロイの不便さなしに、適切なトランザクション JVM レベルのキャッシュや分散トランザクションなど、すべての利点が得られます。JCA コネクターを介した Hibernate SessionFactory の JNDI 登録は、EJB と組み合わせて使用する場合にのみ価値を追加します。

6.3.7. Hibernate を使用した偽のアプリケーションサーバー警告

非常に厳密な XADataSource 実装を備えた一部の JTA 環境(現在、一部の WebLogic サーバーおよび WebSphere バージョン)では、その環境の JTA トランザクションマネージャーに関係なく Hibernate が構成されている場合、誤った警告または例外がアプリケーションサーバーログに表示されることがあります。これらの警告または例外は、おそらくトランザクションがアクティブでなくなったために、アクセスされている接続が無効であるか、JDBC アクセスが無効であることを示しています。例として、WebLogic からの実際の例外を次に示します。

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

もう 1 つの一般的な問題は、JTA トランザクション後の接続リークであり、Hibernate セッション(および潜在的に基礎となる JDBC 接続)が適切に閉じられません。

このような課題は、Hibernate に(Spring とともに)同期する JTA トランザクションマネージャーを認識させることで解決できます。これを行うには、2 つのオプションがあります。

  • Spring JtaTransactionManager Bean を Hibernate セットアップに渡します。最も簡単な方法は、LocalSessionFactoryBean Bean の jtaTransactionManager プロパティへの Bean 参照です(Hibernate トランザクション設定を参照)。次に、Spring は、対応する JTA 戦略を Hibernate で使用できるようにします。

  • Hibernate の JTA 関連のプロパティ、特に LocalSessionFactoryBean の「hibernateProperties」で「hibernate.transaction.coordinator_class」、「hibernate.connection.handling_mode」、場合によっては「hibernate.transaction.jta.platform」を明示的に構成することもできます(Hibernate のマニュアルを参照)。これらのプロパティの詳細については)。

このセクションの残りの部分では、Hibernate が JTA PlatformTransactionManager を認識している場合と認識していない場合に発生するイベントのシーケンスについて説明します。

Hibernate が JTA トランザクションマネージャーを認識して設定されていない場合、JTA トランザクションがコミットすると次のイベントが発生します。

  • JTA トランザクションがコミットします。

  • Spring の JtaTransactionManager は JTA トランザクションに同期されているため、JTA トランザクションマネージャーによって afterCompletion コールバックを通じてコールバックされます。

  • その他のアクティビティでは、この同期化によって Spring から Hibernate へのコールバックが Hibernate の afterTransactionCompletion コールバック (Hibernate キャッシュのクリアに使用) を介してトリガーされ、その後 Hibernate セッションで明示的な close() コールが行われて、Hibernate が JDBC 接続の close() を試行するようになります。

  • 一部の環境では、トランザクションがすでにコミットされているため、アプリケーションサーバーは Connection が使用可能であると見なしなくなるため、この Connection.close() 呼び出しは警告またはエラーをトリガーします。

Hibernate が JTA トランザクションマネージャーを認識して設定されている場合、JTA トランザクションがコミットすると次のイベントが発生します。

  • JTA トランザクションをコミットする準備ができました。

  • Spring の JtaTransactionManager は JTA トランザクションに同期されるため、トランザクションは JTA トランザクションマネージャーによって beforeCompletion コールバックを通じてコールバックされます。

  • Spring は、Hibernate 自体が JTA トランザクションに同期され、前のシナリオとは異なる動作をすることを認識しています。特に、Hibernate のトランザクションリソース管理と連携しています。

  • JTA トランザクションがコミットします。

  • Hibernate は JTA トランザクションに同期されるため、トランザクションは JTA トランザクションマネージャーによって afterCompletion コールバックを介してコールバックされ、そのキャッシュを適切にクリアできます。

6.4. JPA

org.springframework.orm.jpa パッケージで利用可能な Spring JPA は、Hibernate との統合と同様の方法で Java Persistence API: Oracle (英語) の包括的なサポートを提供すると同時に、追加機能を提供するための基礎となる実装を認識します。

6.4.1. Spring 環境での JPA セットアップの 3 つのオプション

Spring JPA サポートは、エンティティマネージャーを取得するためにアプリケーションで使用される JPA EntityManagerFactory をセットアップする 3 つの方法を提供します。

LocalEntityManagerFactoryBean を使用する

このオプションは、スタンドアロンアプリケーションや統合テストなどの単純なデプロイ環境でのみ使用できます。

LocalEntityManagerFactoryBean は、アプリケーションがデータアクセスに JPA のみを使用する単純なデプロイ環境に適した EntityManagerFactory を作成します。ファクトリ Bean は、JPA PersistenceProvider 自動検出メカニズム(JPA の Java SE ブートストラップによる)を使用し、ほとんどの場合、永続性ユニット名のみを指定する必要があります。次の XML の例は、このような Bean を構成します。

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    </bean>
</beans>

JPA デプロイのこの形式は、最も単純で最も制限されています。既存の JDBC DataSource Bean 定義を参照することはできません。また、グローバルトランザクションのサポートはありません。さらに、永続クラスのウィービング(バイトコード変換)はプロバイダー固有であり、多くの場合、起動時に指定する特定の JVM エージェントが必要です。このオプションは、JPA 仕様が設計されているスタンドアロンアプリケーションとテスト環境にのみ十分です。

JNDI から EntityManagerFactory を取得する

Java EE サーバーにデプロイするときに、このオプションを使用できます。カスタム JPA プロバイダーをサーバーにデプロイする方法に関するサーバーのドキュメントを確認し、サーバーのデフォルトとは異なるプロバイダーを許可します。

JNDI から EntityManagerFactory を取得する(たとえば、Java EE 環境で)には、次の例に示すように、XML 構成を変更する必要があります。

<beans>
    <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>

このアクションは、標準の Java EE ブートストラップを前提としています。Java EE サーバーは、永続ユニット(実際には、アプリケーション jar 内の META-INF/persistence.xml ファイル)および Java EE デプロイ記述子内の persistence-unit-ref エントリ(たとえば、web.xml)を自動検出し、それらの永続ユニットの環境命名コンテキストの場所を定義します。

このようなシナリオでは、永続クラスのウィービング(バイトコード変換)を含む永続ユニットデプロイ全体が Java EE サーバーまでです。JDBC DataSource は、META-INF/persistence.xml ファイルの JNDI ロケーションを介して定義されます。EntityManager トランザクションは、サーバーの JTA サブシステムと統合されています。Spring は、取得した EntityManagerFactory を使用するだけで、依存性注入を介してアプリケーションオブジェクトに渡し、永続性ユニットのトランザクションを管理します(通常は JtaTransactionManager を使用)。

同じアプリケーションで複数の永続性ユニットを使用する場合、そのような JNDI 検索永続性ユニットの Bean 名は、アプリケーションが参照するために使用する永続性ユニット名と一致する必要があります(たとえば、@PersistenceUnit および @PersistenceContext アノテーション)。

LocalContainerEntityManagerFactoryBean を使用する

このオプションは、Spring ベースのアプリケーション環境で JPA の全機能に使用できます。これには、Tomcat などの Web コンテナー、スタンドアロンアプリケーション、および高度な永続性要件を備えた統合テストが含まれます。

Hibernate セットアップを具体的に構成する場合、直接の代替手段は、プレーンな JPA LocalContainerEntityManagerFactoryBean の代わりにネイティブ Hibernate LocalSessionFactoryBean をセットアップして、JPA アクセスコードおよびネイティブ Hibernate アクセスコードと相互作用させることです。詳細については、JPA 対話用のネイティブ Hibernate セットアップを参照してください。

LocalContainerEntityManagerFactoryBean は EntityManagerFactory 構成を完全に制御し、きめ細かいカスタマイズが必要な環境に適しています。LocalContainerEntityManagerFactoryBean は、persistence.xml ファイル、提供された dataSourceLookup 戦略、および指定された loadTimeWeaver に基づいて PersistenceUnitInfo インスタンスを作成します。JNDI の外部でカスタムデータソースを操作し、ウィービングプロセスを制御することができます。次の例は、LocalContainerEntityManagerFactoryBean の一般的な Bean 定義を示しています。

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
    </bean>
</beans>

次の例は、典型的な persistence.xml ファイルを示しています。

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>
<exclude-unlisted-classes/> ショートカットは、アノテーション付きエンティティクラスのスキャンが発生しないことを示しています。明示的な「true」値(<exclude-unlisted-classes>true</exclude-unlisted-classes/>)もスキャンなしを意味します。<exclude-unlisted-classes>false</exclude-unlisted-classes/> はスキャンをトリガーします。ただし、エンティティクラススキャンを実行する場合は、exclude-unlisted-classes 要素を省略することをお勧めします。

LocalContainerEntityManagerFactoryBean の使用は、最も強力な JPA セットアップオプションであり、アプリケーション内での柔軟なローカル構成を可能にします。既存の JDBC DataSource へのリンクをサポートし、ローカルトランザクションとグローバルトランザクションの両方をサポートします。ただし、永続性プロバイダーがバイトコード変換を要求する場合、ウィービング対応のクラスローダーの可用性など、ランタイム環境にも要件が課されます。

このオプションは、Java EE サーバーの組み込み JPA 機能と競合する場合があります。完全な Java EE 環境では、JNDI から EntityManagerFactory を取得することを検討してください。または、LocalContainerEntityManagerFactoryBean 定義でカスタム persistenceXmlLocation を指定し(META-INF/my-persistence.xml など)、アプリケーション jar ファイルにその名前の記述子のみを含めます。Java EE サーバーはデフォルトの META-INF/persistence.xml ファイルのみを探すため、このようなカスタム永続性ユニットを無視し、Spring 駆動の JPA セットアップとの競合を事前に回避します。(これは、たとえば Resin 3.1, に適用されます。)

ロード時のウィービングはいつ必要ですか?

すべての JPA プロバイダーが JVM エージェントを必要とするわけではありません。Hibernate はそうではない例です。プロバイダーがエージェントを必要としない場合、またはカスタムコンパイラーや Ant タスクを介してビルド時に拡張機能を適用するなど、他の代替手段がある場合は、ロード時ウィーバーを使用しないでください。

LoadTimeWeaver インターフェースは、環境が Web コンテナーであるかアプリケーションサーバーであるかに応じて、JPA ClassTransformer インスタンスを特定の方法でプラグインできる Spring 提供のクラスです。通常、エージェント: Oracle (英語) を介した ClassTransformers のフックは効率的ではありません。エージェントは仮想マシン全体に対して動作し、ロードされているすべてのクラスをインスペクションします。これは通常、本番サーバー環境では望ましくありません。

Spring は、さまざまな環境に多数の LoadTimeWeaver 実装を提供し、ClassTransformer インスタンスを各 VM にではなく、各クラスローダーにのみ適用できるようにします。

LoadTimeWeaver の実装とそのセットアップに関する詳細なインサイトについては、AOP の章の Spring の構成を参照してください。汎用またはさまざまなプラットフォーム(Tomcat、JBoss、WebSphere など)にカスタマイズされています。

Spring の構成に従って、context:load-time-weaver XML エレメントの @EnableLoadTimeWeaving アノテーションを使用して、コンテキスト全体の LoadTimeWeaver を構成できます。このようなグローバルウィーバーは、すべての JPA LocalContainerEntityManagerFactoryBean インスタンスによって自動的に取得されます。次の例は、ロードタイムウィーバーを設定し、プラットフォームの自動検出(たとえば、Tomcat のウィービング対応クラスローダーまたは Spring の JVM エージェント)と、ウィーバーをすべてのウィーバー対応 Bean に自動伝播する推奨する方法を示しています。

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

ただし、次の例に示すように、必要に応じて、loadTimeWeaver プロパティを使用して専用のウィーバーを手動で指定できます。

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    </property>
</bean>

この手法を使用することにより、LTW がどのように構成されていても、エージェントを必要とせずに、計測に依存する JPA アプリケーションをターゲットプラットフォーム(たとえば、Tomcat)で実行できます。JPA トランスフォーマーはクラスローダーレベルでのみ適用され、相互に分離されているため、ホスティングアプリケーションが異なる JPA 実装に依存している場合、これは特に重要です。

複数の永続性ユニットの処理

複数の永続性ユニットの場所(たとえば、クラスパスのさまざまな JAR に格納されている)に依存するアプリケーションの場合、Spring は PersistenceUnitManager を提供して、中央リポジトリとして機能し、永続性ユニットの検出プロセスを回避します。デフォルトの実装では、複数の場所を指定できます。これらの場所は解析され、後で永続性ユニット名を通じて取得されます。(デフォルトでは、META-INF/persistence.xml ファイルのクラスパスが検索されます)次の例では、複数の場所を構成しています。

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
            <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
            <value>classpath:/my/package/**/custom-persistence.xml</value>
            <value>classpath*:META-INF/persistence.xml</value>
        </list>
    </property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        </map>
    </property>
    <!-- if no datasource is specified, use this one -->
    <property name="defaultDataSource" ref="remoteDataSource"/>
</bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="persistenceUnitName" value="myCustomUnit"/>
</bean>

デフォルトの実装では、PersistenceUnitInfo インスタンスを(JPA プロバイダーにフィードする前に)宣言的に(すべてのホストユニットに影響するプロパティを介して)またはプログラムにより(永続ユニットの選択を可能にする PersistenceUnitPostProcessor を介して)カスタマイズできます。PersistenceUnitManager が指定されていない場合、LocalContainerEntityManagerFactoryBean によって内部的に作成され使用されます。

バックグラウンドブートストラップ

LocalContainerEntityManagerFactoryBean は、次の例に示すように、bootstrapExecutor プロパティを介したバックグラウンドブートストラップをサポートしています。

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="bootstrapExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
    </property>
</bean>

実際の JPA プロバイダーのブートストラップは、指定されたエグゼキューターに渡され、その後、並列で実行されて、アプリケーションのブートストラップスレッドに渡されます。公開された EntityManagerFactory プロキシは、他のアプリケーションコンポーネントに挿入でき、EntityManagerFactoryInfo 構成インスペクションに応答することさえできます。ただし、実際の JPA プロバイダーが他のコンポーネント(たとえば、createEntityManager の呼び出し)によってアクセスされると、それらの呼び出しは、バックグラウンドのブートストラップが完了するまでブロックされます。特に、Spring Data JPA を使用する場合は、リポジトリの遅延ブートストラップも必ず設定してください。

6.4.2. JPA に基づいた DAO の実装: EntityManagerFactory および EntityManager

EntityManagerFactory インスタンスはスレッドセーフですが、EntityManager インスタンスはそうではありません。挿入された JPA EntityManager は、JPA 仕様で定義されているように、アプリケーションサーバーの JNDI 環境からフェッチされた EntityManager のように動作します。すべての呼び出しを現在のトランザクション EntityManager に委譲します(存在する場合)。それ以外の場合、操作ごとに新しく作成された EntityManager にフォールバックし、事実上、その使用をスレッドセーフにします。

注入された EntityManagerFactory または EntityManager を使用することによって、Spring の依存関係なしに単純な JPA に対してコードを書くことができます。Spring は、PersistenceAnnotationBeanPostProcessor が有効になっている場合、@PersistenceUnit および @PersistenceContext アノテーションをフィールドレベルとメソッドレベルの両方で認識できます。次の例は、@PersistenceUnit アノテーションを使用する単純な JPA DAO 実装を示しています。

Java
public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public Collection loadProductsByCategory(String category) {
        try (EntityManager em = this.emf.createEntityManager()) {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
    }
}
Kotlin
class ProductDaoImpl : ProductDao {

    private lateinit var emf: EntityManagerFactory

    @PersistenceUnit
    fun setEntityManagerFactory(emf: EntityManagerFactory) {
        this.emf = emf
    }

    fun loadProductsByCategory(category: String): Collection<*> {
        val em = this.emf.createEntityManager()
        val query = em.createQuery("from Product as p where p.category = ?1");
        query.setParameter(1, category);
        return query.resultList;
    }
}

上記の DAO は Spring に依存せず、Spring アプリケーションコンテキストにうまく適合します。さらに、次の Bean 定義の例が示すように、DAO はアノテーションを利用してデフォルトの EntityManagerFactory の挿入を要求します。

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

PersistenceAnnotationBeanPostProcessor を明示的に定義する代わりに、アプリケーションコンテキスト設定で Spring context:annotation-config XML 要素を使用することを検討してください。これにより、CommonAnnotationBeanPostProcessor などを含むすべての Spring 標準ポストプロセッサがアノテーションベースの構成に自動的に登録されます。

次の例を考えてみましょう。

<beans>

    <!-- post-processors for all standard config annotations -->
    <context:annotation-config/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

このような DAO の主な問題は、ファクトリを通じて常に新しい EntityManager を作成することです。これを回避するには、トランザクション EntityManager (実際のトランザクション EntityManager の共有のスレッドセーフプロキシであるため、「共有 EntityManager」とも呼ばれます)をファクトリの代わりに挿入するようにリクエストします。次の例は、そのメソッドを示しています。

Java
public class ProductDaoImpl implements ProductDao {

    @PersistenceContext
    private EntityManager em;

    public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return query.getResultList();
    }
}
Kotlin
class ProductDaoImpl : ProductDao {

    @PersistenceContext
    private lateinit var em: EntityManager

    fun loadProductsByCategory(category: String): Collection<*> {
        val query = em.createQuery("from Product as p where p.category = :category")
        query.setParameter("category", category)
        return query.resultList
    }
}

@PersistenceContext アノテーションには、type と呼ばれるオプション属性があり、デフォルトは PersistenceContextType.TRANSACTION です。このデフォルトを使用して、共有 EntityManager プロキシを受信できます。代替の PersistenceContextType.EXTENDED は、まったく異なる問題です。これにより、いわゆる拡張 EntityManager が生成されます。これはスレッドセーフではないため、Spring 管理のシングルトン Bean など、同時にアクセスされるコンポーネントで使用しないでください。拡張 EntityManager インスタンスは、たとえばセッションに存在するステートフルコンポーネントでのみ使用されることになっています。EntityManager のライフサイクルは現在のトランザクションに関連付けられておらず、アプリケーションに完全に依存しています。

メソッドおよびフィールドレベルの注入

クラス内のフィールドまたはメソッドに依存性注入(@PersistenceUnit や @PersistenceContext など)を示すアノテーションを適用できます。「メソッドレベルの注入」および「フィールドレベルの注入」という表現になります。フィールドレベルのアノテーションは簡潔で使いやすく、メソッドレベルのアノテーションは注入された依存関係のさらなる処理を可能にします。どちらの場合も、メンバーの可視性(パブリック、保護、またはプライベート)は重要ではありません。

クラスレベルのアノテーションはどうですか?

Java EE プラットフォームでは、リソースの注入ではなく、依存関係の宣言に使用されます。

注入された EntityManager は、Spring で管理されます(進行中のトランザクションを認識します)。新しい DAO 実装では、EntityManagerFactory ではなく EntityManager のメソッドレベルの注入を使用しますが、アノテーションの使用により、アプリケーションコンテキスト XML を変更する必要はありません。

この DAO スタイルの主な利点は、Java Persistence API のみに依存することです。Spring クラスのインポートは必要ありません。さらに、JPA アノテーションが理解されると、注入は Spring コンテナーによって自動的に適用されます。これは、非侵襲性の観点から魅力的であり、JPA 開発者にとってより自然に感じることができます。

6.4.3. Spring-driven JPA トランザクション

Spring の宣言的なトランザクションサポートの詳細を網羅するために、まだ読んでいない場合は宣言的なトランザクション管理を読むことを強くお勧めします。

JPA の推奨戦略は、JPA のネイティブトランザクションサポートによるローカルトランザクションです。Spring の JpaTransactionManager は、通常の JDBC 接続プール(XA 要件なし)に対して、ローカル JDBC トランザクション(トランザクション固有の分離レベルやリソースレベルの読み取り専用最適化など)で知られている多くの機能を提供します。

Spring JPA では、登録された JpaDialect が基盤となる JDBC Connection の取得をサポートしている場合、構成された JpaTransactionManager が JPA トランザクションを同じ DataSource にアクセスする JDBC アクセスコードに公開することもできます。Spring は、EclipseLink および Hibernate JPA 実装のダイアレクトを提供します。JpaDialect メカニズムの詳細については、次のセクションを参照してください。

直接の代替手段として、Spring のネイティブ HibernateTransactionManager は、JPA アクセスコードと対話し、いくつかの Hibernate 仕様に適応し、JDBC 対話を提供することができます。これは、LocalSessionFactoryBean セットアップと組み合わせると特に意味があります。詳細については、JPA インタラクション用のネイティブ Hibernate セットアップを参照してください。

6.4.4. JpaDialect および JpaVendorAdapter を理解する

高度な機能として、JpaTransactionManager および AbstractEntityManagerFactoryBean のサブクラスにより、カスタム JpaDialect を jpaDialect Bean プロパティに渡すことができます。JpaDialect の実装により、通常はベンダー固有の方法で、Spring でサポートされる次の高度な機能を有効にできます。

  • 特定のトランザクションセマンティクスの適用 (カスタム分離レベルやトランザクションタイムアウトなど)

  • トランザクション JDBC Connection の取得 (JDBC ベースの DAO への露出用)

  • PersistenceExceptions から Spring DataAccessExceptions への高度な翻訳

これは、特別なトランザクションセマンティクスおよび例外の高度な変換に特に役立ちます。デフォルトの実装(DefaultJpaDialect)は特別な機能を提供しません。前述の機能が必要な場合は、適切なダイアレクトを指定する必要があります。

主に Spring のフル機能の LocalContainerEntityManagerFactoryBean セットアップのためのさらに広範なプロバイダー適応機能として、JpaVendorAdapter は JpaDialect の機能を他のプロバイダー固有のデフォルトと組み合わせます。HibernateJpaVendorAdapter または EclipseLinkJpaVendorAdapter を指定することは、それぞれ Hibernate または EclipseLink 用に EntityManagerFactory セットアップを自動構成する最も便利な方法です。これらのプロバイダーアダプターは、主に Spring 駆動のトランザクション管理で使用するために設計されていることに注意してください(つまり、JpaTransactionManager で使用するため)。

操作の詳細と、Spring の JPA サポート内での使用方法については、JpaDialect (Javadoc) および JpaVendorAdapter (Javadoc) javadoc を参照してください。

6.4.5. JTA Transaction Management を使用した JPA のセットアップ

JpaTransactionManager の代替として、Spring は、Java EE 環境または Atomikos などのスタンドアロンのトランザクションコーディネーターと JTA を介したマルチリソーストランザクションの調整も可能にします。JpaTransactionManager の代わりに Spring の JtaTransactionManager を選択する以外に、さらにいくつかの手順を実行する必要があります。

  • 基盤となる JDBC 接続プールは XA 対応であり、トランザクションコーディネーターと統合されている必要があります。これは通常、Java EE 環境では簡単であり、JNDI を介して異なる種類の DataSource を公開します。詳細については、アプリケーションサーバーのドキュメントを参照してください。同様に、スタンドアロンのトランザクションコーディネーターには、通常、特別な XA 統合 DataSource バリアントが付属しています。もう一度、そのドキュメントを確認してください。

  • JPA EntityManagerFactory セットアップは、JTA 用に構成する必要があります。これはプロバイダー固有であり、通常は LocalContainerEntityManagerFactoryBean で jpaProperties として指定される特別なプロパティを使用します。Hibernate の場合、これらのプロパティはバージョン固有ですらあります。詳細については、Hibernate のドキュメントを参照してください。

  • Spring の HibernateJpaVendorAdapter は、接続解放モード on-close などの特定の Spring 指向のデフォルトを適用します。これは、Hibernate 5.0 では Hibernate 自身のデフォルトと一致しますが、Hibernate 5.1+. ではこれ以上一致しません。JTA セットアップの場合、永続性ユニットのトランザクションタイプを「JTA」として宣言してください。または、Hibernate 5.2 の hibernate.connection.handling_mode プロパティを DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT に設定して、Hibernate 自体のデフォルトを復元します。関連する注記については、Hibernate を使用した偽のアプリケーションサーバー警告を参照してください。

  • または、アプリケーションサーバー自体から EntityManagerFactory を取得することを検討してください(つまり、ローカルで宣言された LocalContainerEntityManagerFactoryBean の代わりに JNDI ルックアップを使用して)。サーバーが提供する EntityManagerFactory では、サーバー構成で特別な定義が必要になる場合があります(デプロイの移植性が低下します)が、サーバーの JTA 環境用にセットアップされます。

6.4.6. JPA 相互作用のためのネイティブ Hibernate セットアップおよびネイティブ Hibernate トランザクション

HibernateTransactionManager と組み合わせたネイティブ LocalSessionFactoryBean セットアップにより、@PersistenceContext およびその他の JPA アクセスコードとの対話が可能になります。Hibernate SessionFactory は現在 JPA の EntityManagerFactory インターフェースをネイティブに実装しており、Hibernate Session ハンドルはネイティブに JPA EntityManager です。Spring の JPA サポート機能は、ネイティブ Hibernate セッションを自動的に検出します。

このようなネイティブ Hibernate セットアップは、多くのシナリオで標準の JPA LocalContainerEntityManagerFactoryBean と JpaTransactionManager の組み合わせの代替として機能し、同じローカルトランザクション内で @PersistenceContext EntityManager の隣の SessionFactory.getCurrentSession() (および HibernateTemplate)との相互作用を可能にします。このようなセットアップは、JPA ブートストラップ契約に制約されないため、Hibernate の統合を強化し、構成の柔軟性を高めます。

Spring のネイティブ Hibernate セットアップはさらに多くの機能(たとえば、カスタム Hibernate Integrator セットアップ、Hibernate 5.3 Bean コンテナー統合、読み取り専用トランザクションの強力な最適化など)を提供するため、このようなシナリオでは HibernateJpaVendorAdapter 構成は必要ありません。最後に重要なことですが、LocalSessionFactoryBuilder を介してネイティブ Hibernate セットアップを表現し、@Bean スタイルの構成とシームレスに統合することもできます(FactoryBean は含まれません)。

LocalSessionFactoryBean および LocalSessionFactoryBuilder は、JPA LocalContainerEntityManagerFactoryBean と同様に、バックグラウンドブートストラップをサポートしています。はじめにバックグラウンドブートストラップを参照してください。

LocalSessionFactoryBean では、これは bootstrapExecutor プロパティを通じて利用できます。プログラムによる LocalSessionFactoryBuilder では、オーバーロードされた buildSessionFactory メソッドはブートストラップエグゼキューター引数を取ります。

7. オブジェクト XML マッパーを使用した XML のマーシャリング

7.1. 導入

この章では、Spring のオブジェクト XML マッピングのサポートについて説明します。オブジェクト -XML マッピング(略して O-X マッピング)は、XML ドキュメントをオブジェクトに変換したり、オブジェクトから XML ドキュメントを変換したりする行為です。この変換プロセスは、XML マーシャリングまたは XML 直列化とも呼ばれます。この章では、これらの用語を同じ意味で使用します。

O-X マッピングのフィールドでは、マーシャラーはオブジェクト(グラフ)を XML に直列化する責任があります。同様に、アンマーシャラーは XML をオブジェクトグラフにデシリアライズします。この XML は、DOM ドキュメント、入力または出力ストリーム、または SAX ハンドラーの形式をとることができます。

O/X マッピングのニーズに Spring を使用する利点のいくつかは次のとおりです。

7.1.1. 設定が簡単

Spring の Bean ファクトリにより、JAXB コンテキスト、JiBX バインディングファクトリなどを構築する必要なく、マーシャラーを簡単に構成できます。アプリケーションコンテキストの他の Bean と同様に、マーシャラーを構成できます。さらに、多くのマーシャラーが XML 名前空間ベースの構成を使用できるため、構成がさらに簡単になります。

7.1.2. 一貫したインターフェース

Spring の O-X マッピングは、Marshaller (Javadoc) Unmarshaller (Javadoc) の 2 つのグローバルインターフェースを介して動作します。これらの抽象化により、O-X マッピングフレームワークを比較的簡単に切り替えることができ、マーシャリングを行うクラスをほとんどまたはまったく変更する必要がありません。このアプローチには、ミックスアンドマッチアプローチ(たとえば、JAXB を使用して実行されるマーシャリングと XStream によって実行されるマーシャリング)を使用して XML マーシャリングを非侵入的な方法で実行できるという追加の利点があります。技術。

7.1.3. 一貫した例外階層

Spring は、基になる O-X マッピングツールの例外から、XmlMappingException をルート例外とする独自の例外階層への変換を提供します。これらのランタイム例外は元の例外をラップするため、情報は失われません。

7.2. Marshaller および Unmarshaller

で記述されていたように導入、マーシャラーは XML にオブジェクトを直列化し、アンマーシャラーオブジェクトに XML ストリームをデシリアライズ。このセクションでは、この目的で使用される 2 つの Spring インターフェースについて説明します。

7.2.1. Marshaller を理解する

Spring は、org.springframework.oxm.Marshaller インターフェースの背後にあるすべてのマーシャリング操作を抽象化します。その主なメソッドは次のとおりです。

Java
public interface Marshaller {

    /**
     * Marshal the object graph with the given root into the provided Result.
     */
    void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}
Kotlin
interface Marshaller {

    /**
    * Marshal the object graph with the given root into the provided Result.
    */
    @Throws(XmlMappingException::class, IOException::class)
    fun marshal(
            graph: Any,
            result: Result
    )
}

Marshaller インターフェースには、特定のオブジェクトを特定の javax.xml.transform.Result にマーシャリングする 1 つのメインメソッドがあります。結果は、基本的に XML 出力の抽象化を表すタグ付けインターフェースです。次の表に示すように、具体的な実装はさまざまな XML 表現をラップします。

結果の実装 XML 表現をラップする

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.Filejava.io.OutputStream、または java.io.Writer

marshal() メソッドは最初のパラメーターとしてプレーンオブジェクトを受け入れますが、ほとんどの Marshaller 実装は任意のオブジェクトを処理できません。代わりに、オブジェクトクラスは、マッピングファイルにマッピングされるか、アノテーションでマークされるか、マーシャラーに登録されるか、共通の基本クラスを持つ必要があります。O-X テクノロジーがこれをどのように管理するかを決定するには、この章の後のセクションを参照してください。

7.2.2. Unmarshaller を理解する

Marshaller と同様に、次のリストに示す org.springframework.oxm.Unmarshaller インターフェースがあります。

Java
public interface Unmarshaller {

    /**
     * Unmarshal the given provided Source into an object graph.
     */
    Object unmarshal(Source source) throws XmlMappingException, IOException;
}
Kotlin
interface Unmarshaller {

    /**
    * Unmarshal the given provided Source into an object graph.
    */
    @Throws(XmlMappingException::class, IOException::class)
    fun unmarshal(source: Source): Any
}

このインターフェースには、指定された javax.xml.transform.Source (XML 入力抽象化)から読み取り、読み取ったオブジェクトを返す 1 つのメソッドもあります。Result と同様に、Source は 3 つの具体的な実装を持つタグ付けインターフェースです。次の表に示すように、それぞれが異なる XML 表現をラップします。

ソース実装 XML 表現をラップする

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSource および org.xml.sax.XMLReader

StreamSource

java.io.Filejava.io.InputStream、または java.io.Reader

2 つの個別のマーシャリングインターフェース(Marshaller および Unmarshaller)がありますが、Spring-WS のすべての実装は両方を 1 つのクラスに実装します。これは、1 つのマーシャラークラスを接続し、applicationContext.xml でマーシャラーとアンマーシャラーの両方として参照できることを意味します。

7.2.3. XmlMappingException を理解する

Spring は、基になる O-X マッピングツールからの例外を、XmlMappingException をルート例外として独自の例外階層に変換します。これらのランタイム例外は元の例外をラップするため、情報は失われません。

さらに、MarshallingFailureException と UnmarshallingFailureException は、基になる O-X マッピングツールがそうしない場合でも、マーシャリング操作とアンマーシャリング操作を区別します。

O-X マッピングの例外階層を次の図に示します。

oxm exceptions

7.3. Marshaller および Unmarshaller の使用

Spring の OXM は、さまざまな状況で使用できます。次の例では、Spring が管理するアプリケーションの設定を XML ファイルとしてマーシャリングするために使用します。次の例では、単純な JavaBean を使用して設定を表します。

Java
public class Settings {

    private boolean fooEnabled;

    public boolean isFooEnabled() {
        return fooEnabled;
    }

    public void setFooEnabled(boolean fooEnabled) {
        this.fooEnabled = fooEnabled;
    }
}
Kotlin
class Settings {
    var isFooEnabled: Boolean = false
}

アプリケーションクラスは、この Bean を使用して設定を保存します。メインメソッドに加えて、このクラスには 2 つのメソッドがあります。saveSettings() は設定 Bean を settings.xml という名前のファイルに保存し、loadSettings() はこれらの設定を再度読み込みます。次の main() メソッドは、Spring アプリケーションコンテキストを構築し、これら 2 つのメソッドを呼び出します。

Java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {

    private static final String FILE_NAME = "settings.xml";
    private Settings settings = new Settings();
    private Marshaller marshaller;
    private Unmarshaller unmarshaller;

    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    public void saveSettings() throws IOException {
        try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
            this.marshaller.marshal(settings, new StreamResult(os));
        }
    }

    public void loadSettings() throws IOException {
        try (FileInputStream is = new FileInputStream(FILE_NAME)) {
            this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
        }
    }

    public static void main(String[] args) throws IOException {
        ApplicationContext appContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Application application = (Application) appContext.getBean("application");
        application.saveSettings();
        application.loadSettings();
    }
}
Kotlin
class Application {

    lateinit var marshaller: Marshaller

    lateinit var unmarshaller: Unmarshaller

    fun saveSettings() {
        FileOutputStream(FILE_NAME).use { outputStream -> marshaller.marshal(settings, StreamResult(outputStream)) }
    }

    fun loadSettings() {
        FileInputStream(FILE_NAME).use { inputStream -> settings = unmarshaller.unmarshal(StreamSource(inputStream)) as Settings }
    }
}

private const val FILE_NAME = "settings.xml"

fun main(args: Array<String>) {
    val appContext = ClassPathXmlApplicationContext("applicationContext.xml")
    val application = appContext.getBean("application") as Application
    application.saveSettings()
    application.loadSettings()
}

Application では、marshaller プロパティと unmarshaller プロパティの両方を設定する必要があります。次の applicationContext.xml を使用してこれを行うことができます。

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="xstreamMarshaller" />
        <property name="unmarshaller" ref="xstreamMarshaller" />
    </bean>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>

このアプリケーションコンテキストでは XStream を使用しますが、この章で後述する他のマーシャラーインスタンスを使用することもできます。デフォルトでは、XStream はそれ以上の構成を必要としないため、Bean 定義はかなり単純であることに注意してください。また、XStreamMarshaller は Marshaller と Unmarshaller の両方を実装しているため、アプリケーションの marshaller と unmarshaller プロパティの両方で xstreamMarshaller Bean を参照できることに注意してください。

このサンプルアプリケーションは、次の settings.xml ファイルを生成します。

<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

7.4. XML 構成名前空間

OXM 名前空間のタグを使用して、マーシャラーをより簡潔に構成できます。これらのタグを使用可能にするには、最初に 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:oxm="http://www.springframework.org/schema/oxm" (1)
xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> (2)
1oxm スキーマを参照します。
2oxm スキーマの場所を指定します。

このスキーマにより、次の要素が利用可能になります。

各タグは、それぞれのマーシャラーのセクションで説明されています。ただし、例として、JAXB2 マーシャラーの構成は次のようになります。

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

7.5. JAXB

JAXB バインディングコンパイラは、W3C XML スキーマを 1 つ以上の Java クラス、jaxb.properties ファイル、場合によってはいくつかのリソースファイルに変換します。JAXB は、アノテーション付きの Java クラスからスキーマを生成する方法も提供します。

Spring は、Marshaller および Unmarshaller で説明されている Marshaller および Unmarshaller インターフェースに従って、JAXB 2.0 API を XML マーシャリング戦略としてサポートします。対応する統合クラスは、org.springframework.oxm.jaxb パッケージにあります。

7.5.1. Jaxb2Marshaller を使用する

Jaxb2Marshaller クラスは、Spring の Marshaller および Unmarshaller インターフェースの両方を実装します。操作するにはコンテキストパスが必要です。contextPath プロパティを設定することにより、コンテキストパスを設定できます。コンテキストパスは、スキーマ派生クラスを含むコロンで区切られた Java パッケージ名のリストです。また、classesToBeBound プロパティを提供します。これにより、マーシャラーがサポートするクラスの配列を設定できます。次の例に示すように、Bean に 1 つ以上のスキーマリソースを指定することにより、スキーマ検証が実行されます。

<beans>
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>org.springframework.oxm.jaxb.Flight</value>
                <value>org.springframework.oxm.jaxb.Flights</value>
            </list>
        </property>
        <property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
    </bean>

    ...

</beans>
XML 構成名前空間

jaxb2-marshaller 要素は、次の例に示すように、org.springframework.oxm.jaxb.Jaxb2Marshaller を構成します。

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

または、class-to-be-bound 子要素を使用して、マーシャラーにバインドするクラスのリストを提供できます。

<oxm:jaxb2-marshaller id="marshaller">
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
    ...
</oxm:jaxb2-marshaller>

次の表に、使用可能な属性を示します。

属性 説明 必須

id

マーシャラーの ID

いいえ

contextPath

JAXB コンテキストパス

いいえ

7.6. JiBX

JiBX フレームワークは、Hibernate が ORM に提供するものと同様のソリューションを提供します。バインディング定義は、Java オブジェクトと XML の変換方法のルールを定義します。バインディングを準備し、クラスをコンパイルした後、JiBX バインディングコンパイラはクラスファイルを強化し、XML からまたはクラスへのクラスのインスタンスの変換を処理するコードを追加します。

JiBX の詳細については、JiBX Web サイト (英語) を参照してください。Spring 統合クラスは org.springframework.oxm.jibx パッケージにあります。

7.6.1. JibxMarshaller を使用する

JibxMarshaller クラスは、Marshaller および Unmarshaller インターフェースの両方を実装します。操作するには、マーシャリングするクラスの名前が必要です。これは、targetClass プロパティを使用して設定できます。オプションで、bindingName プロパティを設定することにより、バインディング名を設定できます。次の例では、Flights クラスをバインドします。

<beans>
    <bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
        <property name="targetClass">org.springframework.oxm.jibx.Flights</property>
    </bean>
    ...
</beans>

JibxMarshaller は単一のクラスに対して構成されます。複数のクラスをマーシャリングする場合、異なる targetClass プロパティ値で複数の JibxMarshaller インスタンスを構成する必要があります。

XML 構成名前空間

次の例に示すように、jibx-marshaller タグは org.springframework.oxm.jibx.JibxMarshaller を構成します。

<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>

次の表に、使用可能な属性を示します。

属性 説明 必須

id

マーシャラーの ID

いいえ

target-class

このマーシャラーのターゲットクラス

はい

bindingName

このマーシャラーが使用するバインディング名

いいえ

7.7. XStream

XStream は、オブジェクトを XML に直列化し、再び戻すためのシンプルなライブラリです。マッピングを必要とせず、クリーンな XML を生成します。

XStream の詳細については、XStream Web サイト (英語) を参照してください。Spring 統合クラスは org.springframework.oxm.xstream パッケージにあります。

7.7.1. XStreamMarshaller を使用する

XStreamMarshaller は設定を必要とせず、アプリケーションコンテキストで直接設定できます。XML をさらにカスタマイズするために、次の例に示すように、クラスにマップされた文字列エイリアスで構成されるエイリアスマップを設定できます。

<beans>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
        <property name="aliases">
            <props>
                <prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
            </props>
        </property>
    </bean>
    ...
</beans>

デフォルトでは、XStream は任意のクラスの非整列化を許可します。これにより、安全でない Java 直列化効果が生じる可能性があります。そのため、XStreamMarshaller を使用して外部ソース(つまり、Web)から XML を非整列化することはお勧めしません。これにより、セキュリティの脆弱性が生じる可能性があります。

XStreamMarshaller を使用して外部ソースから XML を非整列化することを選択した場合、次の例に示すように、XStreamMarshaller で supportedClasses プロパティを設定します。

<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
    <property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
    ...
</bean>

そうすることで、登録されたクラスのみがアンマーシャリングの対象になります。

さらに、カスタムコンバーター (Javadoc) を登録して、サポートされているクラスのみを非整列化できるようにすることができます。サポートするドメインクラスを明示的にサポートするコンバーターに加えて、リストの最後のコンバーターとして CatchAllConverter を追加することもできます。その結果、優先度が低く、セキュリティ上の脆弱性が存在する可能性があるデフォルトの XStream コンバーターは呼び出されません。

XStream は XML 直列化ライブラリであり、データバインディングライブラリではないことに注意してください。ネームスペースのサポートは制限されています。その結果、Web サービス内での使用には適していない。

8. 付録

8.1. XML スキーマ

付録のこのパートには、以下を含むデータアクセス用の XML スキーマがリストされています。

8.1.1. tx スキーマ

tx タグは、Spring のトランザクションの包括的なサポートでこれらすべての Bean を構成します。これらのタグについては、トランザクション管理というタイトルの章で説明しています。

Spring ディストリビューションに同梱されている 'spring-tx.xsd' ファイルをご覧になることを強くお勧めします。このファイルには、Spring のトランザクション構成の XML スキーマが含まれており、属性のデフォルトや同様の情報など、tx 名前空間のさまざまな要素がすべて含まれています。このファイルはインラインでドキュメント化されているため、DRY(Do n ’ t Repeat Yourself)原則に従うために、ここでは情報を繰り返しません。

完全を期すために、tx スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、tx 名前空間のタグを使用できます。

<?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" (1)
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd (2)
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>
1tx 名前空間の使用を宣言します。
2 場所を(他のスキーマの場所とともに)指定します。
多くの場合、tx 名前空間の要素を使用すると、aop 名前空間の要素も使用します(Spring の宣言的なトランザクションサポートは AOP を使用して実装されるため)。上記の XML スニペットには、aop 名前空間の要素を使用できるように、aop スキーマを参照するために必要な関連行が含まれています。

8.1.2. jdbc スキーマ

jdbc 要素を使用すると、組み込みデータベースをすばやく構成したり、既存のデータソースを初期化したりできます。これらの要素は、それぞれ組み込みデータベースのサポートおよび DataSource の初期化でドキュメント化されています。

jdbc スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、jdbc 名前空間の要素を使用できます。

<?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:jdbc="http://www.springframework.org/schema/jdbc" (1)
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> (2)

    <!-- bean definitions here -->

</beans>
1jdbc 名前空間の使用を宣言します。
2 場所を(他のスキーマの場所とともに)指定します。