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

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のトランザクションサポートモデルの利点

Traditionally, Java EE developers have had two choices for transaction management: global or local transactions, both of which have profound limitations. Global and local transaction management is reviewed in the next two sections, followed by a discussion of how the Spring Framework’s transaction management support addresses the limitations of the global and local transaction models.

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トランザクションの抽象化の鍵は、トランザクション戦略の概念です。トランザクション戦略は、org.springframework.transaction.PlatformTransactionManager インターフェースによって定義され、次のリストに示されています。

Java
public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

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

    @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 が実行のスレッドに関連付けられていることです。

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

  • 伝搬: Typically, all code executed within a transaction scope runs in that transaction. However, you can specify the behavior if a transactional method is executed when a transaction context already exists. For example, code can continue running in the existing transaction (the common case), or the existing transaction can be suspended and a new transaction created. Spring offers all of the transaction propagation options familiar from EJB CMT. To read about the semantics of transaction propagation in Spring, see トランザクションの伝播

  • 分離: The degree to which this transaction is isolated from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions?

  • タイムアウト: How long this transaction runs before timing out and being automatically rolled back by the underlying transaction infrastructure.

  • 読み取り専用状態: You can use a read-only transaction when your code reads but does not modify data. Read-only transactions can be a useful optimization in some cases, such as when you use Hibernate.

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

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

Java
public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();
}
Kotlin
interface TransactionStatus : SavepointManager {

    fun isNewTransaction(): Boolean

    fun hasSavepoint(): Boolean

    fun setRollbackOnly()

    fun isRollbackOnly(): Boolean

    fun flush()

    fun isCompleted(): Boolean
}

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

PlatformTransactionManager implementations normally require knowledge of the environment in which they work: JDBC, JTA, Hibernate, and so on. The following examples show how you can define a local PlatformTransactionManager implementation (この場合、プレーン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を使用する場合は、ZZN1を通じて取得したコンテナー DataSourceをSpringの JtaTransactionManagerと組み合わせて使用します。次の例は、JTAおよびJNDIルックアップバージョンがどのようになるかを示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        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スキーマを参照してください。

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

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

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

この場合、txManager Beanは HibernateTransactionManager タイプです。 DataSourceTransactionManagerDataSourceへの参照を必要とするのと同じ方法で、HibernateTransactionManagerSessionFactoryへの参照を必要とします。次の例では、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 を使用する必要があります。

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
JTAを使用する場合、使用するデータアクセステクノロジー(JDBC、Hibernate JPA、またはその他のサポートされるテクノロジー)に関係なく、トランザクションマネージャーの定義は同じように見えるはずです。これは、JTAトランザクションがすべてのトランザクションリソースを登録できるグローバルトランザクションであるためです。

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

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

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

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

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

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

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

例:JDBCの場合、DataSourcegetConnection() メソッドを呼び出す従来の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 を適切な PlatformTransactionManager 実装と組み合わせて使用して、メソッド呼び出しにトランザクションを駆動するAOPプロキシが生成されます。

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

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 PlatformTransactionManager -->
    <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 属性は、トランザクションを駆動する PlatformTransactionManager Bean(この場合は txManager Bean)の名前に設定されます。

ワイヤリングする PlatformTransactionManager のBean名の名前が transactionManagerである場合、トランザクションアドバイス(<tx:advice/>)の transaction-manager 属性を省略できます。接続する PlatformTransactionManager 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())
}

上記のプログラムの実行からの出力は、次のようになります(ZZ4G出力と、DefaultFooServiceクラスのinsertFoo(..)メソッドによってスローされたUnsupportedOperationExceptionからのスタックトレースは、明確にするために切り捨てられています)。

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

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 PlatformTransactionManager 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 PlatformTransactionManager 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 PlatformTransactionManager 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インスタンスをトランザクション化する行。
接続する PlatformTransactionManager のBean名に transactionManagerという名前がある場合、<tx:annotation-driven/> タグの transaction-manager 属性を省略できます。依存関係を注入する PlatformTransactionManager Beanに他の名前がある場合、前の例のように transaction-manager 属性を使用する必要があります。
メソッドの可視性と @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-classfalse の場合、または属性が省略された場合、標準のJDKインターフェースベースのプロキシが作成されます。(さまざまなプロキシタイプの詳細な調査については、プロキシメカニズムを参照してください。)

order

order

Ordered.LOWEST_PRECEDENCE

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

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

enum : Propagation

オプションの伝播設定。

isolation

enum : Isolation

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

timeout

int (粒度の秒)

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

readOnly

boolean

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

rollbackFor

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

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

rollbackForClassName

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

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

noRollbackFor

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

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

noRollbackForClassName

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

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

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

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

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

Java
public class TransactionalService {

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

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

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

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

以下のリストは、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>

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

カスタムショートカットアノテーション

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

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

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}
Kotlin
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional("order")
annotation class OrderTx

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional("account")
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">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

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

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

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

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

</beans>

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

次の例では、前の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">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

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

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

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

    </aop:config>

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

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

</beans>

上記の構成の結果、fooService 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の使用 に従って aspectjmode 属性を指定することです。ここでは、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

  • PlatformTransactionManager 実装。

Springチームは通常、プログラムによるトランザクション管理に TransactionTemplate を推奨しています。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 executes 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. 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 {
    // execute 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 {
    // execute your business logic here
} catch (ex: MyException) {
    txManager.rollback(status)
    throw ex
}

txManager.commit(status)

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_COMMIT, AFTER_COMMIT (デフォルト)、AFTER_ROLLBACK、および AFTER_COMPLETION です。

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

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フレームワークのサポートにおけるさまざまなテンプレートクラスに当てはまります。インターセプターベースのクラスを使用する場合、アプリケーションは SessionFactoryUtilsconvertHibernateAccessException(..) メソッドまたは 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 は、データベースメタデータを最適化して、必要な構成の量を制限します。このアプローチにより、コーディングが簡素化されるため、テーブルまたはプロシージャの名前のみを提供し、列名に一致するパラメータのマップを提供するだけで済みます。これは、データベースが適切なメタデータを提供する場合にのみ機能します。データベースがこのメタデータを提供しない場合、パラメーターの明示的な構成を提供する必要があります。

  • MappingSqlQuery, SqlUpdateStoredProcedureなどのRDBMSオブジェクトでは、データアクセスレイヤーの初期化中に再利用可能でスレッドセーフなオブジェクトを作成する必要があります。このアプローチは、JDOクエリをモデルにしています。JDOクエリでは、クエリ文字列を定義し、パラメータを宣言し、クエリをコンパイルします。これを実行すると、さまざまなパラメーター値を使用してexecuteメソッドを複数回呼び出すことができます。

3.2. パッケージ階層

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

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

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

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

  • support : org.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 = ?",
        new Object[]{1212L}, String.class);
Kotlin
val lastName = this.jdbcTemplate.queryForObject<String>(
        "select last_name from t_actor where id = ?",
        arrayOf(1212L))!!

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

Java
Actor actor = this.jdbcTemplate.queryForObject(
        "select first_name, last_name from t_actor where id = ?",
        new Object[]{1212L},
        new RowMapper<Actor>() {
            public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
                Actor actor = new Actor();
                actor.setFirstName(rs.getString("first_name"));
                actor.setLastName(rs.getString("last_name"));
                return actor;
            }
        });
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",
        new RowMapper<Actor>() {
            public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
                Actor actor = new Actor();
                actor.setFirstName(rs.getString("first_name"));
                actor.setLastName(rs.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 匿名内部クラスに存在する重複を削除し、参照可能な単一のクラス(通常は static ネストクラス)に抽出するのが理にかなっています。必要に応じてDAOメソッドによって。例:次のように上記のコードスニペットを記述する方が良い場合があります。

Java
public List<Actor> findAllActors() {
    return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}

private static final class ActorMapper implements RowMapper<Actor> {

    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
        Actor actor = new Actor();
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }
}
Kotlin
fun findAllActors(): List<Actor> {
    return jdbcTemplate.query("select first_name, last_name from t_actor", ActorMapper())
}

class ActorMapper : RowMapper<Actor> {

    override fun mapRow(rs: ResultSet, rowNum: Int) = Actor(
            rs.getString("first_name"),
            rs.getString("last_name"))
    }
}
JdbcTemplateによる更新(INSERT, UPDATEおよび 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 actor where id = ?",
        Long.valueOf(actorId));
Kotlin
jdbcTemplate.update("delete from 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でクラスにアノテーションを付けます。
2 DataSource setterメソッドに @Autowiredでアノテーションを付けます。
3 DataSourceで新しい 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でクラスにアノテーションを付けます。
2 DataSourceのコンストラクター注入。
3 DataSourceで新しい 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規約(英語) に準拠するクラスのインスタンス)をラップし、ラップされた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ステートメントの実行に必要なコードはごくわずかです。 DataSourceJdbcTemplateが必要です( 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 を最初の引数として受け取り、これが必要な挿入ステートメントの指定方法です。他の引数は KeyHolderで、更新から正常に戻ったときに生成されたキーが含まれています。適切な PreparedStatement を作成するための標準的な単一の方法はありません(メソッドシグネチャーがそうである理由を説明します)。次の例はOracleで機能しますが、他のプラットフォームでは機能しない場合があります。

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

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
    new PreparedStatementCreator() {
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
            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からデータソースを取得するか、サードパーティが提供する接続プール実装を使用して独自のデータソースを構成できます。一般的な実装は、Apache Jakarta Commons DBCPおよびC3P0です。Springディストリビューションの実装は、テストのみを目的としており、プーリングを提供しません。

このセクションでは、Springの DriverManagerDataSource 実装を使用します。いくつかの追加の実装については後で説明します。

DriverManagerDataSource クラスは、プーリングを提供せず、接続に対する複数の要求が行われたときにパフォーマンスが低下するため、テスト目的でのみ使用する必要があります。

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管理トランザクションに参加させることができます。一般に、JdbcTemplateDataSourceUtilsなど、リソース管理用のより高いレベルの抽象化を使用して、独自の新しいコードを作成することをお勧めします。

詳細については、 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 呼び出しで指定した回数と呼ばれます。次の例では、リストのエントリに基づいて 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. オブジェクトのリストを使用したバッチ操作

JdbcTemplateNamedParameterJdbcTemplate の両方が、バッチ更新を提供する代替方法を提供します。特別なバッチインターフェースを実装する代わりに、呼び出しのすべてのパラメーター値をリストとして提供します。フレームワークはこれらの値をループ処理し、内部準備済みステートメント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(SPR)で報告されているように、最新のドライバーバージョンを使用し、spring.jdbc.getParameterType.ignore プロパティを true (JVMシステムプロパティまたはクラスパスのルートの spring.properties ファイル)に設定することを検討する必要があります。16139)。

あるいは、「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,
                new ParameterizedPreparedStatementSetter<Actor>() {
                    public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
                        ps.setString(1, argument.getFirstName());
                        ps.setString(2, argument.getLastName());
                        ps.setLong(3, argument.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番目のアプローチを使用して挿入を実行する場合の主な違いは、idMapに追加せず、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_name, last_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_name, out_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の各行の ResultSetTitle ドメインオブジェクトにマップします。

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の各行の ResultSetGenre ドメインオブジェクトにマップします。

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

    • InputStream : getBlobAsBinaryStream および setBlobAsBinaryStream

  • CLOB

    • String : getClobAsString および setClobAsString

    • InputStream : getClobAsAsciiStream および setClobAsAsciiStream

    • Reader : getClobAsCharacterStream および setClobAsCharacterStream

次の例は、BLOBを作成および挿入する方法を示しています。後で、データベースからそれを読み戻す方法を示します。

この例では、JdbcTemplateAbstractLobCreatingPreparedStatementCallbackの実装を使用しています。 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();
1 lobHandler を渡します(この例では)プレーン 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()
1 lobHandler を渡します(この例では)プレーン DefaultLobHandlerです。
2メソッド setClobAsCharacterStream を使用して、CLOBのコンテンツを渡します。
3メソッド setBlobAsBinaryStream を使用して、BLOBのコンテンツを渡します。

DefaultLobHandler.getLobCreator()から返された LobCreatorsetBlobAsBinaryStream, setClobAsAsciiStreamまたは setClobAsCharacterStream メソッドを呼び出す場合、オプションで contentLength 引数に負の値を指定できます。指定されたコンテンツの長さが負の場合、DefaultLobHandler は、長さパラメーターなしでset-streamメソッドのJDBC 4.0バリアントを使用します。それ以外の場合は、指定された長さをドライバーに渡します。

使用するJDBCドライバーのドキュメントを参照して、コンテンツの長さを指定せずにLOBのストリーミングをサポートしていることを確認してください。

次に、データベースからLOBデータを読み取ります。再び、同じインスタンス変数 lobHandlerDefaultLobHandlerへの参照を持つ 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",
            new SqlReturnType() {
                public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String   ) throws SQLException {
                    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>
1 INITIALIZE_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区切りスクリプトを @@に設定します。
2 db-schema.sql のセパレーターを ;に設定します。

この例では、2つの test-data スクリプトは @@ をステートメント区切り文字として使用し、db-schema.sql のみが ;を使用します。この構成は、デフォルトの区切り文字が @@ であることを指定し、db-schema スクリプトのデフォルトをオーバーライドします。

XML名前空間から取得するよりも多くの制御が必要な場合は、DataSourceInitializer を直接使用して、アプリケーションのコンポーネントとして定義できます。

データベースに依存する他のコンポーネントの初期化

大規模なクラスのアプリケーション(Springコンテキストが開始されるまでデータベースを使用しないアプリケーション)は、データベースイニシャライザーをさらに複雑にすることなく使用できます。アプリケーションがそれらのいずれでもない場合、このセクションの残りを読む必要があるかもしれません。

データベース初期化子は DataSource インスタンスに依存し、その初期化コールバックで提供されるスクリプトを実行します(XML Bean定義の init-method、コンポーネントの @PostConstruct メソッド、または InitializingBeanを実装するコンポーネントの afterPropertiesSet() メソッドに類似)。他のBeanが同じデータソースに依存し、初期化コールバックでデータソースを使用する場合、データがまだ初期化されていないために問題が発生する可能性があります。これの一般的な例は、熱心に初期化され、アプリケーションの起動時にデータベースからデータをロードするキャッシュです。

To get around this issue, you have two options: change your cache initialization strategy to a later phase or ensure that the database initializer is initialized first.

キャッシュの初期化戦略を変更するのは、アプリケーションが制御下にある場合で、そうでない場合は簡単です。これを実装する方法についてのいくつかの提案は次のとおりです。

  • 最初の使用時にキャッシュを遅延初期化して、アプリケーションの起動時間を改善します。

  • キャッシュまたはキャッシュを初期化する別のコンポーネントに Lifecycle または SmartLifecycleを実装させます。アプリケーションコンテキストが起動すると、autoStartup フラグを設定して SmartLifecycle を自動的に起動できます。また、囲んでいるコンテキストで ConfigurableApplicationContext.start() を呼び出して Lifecycle を手動で起動できます。

  • Spring ApplicationEvent または同様のカスタムオブザーバーメカニズムを使用して、キャッシュの初期化をトリガーします。 ContextRefreshedEvent は(すべてのBeanが初期化された後)使用準備ができたときに常にコンテキストによって公開されるため、多くの場合、これは便利なフックです(これが SmartLifecycle のデフォルトの動作です)。

データベース初期化子が最初に初期化されるようにすることも簡単です。これを実装する方法に関するいくつかの提案は次のとおりです。

  • Spring BeanFactoryのデフォルトの動作に依存します。つまり、Beanは登録順に初期化されます。XML構成の一連の <import/> 要素の一般的なプラクティスを採用してアプリケーションモジュールを並べ、データベースとデータベースの初期化が最初にリストされるようにすることで、簡単に調整できます。

  • DataSource とそれを使用するビジネスコンポーネントを分離し、個別の ApplicationContext インスタンスに配置して起動順序を制御します(たとえば、親コンテキストには DataSourceが含まれ、子コンテキストにはビジネスコンポーネントが含まれます)。この構造はSpring Webアプリケーションでは一般的ですが、より一般的に適用できます。

4. オブジェクトリレーショナルマッピング(ORM)データアクセス

このセクションでは、オブジェクトリレーショナルマッピング(ORM)を使用する場合のデータアクセスについて説明します。

4.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を使用したデータへのアクセスの開始ガイドが優れた紹介を提供します。

4.2. ORM統合の一般的な考慮事項

このセクションでは、すべてのORMテクノロジーに適用される考慮事項について説明します。Hibernateセクションでは、詳細を提供し、これらの機能と構成を具体的なコンテキストで示します。

The major goal of Spring’s ORM integration is clear application layering (with any data access and transaction technology) and for loose coupling of application objects — no more business service dependencies on the data access or transaction strategy, no more hard-coded resource lookups, no more hard-to-replace singletons, no more custom service registries. The goal is to have one simple and consistent approach to wiring up application objects, keeping them as reusable and free from container dependencies as possible. All the individual data access features are usable on their own but integrate nicely with Spring’s application context concept, providing XML-based configuration and cross-referencing of plain JavaBean instances that need not be Spring-aware. In a typical Spring application, many important objects are JavaBeans: data access templates, data access objects, transaction managers, business services that use the data access objects and transaction managers, web view resolvers, web controllers that use the business services, and so on.

4.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サポートを提供します。トランザクションサポートの詳細については、トランザクション管理の章を参照してください。

4.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を実装できます。

4.3. Hibernate

Spring環境でのHibernate 5(英語) のカバレッジから始め、それを使用して、SpringがORマッパーの統合に向けて取っているアプローチを示します。このセクションでは、多くの課題を詳細にカバーし、DAO実装のさまざまなバリエーションとトランザクション境界を示します。これらのパターンのほとんどは、サポートされている他のすべてのORMツールに直接変換できます。この章の後半のセクションでは、他のORMテクノロジーについて説明し、簡単な例を示します。

Spring Framework 5.0の時点で、Springは、JPAサポートのためにHibernate ORM 4.3以降を必要とし、ネイティブHibernateセッションAPIに対するプログラミングのためにHibernate ORM 5.0+を必要とします。Hibernateチームは5.1より前のバージョンを維持しておらず、5.3+にすぐに専念することに注意してください。

4.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 は含まれません)。

LocalSessionFactoryBeanLocalSessionFactoryBuilder は両方ともバックグラウンドブートストラップをサポートし、Hibernateの初期化は特定のブートストラップエグゼキューター( SimpleAsyncTaskExecutorなど)のアプリケーションブートストラップスレッドと並行して実行されます。 LocalSessionFactoryBeanでは、これは bootstrapExecutor プロパティを介して利用できます。プログラムによる LocalSessionFactoryBuilderには、ブートストラップエグゼキューター引数を取るオーバーロード buildSessionFactory メソッドがあります。

Spring Framework 5.1の時点で、このようなネイティブHibernateセットアップは、ネイティブHibernateアクセスの隣の標準JPAインタラクション用のJPA EntityManagerFactory も公開できます。詳細については、JPAのネイティブHibernateセットアップを参照してください。

4.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サンプルアプリケーションの従来の static HibernateUtil クラスよりも、このようなインスタンスベースのセットアップを強くお勧めします。(一般的に、どうしても必要な場合を除き、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開発者にとってより自然に感じるかもしれません。

However, the DAO throws plain HibernateException (which is unchecked, so it does not have to be declared or caught), which means that callers can treat exceptions only as being generally fatal — unless they want to depend on Hibernate’s own exception hierarchy. Catching specific causes (such as an optimistic locking failure) is not possible without tying the caller to the implementation strategy. This trade off might be acceptable to applications that are strongly Hibernate-based, do not need any special exception treatment, or both.

幸い、Springの LocalSessionFactoryBean は、Hibernateの SessionFactory.getCurrentSession() メソッドをSpringトランザクション戦略でサポートし、現在のSpring管理のトランザクション SessionHibernateTransactionManagerでも返します。そのメソッドの標準的な動作は、進行中のJTAトランザクションに関連付けられている現在の Session を返します(存在する場合)。この動作は、Springの JtaTransactionManager、EJBコンテナー管理トランザクション(CMT)、またはJTAのいずれを使用するかに関係なく適用されます。

要約すると、Springが管理するトランザクションに参加しながら、プレーンなHibernate APIに基づいてDAOを実装できます。

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

4.3.4. プログラムによるトランザクション区分

任意の数の操作にまたがる低レベルのデータアクセスサービスに加えて、アプリケーションの高レベルでトランザクションの境界を定めることができます。周囲のビジネスサービスの実装にも制限はありません。Spring PlatformTransactionManagerのみが必要です。ここでも、後者はどこからでも取得できますが、setTransactionManager(..) メソッドを介したBean参照として取得することが望ましいです。また、productDAOsetProductDao(..) メソッドで設定する必要があります。次のスニペットのペアは、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 は同じように動作しますが、メソッドごとに構成可能なロールバックポリシーを許可します。

4.3.5. トランザクション管理戦略

TransactionTemplateTransactionInterceptor の両方は、Hibernateアプリケーションの実際のトランザクション処理を PlatformTransactionManager インスタンス(内部で ThreadLocal Session を使用して HibernateTransactionManager (単一のHibernate SessionFactoryの場合)または JtaTransactionManager (コンテナーのJTAサブシステムに委譲)に委譲します。カスタム PlatformTransactionManager 実装を使用することもできます。ネイティブHibernateトランザクション管理からJTAへの切り替え(アプリケーションの特定のデプロイの分散トランザクション要件に直面する場合など)は、構成の問題です。HibernateトランザクションマネージャーをSpringのJTAトランザクション実装に置き換えることができます。トランザクション境界とデータアクセスコードは、どちらも汎用のトランザクション管理APIを使用するため、変更なしで機能します。

複数のHibernateセッションファクトリにわたる分散トランザクションの場合、JtaTransactionManager をトランザクション戦略として複数の LocalSessionFactoryBean 定義と組み合わせることができます。各DAOは、対応するBeanプロパティに渡される1つの特定の SessionFactory 参照を取得します。基礎となるすべてのJDBCデータソースがトランザクションコンテナーのものである場合、ビジネスサービスは、JtaTransactionManager を戦略として使用している限り、特に考慮せずに、任意の数のDAOと任意の数のセッションファクトリにまたがってトランザクションを区別できます

HibernateTransactionManagerJtaTransactionManager はどちらも、コンテナー固有のトランザクションマネージャールックアップまたはJCAコネクタ(EJBを使用してトランザクションを開始しない場合)を使用せずに、Hibernateで適切なJVMレベルのキャッシュ処理を可能にします。

HibernateTransactionManager は、Hibernate JDBC Connection を特定の DataSourceのプレーンなJDBCアクセスコードにエクスポートできます。この機能により、HibernateとJDBCデータが混在し、JTAなしで完全にアクセスできる高レベルのトランザクション境界設定が可能になります。ただし、1つのデータベースにのみアクセスできます。 LocalSessionFactoryBean クラスの dataSource プロパティを介して DataSource で渡された SessionFactory をセットアップした場合、HibernateTransactionManager はHibernateトランザクションをJDBCトランザクションとして自動的に公開します。または、HibernateTransactionManager クラスの dataSource プロパティを介して、トランザクションが公開されることになっている DataSource を明示的に指定できます。

4.3.6. コンテナー管理のリソースとローカルで定義されたリソースの比較

アプリケーションコードの1行を変更することなく、コンテナー管理のJNDI SessionFactory とローカルで定義された 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 とともに使用する必要があります。

Spring駆動のトランザクションは、単一のデータベースにアクセスする場合、ローカルJDBC DataSourceと同様に、ローカルに定義されたHibernate SessionFactory と同様に機能します。トランザクション要件を分散している場合にのみ、SpringのJTAトランザクション戦略を使用する必要があります。JCAコネクターには、コンテナー固有のデプロイステップ、および(明らかに)最初のJCAサポートが必要です。この構成では、ローカルリソース定義とSpring駆動型トランザクションを使用した単純なWebアプリケーションをデプロイするよりも多くの作業が必要です。また、たとえば、JCAを提供しないWebLogic Expressを使用する場合は、コンテナーのEnterprise エディションが必要になることがよくあります。単一のデータベースにまたがるローカルリソースとトランザクションを備えたSpringアプリケーションは、Tomcat、Resin、またはプレーンJettyなどの任意のJava EE Webコンテナー(JTA、JCA、またはEJBなし)で動作します。さらに、デスクトップアプリケーションまたはテストスイートでこのような中間層を簡単に再利用できます。

EJBを使用しない場合は、すべてを考慮して、ローカル SessionFactory セットアップとSpringの HibernateTransactionManager または JtaTransactionManagerを使用してください。コンテナーデプロイの不便さなしに、適切なトランザクションJVMレベルのキャッシュや分散トランザクションなど、すべての利点が得られます。JCAコネクタを介したHibernate SessionFactory のJNDI登録は、EJBと組み合わせて使用する場合にのみ価値を追加します。

4.3.7. Hibernateを使用した偽のアプリケーションサーバー警告

非常に厳密な XADataSource 実装(現在は一部のWebLogicサーバーおよびWebSphereバージョンのみ)を備えた一部のJTA環境では、Hibernateがその環境のJTA PlatformTransactionManager オブジェクトに関係なく構成されている場合、アプリケーションサーバーログに偽の警告または例外が表示されることがあります。これらの警告または例外は、おそらくトランザクションがアクティブでなくなったために、アクセス中の接続が無効になったか、JDBCアクセスが無効になったことを示します。例として、WebLogicの実際の例外を次に示します。

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

この警告を解決するには、Hibernateに(Springとともに)同期するJTA PlatformTransactionManager インスタンスを認識させます。これを行うための2つのオプションがあります。

  • アプリケーションのコンテキストで、JTA PlatformTransactionManager オブジェクトを(おそらくJNDIから JndiObjectFactoryBean または <jee:jndi-lookup>を介して)直接取得し、たとえばSpringの JtaTransactionManagerにフィードする場合、最も簡単な方法は、このJTAを定義するBeanへの参照を指定することです LocalSessionFactoryBean. Springの jtaTransactionManager プロパティの値としての PlatformTransactionManager インスタンスは、オブジェクトをHibernateで利用できるようにします。

  • おそらく、Springの JtaTransactionManager はそれ自体を見つけることができるため、JTA PlatformTransactionManager インスタンスはまだありません。Hibernateを構成してJTA PlatformTransactionManager を直接検索する必要があります。これを行うには、Hibernateマニュアルの説明に従って、Hibernate構成でアプリケーションサーバー固有の TransactionManagerLookup クラスを構成します。

このセクションの残りの部分では、HibernateがJTA PlatformTransactionManagerを認識している場合と認識していない場合に発生するイベントのシーケンスについて説明します。

HibernateがJTA PlatformTransactionManagerを認識しないように構成されている場合、JTAトランザクションがコミットされると、次のイベントが発生します。

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

  • Springの JtaTransactionManager はJTAトランザクションに同期されているため、JTAトランザクションマネージャーによって afterCompletion コールバックを通じてコールバックされます。

  • その他のアクティビティでは、この同期化によってSpringからHibernateへのコールバックがHibernateの afterTransactionCompletion コールバック (Hibernateキャッシュのクリアに使用) を介してトリガーされ、その後Hibernateセッションで明示的な close() コールが行われて、HibernateがJDBC接続の close() を試行するようになります。

  • 一部の環境では、トランザクションがすでにコミットされているため、アプリケーションサーバーは Connection が使用可能であると見なしなくなるため、この Connection.close() 呼び出しは警告またはエラーをトリガーします。

HibernateがJTA PlatformTransactionManagerを認識して構成されている場合、JTAトランザクションがコミットされると、次のイベントが発生します。

  • JTAトランザクションをコミットする準備ができました。

  • Springの JtaTransactionManager はJTAトランザクションに同期されるため、トランザクションはJTAトランザクションマネージャーによって beforeCompletion コールバックを通じてコールバックされます。

  • Springは、Hibernate自体がJTAトランザクションに同期され、以前のシナリオとは異なる動作をすることを認識しています。Hibernate Session を閉じる必要があると仮定すると、Springは今すぐ閉じます。

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

  • HibernateはJTAトランザクションに同期されるため、トランザクションはJTAトランザクションマネージャーによって afterCompletion コールバックを介してコールバックされ、そのキャッシュを適切にクリアできます。

4.4. JPA

org.springframework.orm.jpa パッケージで利用可能なSpring JPAは、Hibernateとの統合と同様の方法でJava Persistence API(英語) の包括的なサポートを提供すると同時に、追加機能を提供するための基礎となる実装を認識します。

4.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セットアップを具体的に構成する場合、すぐに別の方法として、Hibernate 5.2または5.3を使用し、プレーンなJPA LocalContainerEntityManagerFactoryBeanではなくネイティブHibernate LocalSessionFactoryBean をセットアップし、JPAアクセスコードおよびネイティブHibernateアクセスコードと対話できるようにします。詳細については、JPA対話用のネイティブHibernateセットアップを参照してください。

LocalContainerEntityManagerFactoryBeanEntityManagerFactory 構成を完全に制御し、きめ細かいカスタマイズが必要な環境に適しています。 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環境では、EntityManagerFactory をJNDIから取得することを検討してください。または、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提供のクラスです。通常、エージェント(英語) を介した 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を使用する場合は、リポジトリの遅延ブートストラップも必ず設定してください。

4.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 のライフサイクルは現在のトランザクションに関連付けられておらず、アプリケーションに完全に依存しています。

メソッドおよびフィールドレベルの注入

You can apply annotations that indicate dependency injections (such as @PersistenceUnit and @PersistenceContext ) on field or methods inside a class — hence the expressions “method-level injection” and “field-level injection”. Field-level annotations are concise and easier to use while method-level annotations allow for further processing of the injected dependency. In both cases, the member visibility (public, protected, or private) does not matter.

クラスレベルのアノテーションはどうですか?

Java EEプラットフォームでは、リソース注入ではなく依存関係宣言に使用されます。

注入された EntityManager は、Springで管理されます(進行中のトランザクションを認識します)。新しいDAO実装では、EntityManagerFactoryではなく EntityManager のメソッドレベルの注入を使用しますが、アノテーションの使用により、アプリケーションコンテキストXMLを変更する必要はありません。

このDAOスタイルの主な利点は、Java Persistence APIのみに依存することです。Springクラスのインポートは必要ありません。さらに、JPAアノテーションが理解されると、注入はSpringコンテナーによって自動的に適用されます。これは、非侵襲性の観点から魅力的であり、JPA開発者にとってより自然に感じることができます。

4.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 は、Spring Framework 5.1およびHibernate 5.2/5.3の時点でJPAアクセスコードと対話でき、いくつかのHibernateの仕様に適応し、JDBC相互作用を提供します。これは、LocalSessionFactoryBean セットアップとの組み合わせで特に意味があります。詳細については、JPAインタラクション用のネイティブHibernateセットアップを参照してください。

4.4.4. JpaDialect および JpaVendorAdapterを理解する

高度な機能として、JpaTransactionManager および AbstractEntityManagerFactoryBean のサブクラスにより、カスタム JpaDialectjpaDialect Beanプロパティに渡すことができます。 JpaDialect の実装により、通常はベンダー固有の方法で、Springでサポートされる次の高度な機能を有効にできます。

  • 特定のトランザクションセマンティクスの適用 (カスタム分離レベルやトランザクションタイムアウトなど)

  • トランザクションJDBC Connection の取得 (JDBCベースのDAOへの露出用)

  • PersistenceExceptions からSpring DataAccessExceptionsへの高度な翻訳

これは、特別なトランザクションセマンティクスおよび例外の高度な変換に特に役立ちます。デフォルトの実装(DefaultJpaDialect)は特別な機能を提供しません。前述の機能が必要な場合は、適切なダイアレクトを指定する必要があります。

主にSpringのフル機能の LocalContainerEntityManagerFactoryBean セットアップのためのさらに広範なプロバイダー適応機能として、JpaVendorAdapterJpaDialect の機能を他のプロバイダー固有のデフォルトと組み合わせます。 HibernateJpaVendorAdapter または EclipseLinkJpaVendorAdapter を指定することは、それぞれHibernateまたはEclipseLink用に EntityManagerFactory セットアップを自動構成する最も便利な方法です。これらのプロバイダーアダプターは、主にSpring駆動のトランザクション管理で使用するために設計されていることに注意してください(つまり、JpaTransactionManagerで使用するため)。

操作の詳細と、SpringのJPAサポート内での使用方法については、 JpaDialect (Javadoc) および JpaVendorAdapter (Javadoc) javadocを参照してください。

4.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用に構成する必要があります。これはプロバイダー固有であり、通常は LocalContainerEntityManagerFactoryBeanjpaProperties として指定される特別なプロパティを使用します。Hibernateの場合、これらのプロパティはバージョン固有ですらあります。詳細については、Hibernateのドキュメントを参照してください。

  • Springの HibernateJpaVendorAdapter は、HibernateのHibernate 5.0のデフォルトに一致しますが、5.1/5.2にはない、接続リリースモード on-closeなど、特定のSpring指向のデフォルトを強制します。JTAセットアップの場合、HibernateJpaVendorAdapter を最初から宣言しないか、prepareConnection フラグをオフにします。または、Hibernate 5.2の hibernate.connection.handling_mode プロパティを DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT に設定して、Hibernateのデフォルトを復元します。WebLogicに関連する注記については、Hibernateを使用した偽のアプリケーションサーバー警告を参照してください。

  • または、アプリケーションサーバー自体から EntityManagerFactory を取得することを検討してください(つまり、ローカルで宣言された LocalContainerEntityManagerFactoryBeanの代わりにJNDIルックアップを使用して)。サーバーが提供する EntityManagerFactory では、サーバー構成で特別な定義が必要になる場合があります(デプロイの移植性が低下します)が、サーバーのJTA環境用にセットアップされます。

4.4.6. JPA相互作用のためのネイティブHibernateセットアップおよびネイティブHibernateトランザクション

Spring Framework 5.1およびHibernate 5.2/5.3の時点で、ネイティブ LocalSessionFactoryBean セットアップと HibernateTransactionManager の組み合わせにより、@PersistenceContext および他のJPAアクセスコードとの相互作用が可能になります。Hibernate SessionFactory は現在JPAの EntityManagerFactory インターフェースをネイティブに実装しており、Hibernate Session ハンドルはネイティブにJPA EntityManagerです。SpringのJPAサポート機能は、ネイティブHibernateセッションを自動的に検出します。

このようなネイティブHibernateセットアップは、多くのシナリオで標準のJPA LocalContainerEntityManagerFactoryBeanJpaTransactionManager の組み合わせの代替として機能し、同じローカルトランザクション内で @PersistenceContext EntityManager の隣の SessionFactory.getCurrentSession() (および HibernateTemplate)との相互作用を可能にします。このようなセットアップは、JPAブートストラップ契約に制約されないため、Hibernateの統合を強化し、構成の柔軟性を高めます。

SpringのネイティブHibernateセットアップはさらに多くの機能(たとえば、カスタムHibernateインテグレーターセットアップ、Hibernate 5.3 Beanコンテナー統合、読み取り専用トランザクションの強力な最適化など)を提供するため、このようなシナリオでは HibernateJpaVendorAdapter 構成は必要ありません。最後になりましたが、LocalSessionFactoryBuilderを介してネイティブHibernateセットアップを表現し、@Bean スタイルの構成とシームレスに統合することもできます( FactoryBean は関与しません)。

LocalSessionFactoryBean および LocalSessionFactoryBuilder は、JPA LocalContainerEntityManagerFactoryBean と同様に、バックグラウンドブートストラップをサポートしています。はじめにバックグラウンドブートストラップを参照してください。

LocalSessionFactoryBeanでは、これは bootstrapExecutor プロパティを通じて利用できます。プログラムによる LocalSessionFactoryBuilderでは、オーバーロードされた buildSessionFactory メソッドはブートストラップエグゼキューター引数を取ります。

5. オブジェクトXMLマッパーを使用したXMLのマーシャリング

5.1. 導入

この章では、SpringのオブジェクトXMLマッピングのサポートについて説明します。オブジェクト-XMLマッピング(略してO-Xマッピング)は、XMLドキュメントをオブジェクトに変換したり、オブジェクトからXMLドキュメントを変換したりする行為です。この変換プロセスは、XMLマーシャリングまたはXML直列化とも呼ばれます。この章では、これらの用語を同じ意味で使用します。

O-Xマッピングのフィールドでは、マーシャラーはオブジェクト(グラフ)をXMLに直列化する責任があります。同様に、アンマーシャラーはXMLをオブジェクトグラフにデシリアライズします。このXMLは、DOMドキュメント、入力または出力ストリーム、またはSAXハンドラーの形式をとることができます。

O/XマッピングのニーズにSpringを使用する利点のいくつかは次のとおりです。

5.1.1. 設定が簡単

SpringのBeanファクトリにより、JAXBコンテキスト、JiBXバインディングファクトリなどを構築する必要なく、マーシャラーを簡単に構成できます。アプリケーションコンテキストの他のBeanと同様に、マーシャラーを構成できます。さらに、多くのマーシャラーがXML名前空間ベースの構成を使用できるため、構成がさらに簡単になります。

5.1.2. 一貫したインターフェース

Spring’s O-X mapping operates through two global interfaces: Marshaller (Javadoc) and Unmarshaller (Javadoc) . These abstractions let you switch O-X mapping frameworks with relative ease, with little or no change required on the classes that do the marshalling. This approach has the additional benefit of making it possible to do XML marshalling with a mix-and-match approach (for example, some marshalling performed using JAXB and some by XStream) in a non-intrusive fashion, letting you use the strength of each technology.

5.1.3. 一貫した例外階層

Springは、基になるO-Xマッピングツールの例外から、XmlMappingException をルート例外とする独自の例外階層への変換を提供します。これらのランタイム例外は元の例外をラップするため、情報は失われません。

5.2. Marshaller および Unmarshaller

で記述されていたように導入、マーシャラーはXMLにオブジェクトを直列化し、アンマーシャラーオブジェクトにXMLストリームをデシリアライズ。このセクションでは、この目的で使用される2つのSpringインターフェースについて説明します。

5.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.File, java.io.OutputStream、または java.io.Writer

marshal() メソッドは最初のパラメーターとしてプレーンオブジェクトを受け入れますが、ほとんどの Marshaller 実装は任意のオブジェクトを処理できません。代わりに、オブジェクトクラスは、マッピングファイルにマッピングされるか、アノテーションでマークされるか、マーシャラーに登録されるか、共通の基本クラスを持つ必要があります。O-Xテクノロジーがこれをどのように管理するかを決定するには、この章の後のセクションを参照してください。

5.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.File, java.io.InputStream、または java.io.Reader

2つの個別のマーシャリングインターフェース(Marshaller および Unmarshaller)がありますが、Spring-WSのすべての実装は両方を1つのクラスに実装します。これは、1つのマーシャラークラスを接続し、applicationContext.xmlでマーシャラーとアンマーシャラーの両方として参照できることを意味します。

5.2.3. XmlMappingExceptionを理解する

Springは、基になるO-Xマッピングツールからの例外を、XmlMappingException をルート例外として独自の例外階層に変換します。これらのランタイム例外は元の例外をラップするため、情報は失われません。

さらに、MarshallingFailureExceptionUnmarshallingFailureException は、基になるO-Xマッピングツールがそうしない場合でも、マーシャリング操作とアンマーシャリング操作を区別します。

O-Xマッピングの例外階層を次の図に示します。

oxm exceptions

5.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定義はかなり単純であることに注意してください。また、XStreamMarshallerMarshallerUnmarshallerの両方を実装しているため、アプリケーションの marshallerunmarshaller プロパティの両方で xstreamMarshaller Beanを参照できることに注意してください。

このサンプルアプリケーションは、次の settings.xml ファイルを生成します。

<?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

5.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)
1 oxm スキーマを参照します。
2 oxm スキーマの場所を指定します。

このスキーマにより、次の要素が利用可能になります。

各タグは、それぞれのマーシャラーのセクションで説明されています。ただし、例として、JAXB2マーシャラーの構成は次のようになります。

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

5.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 パッケージにあります。

5.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コンテキストパス

いいえ

5.6. JiBX

The JiBX framework offers a solution similar to that which Hibernate provides for ORM: A binding definition defines the rules for how your Java objects are converted to or from XML. After preparing the binding and compiling the classes, a JiBX binding compiler enhances the class files and adds code to handle converting instances of the classes from or to XML.

JiBXの詳細については、JiBX Webサイト(英語) を参照してください。Spring統合クラスは org.springframework.oxm.jibx パッケージにあります。

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

このマーシャラーが使用するバインディング名

いいえ

5.7. XStream

XStreamは、オブジェクトをXMLに直列化し、再び戻すためのシンプルなライブラリです。マッピングを必要とせず、クリーンなXMLを生成します。

XStreamの詳細については、XStream Webサイト(英語) を参照してください。Spring統合クラスは org.springframework.oxm.xstream パッケージにあります。

5.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を非整列化することを選択した場合、次の例に示すように、XStreamMarshallersupportedClasses プロパティを設定します。

<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サービス内での使用には適していない。

6. 付録

6.1. XML スキーマ

付録のこの部分には、以下を含むデータアクセス用のXMLスキーマがリストされています。

6.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>
1 tx 名前空間の使用を宣言します。
2場所を(他のスキーマの場所とともに)指定します。
多くの場合、tx 名前空間の要素を使用すると、aop 名前空間の要素も使用します(Springの宣言的なトランザクションサポートはAOPを使用して実装されるため)。上記のXMLスニペットには、aop 名前空間の要素を使用できるように、aop スキーマを参照するために必要な関連行が含まれています。

6.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>
1 jdbc 名前空間の使用を宣言します。
2場所を(他のスキーマの場所とともに)指定します。

Unofficial Translation by spring.pleiades.io. See the original content.