読者です 読者をやめる 読者になる 読者になる

kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのV. Data Access 18. Data access with JDBCをテキトーに訳した

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#jdbc をテキトーに訳した。※途中で飽きたので一部抜けあり。

18. Data access with JDBC

18.1 Introduction to Spring Framework JDBC

Spring Framework JDBCの抽象化がもたらす価値を以下の表にまとめています。以下の表は、Springが提供するものと、アプリケーション開発者が設定などの責任を負うもの、に関する要約です。

Table 18.1. Spring JDBC - who does what?

Action Spring You
コネクションパラメータの定義 X
コネクションの接続 X
SQLステートメントの指定 X
パラメータ宣言と値の指定 X
ステートメントのプリペアと実行 X
結果セット走査のセットアップ X
結果セットループに対する操作 X
例外処理 X
トランザクション X
コネクション・ステートメント・リザルトセットの切断 X

Spring FrameworkJDBCなどの面倒なAPIを使用して行う低レベルの詳細部分を処理してくれます。

18.1.1 Choosing an approach for JDBC database access

JDBCデータアクセスには何種類かの方法があります。JdbcTemplate, SimpleJdbcInsert, SimplejdbcCallというデータベースメタデータを最適する三種類の方法に加え、RDBMS ObjectスタイルはJDO Queryに似たオブジェクト指向のアプローチが出来ます。その内の一つを使い始めた後、別の方法の機能と混在して使うことも出来ます。すべてのアプローチはJDBC 2.0準拠のドライバを要求し、一部の高度な機能はJDBC 3.0ドライバを要求します。

  • JdbcTemplateはクラシックなSpring JDBCアプローチで最もポピュラーなものです。この"低レベル"アプローチとその他全ては裏側でJdbcTemplateを使用しています。
  • NamedParameterJdbcTemplateJdbcTemplateをラップし、伝統的なJDBCの"?"プレースホルダの代わりに名前付きパラメータを提供します。このアプローチによりSQLステートメント複数パラメータを持ちたい場合の使い勝手と可読性が向上します。
  • SimpleJdbcInsert and SimpleJdbcCallは必要な設定の量を制限するためにデータベースメタデータを最適化します。このアプローチはコーディングをシンプルにするもので、テーブルもしくはプロシージャ名を記述するだけでカラム名に対応するパラメータのマップを取得します。この機能はデータベースが適切なメタデータを返す場合のみ動作します。データベースがメタデータを返さない場合、パラメータの設定を明示的に指定する必要があります。
  • RDBMS Objects including MappingSqlQuery, SqlUpdate and StoredProcedureはデータアクセスレイヤーの初期化中に再利用可能かつスレッドセーフなオブジェクトの生成を開発者に要求します。このアプローチはJDO Queryをモデルにしており、クエリ文字列の定義・パラメータ宣言・クエリのコンパイル、を行います。一度設定を行うと、渡す各種のパラメータで複数回メソッド実行が可能です。

18.1.2 Package hierarchy

Spring Framework’s JDBC抽象化フレームワークは4つのパッケージで構成されており、それぞれcore, datasource, object, supportです。

org.springframework.jdbc.coreパッケージにはJdbcTemplateクラスと各種のコールバックインタフェース、それらの関連クラス、があります。サブパッケージorg.springframework.jdbc.core.simpleにはSimpleJdbcInsertSimpleJdbcCallがあります。別のサブパッケージorg.springframework.jdbc.core.namedparamにはNamedParameterJdbcTemplateクラスとその関連サポートクラスがあります。Section 18.2, “Using the JDBC core classes to control basic JDBC processing and error handling”, Section 18.4, “JDBC batch operations”Section 18.5, “Simplifying JDBC operations with the SimpleJdbc classes”を参照してください。

org.springframework.jdbc.datasourceパッケージにはDataSourceアクセス簡易化用のユーティリティクラスと、各種のシンプルなDataSource実装があり、この実装によりJava EEコンテナ外でJDBCコードを修正することなくテストと実行が可能です。org.springfamework.jdbc.datasource.embeddedサブパッケージにはHSQL, H2, DerbyなどJavaデータベースエンジンを使用する組み込みデータベースの生成サポートがあります。Section 18.3, “Controlling database connections”Section 18.8, “Embedded database support”を参照してください。

org.springframework.jdbc.objectパッケージにはRDBMSクエリと更新を表現するクラスとスレッドセーフで再利用オブジェクトなストアドプロシージャが含まれます。Section 18.6, “Modeling JDBC operations as Java objects”を参照してください。このアプローチはJDOをモデルにしていますが、クエリが返すオブジェクトはデータベースから切断されて(disconnected )います。この高レベルなJDBC抽象化はorg.springframework.jdbc.coreパッケージの低レベル抽象化に依存しています。

org.springframework.jdbc.supportパッケージはSQLExceptionの変換機能とユーティリティクラスを提供します。JDBC処理中にスローされた例外はorg.springframework.daoパッケージに定義されている例外に変換されます。つまり、Spring JDBC抽象化レイヤーを使うコードではJDBCもしくはRDBMS固有のエラー処理を実装する必要がありません。変換された例外は未チェックのため、呼び出し元に伝播可能な例外で回復可能な例外の場合には選択肢を与えることにあります。Section 18.2.3, “SQLExceptionTranslator”を参照してください。

18.2 Using the JDBC core classes to control basic JDBC processing and error handling

18.2.1 JdbcTemplate

JdbcTemplateクラスはJDBC coreパッケージの中核となるクラスです。リソースの生成と破棄を処理し、コネクション切断忘れなどよくあるエラーの回避を開発者の代わりにやってくれます。core JDBCのワークフローの基本的なタスクはステートメントの生成と実行で、SQLの指定と結果セットの展開はアプリケーションコードでやります。JdbcTemplateクラスは、SQLクエリの実行、ステートメントの更新とストアドプロシージャの呼び出し、ResultSetsのイテレーションと返されたパラメータ値の展開、を行います。また、JDBC例外をキャッチして、ジェネリックで情報量の多いorg.springframework.daoパッケージに定義された例外階層の例外クラスに変換します。

JdbcTemplateを使うコードは、明確に定義された契約に基づき、コールバックインタフェースを実装するだけです。PreparedStatementCreatorコールバックインタフェースには、SQLと必要なパラメータを指定し、このコールバックに渡されるConncetionを用いてプリペアードステートメントを生成します。CallableStatementCreatorはcallableステートメントを生成することと全く同じです。RowCallbackHandlerインタフェースはResultSetの各行から値を抽出します。

JdbcTemplateはDAO実装で使用可能で、DataSource参照で直接インスタンス化したり、Spring IoDコンテナで設定したり、ビーン参照としてDAOに与えたりします。

DataSourceは常にSpring IoCコンテナのビーンとして設定すべきです。 In the first case the bean is given to the service directly; in the second case it is given to the prepared template.

このクラスで発生したSQLの問題はテンプレートインスタンス(通常はJdbcTemplateですがJdbcTemplateのサブクラスをカスタムしている場合には異なるものになります)の完全修飾クラス名に対応するカテゴリ下にDEBUGレベルでログ出力されます。

Examples of JdbcTemplate class usage

このセクションではJdbcTemplateクラスの使用法のサンプルをいくつか紹介します。これらのサンプルはJdbcTemplateが提供する機能をすべて網羅しているとは言えないため、詳細については関連javadocを参照してください。

Querying (SELECT)

以下はリレーションの行数を取得する簡単なクエリの例です。

int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);

バインド変数を使用するクエリ。

int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
        "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

Stringを返すクエリ。

String lastName = this.jdbcTemplate.queryForObject(
        "select last_name from t_actor where id = ?",
        new Object[]{1212L}, String.class);

クエリと単一のドメインオブジェクトの処理。

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;
            }
        });

クエリと複数ドメインオブジェクトの処理。

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;
            }
        });

もし最後の二つが同一アプリケーションに存在する場合、二つのRowMapper無名内部クラスの重複を除去したくなると思われ、その場合、単一クラス(通常はstaticネストクラス)に抽出し、必要に応じてDAOメソッドから参照します。たとえば、以下のように書き換えられます。

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;
    }
}
Updating (INSERT/UPDATE/DELETE) with jdbcTemplate

追加・更新・削除の実行にはupdate(..)メソッドを使います。パラメータ値は可変長引数かobject配列として渡します。

this.jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling");
this.jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L);
this.jdbcTemplate.update(
        "delete from actor where id = ?",
        Long.valueOf(actorId));
Other jdbcTemplate operations

任意のSQLDDLステートメントなど、の実行にはexecute(..)メソッドを使います。オーバーロードされたメソッドが幾つかあり、各種のコールバックインターフェースを取るものや、変数配列のバインドなどがあります。

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

以下のサンプルはストアドプロシージャを呼び出しています。より高機能なストアドサポートについては後に解説します

this.jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        Long.valueOf(unionId));
JdbcTemplate best practices

JdbcTemplateクラスのインスタンス設定後はスレッドセーフ(threadsafe once configured)です。この点は重要で、つまり、単一のJdbcTemplateインスタンスを設定後、安全にこのインスタンスの参照を共有して複数のDAO(かリポジトリ)にDIできます。JdbcTemplateは、DataSourceの参照を保持し続けるという点でステートフルですが、その状態は会話的(conversational)な状態ではないです

JdbcTemplateクラス(と関連NamedParameterJdbcTemplateクラス)のよくある使い方は、Spring設定ファイルでDataSourceを設定し、共有DataSourceビーンをDAOクラスにDIします。JdbcTemplateDataSourceのセッター時に生成します。これに従うとDAOは以下のようなコードになります。

public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

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

    // 以下、JDBC経由でアクセスするCorporateEventDaoの実装
}

対応する設定ファイルは以下のようになります。

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://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>

明示的な設定ではなくコンポーネントスキャンとDIのアノテーションを使うことも出来ます。この場合、クラスに@Repositoryコンポーネントスキャンの候補にする)を付与し、@AutowiredDataSourceのセッターメソッドに付与します。

@Repository
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

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

    // 以下、JDBC経由でアクセスするCorporateEventDaoの実装
}

対応する設定ファイルは以下のようになります。

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- @Componentをビーンとして設定するためにアプリケーションのbase
         パッケージ内をスキャンする -->
    <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が必要で、よって複数の異なる設定をしたJdbcTemplatesを作ることになります。

18.2.2 NamedParameterJdbcTemplate

NamedParameterJdbcTemplateは、クラシックなプレースホルダー(?)引数を使用したJDBCステートメントに対し、名前付きパラメータでJDBCステーメントを操作するサポートを追加したものです。NamedParameterJdbcTemplateクラスはJdbcTemplateをラップし、より便利に使えるようにラップしたJdbcTemplateにデリゲートします。このセクションではJdbcTemplate自体ではなくNamedParameterJdbcTemplateクラスの範囲についてのみ解説します。つまり、名前付き引数を使用したJDBCステーメントについてです。

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

sql変数への代入値としての名前付きパラメータ記法と、それに対応する値は(MapSqlParameterSource型の)namedParameters変数で結びつけます。

もしくは、Mapで名前付きパラメータとそれに対応する値をNamedParameterJdbcTemplateに渡せます。NamedParameterJdbcOperationsの公開メソッドNamedParameterJdbcTemplateの実装メソッドは似たようなパターンになっていますが、その点についてはここでは触れません。

以下の例はMapベーススタイル使った例です。

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

NamedParameterJdbcTemplateの関連機能の一つが(同一パッケージ内の)SqlParameterSourceインタフェースです。このインタフェース実装の使い方の例は前述のサンプルで(MapSqlParameterSourceクラス)示しました。SqlParameterSourceNamedParameterJdbcTemplateに渡す名前付きパラメータ値です。MapSqlParameterSourceクラスはjava.util.Mapのアダプターとなるごくシンプルな実装で、キーがパラメータ名で値がパラメータ値となります。

別のSqlParameterSource実装にBeanPropertySqlParameterSourceクラスがあります。このクラスは任意のJavaBean(つまりthe JavaBean conventions)に従うクラスのインスタンス)をラップし、ラップしたJavaBeanのプロパティを名前付きパラメータ値として使用します。

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

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

先にNamedParameterJdbcTemplateクラスはクラシックなJdbcTemplateをラップしている、との述べました。もしJdbcTemplateクラスにだけ存在する機能にアクセスしたい場合、JdbcOperationsインタフェースのラップされたJdbcTemplateにアクセスするためのgetJdbcOperations()メソッドを使います。

アプリケーションコンテキストでNamedParameterJdbcTemplateクラスを使うガイドラインについてはthe section called “JdbcTemplate best practices”を参照してください。

18.2.3 SQLExceptionTranslator

SQLExceptionTranslatorSQLExceptionsとSpring固有のorg.springframework.dao.DataAccessExceptionに変換可能なクラスが実装するインタフェースで、データアクセスの手段とは切り離されています。実装はジェネリック(例えばJDBCのSQLStateコードを使用)かより正確を期すためにプロプライエタリ(例えばOracleのエラーコードを使用)に出来ます。

SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslatorの実装でデフォルトです。この実装はベンダー固有のコードを使います。SQLState実装よりかは正確です。エラーコード変換はSQLErrorCodesというJavaBean型が持つコードに基づいて行われます。このクラスはSQLErrorCodesFactoryが生成と処理を行い、クラス名の由来についてはsql-error-codes.xml設定ファイルの内容に基づいてSQLErrorCodesを生成するファクトリーである、という点から来ています。このファイルにはベンダーコードが含まれ、DatabaseMetaDataから取得したDatabaseProductNameに基づきます。使用している実際のデータベースのコードが使われます。

SQLErrorCodeSQLExceptionTranslatorは以下の順序でマッチングルールを適用します。

SQLErrorCodesFactoryはデフォルトのエラーコード定義とカスタム例外変換に使われます。エラーコードはクラスパスのsql-error-codes.xmlファイル内を参照し、マッチするSQLErrorCodesインスタンスが使用中のデータベースのメタデータのDB名に基づいて配置されます。

  • サブクラスによるカスタム変換の実装。通常はSQLErrorCodeSQLExceptionTranslatorが使われるのでこのルールは適用されません。このルールが適用されるのはサブクラス実装を自前で作成する場合だけです。
  • SQLErrorCodesクラスのcustomSqlExceptionTranslatorプロパティとして提供されるSQLExceptionTranslatorインタフェースのカスタム実装。
  • CustomSQLErrorCodesTranslationクラスのインスタンスのリスト、SQLErrorCodesクラスのcustomTranslationsプロパティで提供される。このリストが検索条件にマッチする場合。
  • マッチするエラーコードの適用
  • フォールバックの使用。SQLExceptionSubclassTranslatorはデフォルトのフォールバックトランスレータです。この変換が利用不可能な場合、次のフォールバックトランスレータSQLStateSQLExceptionTranslatorになります。

SQLErrorCodeSQLExceptionTranslatorを拡張可能です。

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

上記の例では、固有のエラーコード-12345を変換し、それ以外はデフォルトのトランスレータ実装に任せています。上記のカスタムトランスレータを使用するには、setExceptionTranslatorメソッド経由でJdbcTemplateに渡し、このトランスレータが必要となるすべてのデータアクセス処理でJdbcTemplateを使う必要があります。以下はカスタムトランスレータの使い方の例です。

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);
}

カスタムトランスレータに渡すデータソースはsql-error-codes.xmlのエラーコードのルックアップに使います。

18.2.4 Executing statements

SQLステートメントの実行に必要なコードはほんのわずかです。必要なのはDataSourceJdbcTemplateで、JdbcTemplateには便利なメソッドがいくつかあります。以下の例は新規テーブルを作成する機能を持つクラスに最低限必要となるコードの例です。

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))");
    }
}

18.2.5 Running queries

クエリーのメソッドによっては単一値を返すものがあります。カウントや一行の特定値を取得するにはqueryForObject(..)を使います。戻されるJDBCTypeを引数に渡したJavaクラスに変換します。型変換が妥当ではない場合、InvalidDataAccessApiUsageExceptionがスローされます。以下はクエリーメソッドの例で、一つはintでもう一つはStringを戻り値に要求します。

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);
    }

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

単一結果を返すクエリーメソッドの他に、クエリーが戻す各行のエントリから成るリストを戻すメソッドもあります。最も汎用のメソッドqueryForList(..)で、Listを返し各行はカラム値を表現するMapです。上記の例に全行をリストで取得するメソッドを追加する場合、以下のようになります。

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");
}

リストは以下のようなものが返されます。

18.2.6 Updating the database

以下の例は主キーでカラムを更新する例です。以下の例では、SQLステートメントはパラメータ用にプレースホルダーを持っています。パラメータ値は可変長引数かオブジェクト配列で渡せます。プリミティブは明示的もしくはオートボクシングでラッパークラスにラップすべきです。

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);
    }
}

18.2.7 Retrieving auto-generated keys

update()はデータベースが生成する主キー取得をサポートするメソッドです。このサポートはJDBC 3.0標準の一部で、詳細は仕様のChapter 13.6を参照してください。このメソッドは最初の引数にPreparedStatementCreatorを取り、これには挿入のステートメントを指定します。それ以外の引数はKeyHolderで、これにはupdateが正常時に返す生成キーが含まれます。PreparedStatement (which explains why the method signature is the way it is)を生成する単一で標準的な方法というわけではありません。以下の例はOracleでは動作しますが他プラットフォームでは動作しない可能性があります。

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

18.3 Controlling database connections

18.3.1 DataSource

SpringはDataSource経由でデータベースへのコネクションを取得します。DataSourceJDBC仕様の一部でコネクションファクトリーを汎用化したものです。このクラスによりコンテナやフレームワークでコネクションプーリングやトランザクション関連の問題をアプリケーションコードから隠すことが可能となります。開発者にとっては、データベース接続に関する詳細を知る必要がなくなります。詳細に関する責任はデータベースをセットアップする管理者のものとなります。開発者は開発とテストコードに責任は持ちますが、本番環境のデータソースの設定方法については知る必要はありません。

SpringのJDBCレイヤーを使う場合、データソースはJNDIから取得したり、サードパーティ提供のコネクションプール実装を設定したりします。ポピュラーな実装はApache Jakarta Commons DBCPとC3P0です。Springディストリビューションの実装はテスト目的のものだけなのでプーリングは提供しません。

このセクションではSpringのDriverManagerDataSource実装と、後に説明するいくつかの別の実装を使います。

DriverManagerDataSourceクラスの使用はテスト目的のためだけに使用してください。このクラスはプーリングを提供せず、コネクションを複数要求する場合のパフォーマンスが悪いためです。

JDBCのコネクションを得るようにDriverManagerDataSourceでコネクションを取得します。DriverManagerがドライバクラスをロードするために、JDBCドライバの完全修飾クラス名を指定します。次に、JDBCドライバごとに異なるURLを指定します。(ドライバごとの正しい値については各ドキュメントを参照してください)それから、データベース接続のためにユーザ名とパスワードを指定します。以下はJavaコードでDriverManagerDataSourceを設定する方法です。

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

以下は上記に相当する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"/>

上記は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"/>

18.3.2 DataSourceUtils

DataSourceUtilsクラスは必要に応じてJNDIからコネクションの接続と切断のためのstaticメソッドを提供するヘルパークラスです。DataSourceTransactionManagerを使用してスレッドにバインドするコネクションをサポートします。

18.3.3 SmartDataSource

SmartDataSourceインタフェース関係型データベースへの接続を提供可能なクラスが実装すべきクラスです。DataSourceインタフェースを拡張しており、このインタフェースを使うクラスが指定操作後にコネクションを接続すべきかどうかを問い合わせることが可能です。開発者がコネクションを再利用すると知っている場合に有用です。

18.3.4 AbstractDataSource

AbstractDataSourceはSpringのDataSource実装のベースとなるabstractクラスで、すべてのDataSource実装に共通なコードが含まれます。自前のDataSource実装を作る場合にはこのAbstractDataSourceクラスを拡張します。

18.3.5 SingleConnectionDataSource

SingleConnectionDataSourceクラスはSmartDataSourceインタフェースの実装で単一のConnectionをラップし、使用後にはクローズされません。このことから明らかなとおり、マルチスレッド環境には適しません。

いま、何らかの永続化ツールを使用するなど、コネクションプーリングを使っていると仮定して、その上でクライアントコードがcloseを呼ぶ場合、suppressCloseプロパティをtrueに設定してください。この設定により物理コネクションをラップしてクローズを抑止するプロキシが返されます。ネイティブのOracleConnectionなどにはキャスト出来ない点に注意してください。

主にテスト用のクラスとして使います。たとえば、単純なJNDI環境と組み合わせて、アプリケーションサーバ外でコードのちょっとしたテストをする、などです。DriverManagerDataSourceとは対照的に、物理コネクションの過度な生成を避けて常に同一のコネクションを再利用します。

18.3.6 DriverManagerDataSource

DriverManagerDataSourceクラスは一般的なDataSourceインタフェースの実装で、ビーンプロパティ経由でJDBCドライバを設定し、常に新規のConnectionを返します。

この実装は、Spring IoCコンテナDataSourceビーンやJNDI環境との組み合わせなど、Java EEコンテナ外のスタンドアローン環境とテストに便利です。プーリングを行うConnection.close()はただ単にコネクションをクローズし、どんなDataSrouceベースの永続化コードでも動作します。commons-dbcpなどのJavaBeanスタイルのコネクションプールを使う方が簡単で、テスト環境においても同様に、DriverManagerDataSourceの上でコネクションプールなどを使うのが望ましいです。

18.3.7 TransactionAwareDataSourceProxy

TransactionAwareDataSourceProxyはターゲットとなるDataSourceのプロキシで、Springマネージドトランザクションの機能を追加するためにターゲットとなるDataSourceをラップします。その点では、Java EEサーバが提供するトランザクショナルなJNDI DataSourceと似ています。

このクラスを使用した場合は稀であり、ただし、既存コードに標準JDBCDataSourceインタフェースの実装を渡して呼び出す必要がある場合を除きます。この除外ケースの場合に、既存コードがそのまま使用可能で、同時にコードをSpringマネージドトランザクション下に置くことが出来ます。通常、新規のコードを書く場合、リソース管理にはJdbcTemplateDataSourceUtilsなどの高レベル抽象化を使用するのが望ましいです。

(詳細についてはTransactionAwareDataSourceProxyjavadocを参照してください)

18.3.8 DataSourceTransactionManager

DataSourceTransactionManagerクラスはPlatformTransactionManagerの実装で単一JDBCデータソース用です。指定のデータソースからのJDBCコネクションを現在実行中のスレッドにバインドし、データソースごとに1スレッドに1コネクションが可能になります*1

アプリケーションコードではJava EE標準DataSource.getConnectionの代わりにDataSourceUtils.getConnection(DataSource)経由でJDBCコネクションを取得する必要があります。チェック例外SQLExceptionsの代わりに未チェック例外org.springframework.daoをスローします。JdbcTemplateなどすべてのフレームワーククラスは暗黙的にこの機能を使用しています。If not used with this transaction manager, the lookup strategy behaves exactly like the common one - it can thus be used in any case.

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

JTAをサポートするコンテナが不要など、単一リソースの場合にJtaTransactionManagerの代わりにこの実装を使用可能です。コネクションのルックアップパターンを変えたい場合、両者を切り替えるには設定を変更するだけです。JTAはカスタム分離レベルをサポートしていません。

18.3.9 NativeJdbcExtractor

稀に、標準JDBC APIとは異なるベンダー固有のJDBCメソッドにアクセスしたい場合があります。アプリケーションサーバで動作させようとしたり、Connection, Statement, ResultSetをラップするDataSourceと共に動作させようとすると、困難が伴います。ネイティブオブジェクトへのアクセスを取得するには、JdbcTemplateを設定するかNativeJdbcExtractorと共にOracleLobHandlerを使います。

NativeJdbcExtractorは実行環境に適合するために様々な種類があります。

  • SimpleNativeJdbcExtractor
  • C3P0NativeJdbcExtractor
  • CommonsDbcpNativeJdbcExtractor
  • JBossNativeJdbcExtractor
  • WebLogicNativeJdbcExtractor
  • WebSphereNativeJdbcExtractor
  • XAPoolNativeJdbcExtractor

通常はSimpleNativeJdbcExtractorを使用して各種の環境でConnectionオブジェクトをアンラップすれば十分です。詳細についてはjavadocを参照してください。

18.4 JDBC batch operations

大抵のJDBCドライバには、バッチで同一のプリペアードステートメント複数回呼び出す場合の、パフォーマン向上機能を提供しています。バッチ更新をグループ化することでデータベースとのラウンドトリップ回数を制限できます。

18.4.1 Basic batch operations with the JdbcTemplate

専用インタフェースBatchPreparedStatementSetterの二つのメソッドを実装することでJdbcTemplateバッチ処理を実現できます。batchUpdateメソッド呼び出しの第二引数にその実装を渡します。getBatchSizeは現在のバッチのサイズを指定するのに使います。setValuesはプリペアードステートメントのパラメータに値を設定するのに使います。setValuesgetBatchSizeの回数分呼び出されます。以下のサンプルではリストを使用してactorテーブルを更新しています。この例ではリストの内容すべてがバッチに使われます。

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) {
        int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
                "last_name = ? where id = ?",
            new BatchPreparedStatementSetter() {
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, actors.get(i).getFirstName());
                        ps.setString(2, actors.get(i).getLastName());
                        ps.setLong(3, actors.get(i).getId().longValue());
                    }

                    public int getBatchSize() {
                        return actors.size();
                    }
                });
        return updateCounts;
    }

    // ... additional methods
}

ファイルを読み込んでのストリーム処理の場合、適切なバッチサイズを指定する必要がありますが、エントリの数が分からない可能性があります。この場合は、InterruptibleBatchPreparedStatementSetterインタフェースを使用します。このインタフェースにより入力ソースを読み終えるまでバッチをインタラプト出来ます。isBatchExhaustedでバッチの終了を判定可能です。

18.4.2 Batch operations with a List of objects

JdbcTemplateNamedParameterJdbcTemplateによりバッチ更新に別の方法を取ることが出来ます。専用のバッチインタフェースを実装する代わりに、リスト形式ですべてのパラメータ値を指定します。フレームワークがループ処理で内部的にプリペアードステートメントのセッターに与えます。このAPIは名前付きパラメータを使う点が異なります。名前付きパラメータではSqlParameterSourceの配列を使い、各エントリがバッチのメンバになります。配列の生成にはSqlParameterSource.createBatchを使い、引数にはJavaBeansの配列かパラメータ値を含むMapの配列を渡します。

名前付きパラメータを使用するバッチ更新の例は以下の通りです。

public class JdbcActorDao implements ActorDao {
    private NamedParameterTemplate namedParameterJdbcTemplate;

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

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

    // ... additional methods
}

クラシックな"?"プレースホルダを使うSQLステートメントの場合、更新する値のオブジェクト配列のリストを渡します。オブジェクト配列にはSQLステートメントの各プレースホルダに対応する一つのエントリを持つ必要があり、SQLステートメントの定義と同じ順序である必要があります。

上記と同じことをクラシックなJDBC"?"プレースホルダを使う例は以下です。

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);
        }
        int[] updateCounts = jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                batch);
        return updateCounts;
    }

    // ... additional methods

}

上記すべてのバッチ更新メソッドは各バッチエントリが影響を与えた行数を含むint配列を返します。このカウントはJDBCドライバがレポートするものです。カウントが利用不可能の場合、JDBCドライバは-2を返します。

18.4.3 Batch operations with multiple batches

バッチ更新の最後の例は巨大なバッチのためにいくつかのバッチに小分けしたい場合です。上記のbatchUpdateメソッド複数回呼び出して解決することも可能ですが、より便利なメソッドが存在します。このメソッドSQLステートメントに加え、パラメータを持つオブジェクトのCollection、各バッチごとの更新数、プリペアードステートメントのパラメータに値を設定するためのParameterizedPreparedStatementSetter、を渡します。フレームワークは与えられた値をループして指定サイズでバッチ更新を行います。

以下の例はサイズ100のバッチ更新の例です。

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

}

この場合のバッチ更新メソッドはintの二次元配列を返し、各バッチごとの配列で、その配列の各要素の配列は更新ごとに影響を受けた行数です。一次元目の配列長は実行したバッチの数で、二次元目の配列長は各バッチごとの更新数です。各バッチの更新数は指定のバッチサイズで最後の一つは指定のサイズより少なくなる事が多いですが、更新オブジェクトの合計数に依存します。更新ステートメントのカウントはJDBCがレポートしたものです。カウントが利用不可能の場合、JDBCドライバは-2を返します。

18.5 Simplifying JDBC operations with the SimpleJdbc classes

SimpleJdbcInsertSimpleJdbcCallJDBCドライバ経由で取得可能なデータベースメタデータを活かして設定の単純化をしています。つまり事前の設定が少なくて済みますが、もしコードで詳細を指定したい場合にはメタデータ処理を切ったりオーバーライド可能です。

18.5.1 Inserting data using SimpleJdbcInsert

最小の設定オプションでSimpleJdbcInsertクラスを見ていきます。データアクセスレイヤの初期化メソッドSimpleJdbcInsertインスタンス化します。例えば、初期化メソッドとはsetDataSourceなどです。SimpleJdbcInsertクラスをサブクラス化する必要は無く、単に新規のインスタンスをっ作成してwithTableNameメソッドでテーブル名を指定します。このクラスの設定メソッドは"fluid"(流れるようなインタフェース)スタイルなのでSimpleJdbcInsertインスタンスを返し、すべての設定メソッドをチェーンで呼び出せます。以下のサンプルでは設定メソッドは一つだけ使用しており、複数の例は後述します。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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
}

上記で使われているexecuteメソッドは単一パラメータとしてjava.utils.Mapを取ります。ここでの重要なポイントはMapのキーはデータベースに定義してあるテーブルのカラム名と一致する必要があります。実際のinsertステートメントを構築する際にメタデータを読み取るため、一致することは重要です。

18.5.2 Retrieving auto-generated keys using SimpleJdbcInsert

以下の例は前述と同じinsertですが、idを渡す代わりに自動生成キーを参照して新規のActorオブジェクトに設定します。SimpleJdbcInsertを生成する時にテーブル名に加えてusingGeneratedKeyColumnsメソッドで生成キーのカラム名を指定します。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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
}

前述の例とこのやり方でのinsert実行時の違いは、Mapにidを追加不要なのとexecuteAndReturnKeyメソッドを呼ぶ点です。このメソッドが返すのは、ドメインクラスで使われる数値型のインスタンスを生成可能なjava.lang.Numberとなります*2。特定のJavaクラスを返すようにはすべてのデータベースに依存出来ません。java.lang.Numberが依存可能なベースクラスです。複数の自動生成カラムがあるか、生成値が非数値の場合、executeAndReturnKeyHolderが返すKeyHolderを使います。

18.5.3 Specifying columns for a SimpleJdbcInsert

usingColumnsメソッドカラム名のリストを指定することによりinsertのカラムを制限できます。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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

}

使用カラムの決定にメタデータに依存していた場合と同じ事になります。

18.5.4 Using SqlParameterSource to provide parameter values

パラメータ値を渡すのにMapで十分動作しますが、便利だとは言えません。Springはその代わりとなるSqlParameterSourceインタフェースの実装を提供しています。最初の一つはBeanPropertySqlParameterSourceで、値をJavaBean互換のクラスで持つ場合には大変便利なクラスです。パラメータ値の抽出には対応するgetterメソッドを使います。以下がその例です。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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

}

別のオプションにMapSqlParameterSourceがあり、Mapに似ていますが、より便利なメソッドチェーンが可能なaddValueを持っています。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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

}

以上の例は、設定は共通で、それぞれの入力クラスを使うようにコードを変更している点だけが異なります。

18.5.5 Calling a stored procedure with SimpleJdbcCall

SimpleJdbcCallクラスはinoutパラメータ名をルックアップするのにデータベースのメタデータを参照するため、明示的にそれらを宣言する必要はありません。もし明示的に宣言したい場合や、Javaクラスへの自動マッピングが出来ないARRAYSTRUCTなどのパラメータがある場合、パラメータの宣言が可能です。最初の例はシンプルなプロシージャでMySQLデータベースからVARCHARDATEフォーマットのスカラ値を返します。サンプルのプロシージャは指定されたactorエントリを読み込みoutパラメータ形式でfirst_name, last_name, birth_dateを返します。

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パラメータは検索対象のactorのidです。outパラメータはテーブルから読み込んだデータを返します。

SimpleJdbcCallSimpleJdbcInsertと似たような形式で宣言されています。データアクセスレイヤの初期化メソッドでこのクラスのインスタンス化と設定を行います。StoredProcedureクラスとは対照的に、サブクラスを生成したりデータベースメタデータでルックアップ可能なパラメータを宣言してはいけません。以下は上記のストアドプロシージャを使うSimpleJdbcCall設定のサンプルです。設定オプションはDataSourceとストアドプロシージャ名だけです。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(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

}

上記のコードではINパラメータに渡す値を持つSqlParameterSourceを生成しています。重要な点は、ストアドプロシージャで宣言するパラメータ名と入力値の名前が一致していることです。このケースでは一致していませんが、ストアドプロシージャで参照されるデータベースオブジェクトの決定にメタデータを使用するためです。ストアドプロシージャのソースがデータベースにそのまま保存されるとは限りません。データベースによっては小文字をすべて大文字に変換するものがあります。

executeメソッドはINパラメータを取り、ストアドプロシージャで定義した名前がキーとなるoutパラメータのMapを返します。上記の例の場合ではout_first_name, out_last_name, out_birth_dateになります。

それからexecuteメソッドは取得したデータを返すためにActorインスタンスを生成します。繰り返しますが、重要な点は、ストアドプロシージャに宣言したoutパラメータ名を使います。なお、結果mapに格納されるoutパラメータ名とデータベースのoutパラメータ名は一致します。ただし、データベースによっては一致しない場合があります。コードの移植可能性を高めるには、大文字小文字を区別しないルックアップか、SpringにLinkedCaseInsensitiveMapを使うよう指定します。後者の場合、JdbcTemplateを生成してsetResultsMapCaseInsensitiveプロパティをtrueに設定します。それから、そのJdbcTemplateインスタンスSimpleJdbcCallコンストラクタに渡します。設定例は以下の通りです。

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

}

上記の方法により、outパラメータ名に関する衝突を避けられます。

18.5.6 Explicitly declaring parameters to use for a SimpleJdbcCall

メタデータに基づくパラメータの推定について見てきましたが、必要であれば明示的に宣言することも可能です。入力として複数SqlParameter変数を受け取るdeclareParametersメソッドSimpleJdbcCallを生成および設定することで行います。`SqlParameterを定義する方法の詳細については次のセクションで解説します。

Springがサポートしないデータベースを使用する場合に明示的な宣言が必要となります。今のところ、次のデータベースでストアドプロシージャ呼び出しのメタデータルックアップをサポートしています。Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle, and Sybase. また、MySQL, Microsoft SQL Server, and Oracleで、ストアド関数のメタデータルックアップをサポートしています。

明示的なパラメータ宣言を行う数は、すべて・複数・単一、から選択可能です。明示的なパラメータ宣言を行わない場合であっても、パラメータのメタデータは使用可能です。パラメータ候補のメタデータルックアップ処理をすべてバイパスし、パラメータ宣言のみを使うのであれば、宣言にwithoutProcedureColumnMetaDataAccessメソッドの呼び出しを含めます。Suppose that you have two or more different call signatures declared for a database function. INパラメータ名のリストを指定にはuseInParameterNamesを使います。

以下はプロシージャ呼び出しの例です。

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
}

実行結果は以前の例のものと同じになります。上記の例はメタデータではなく明示的にすべてを指定します。

18.5.7 How to define SqlParameters

Section 18.6, “Modeling JDBC operations as Java objects”で解説するRDBMSオペレーションクラス(RDBMS operations classes)とSimpleJdbcクラスでパラメータを定義するには、SqlParameterかそのサブクラスを使います。基本的にはコンストラクタでパラメータ名とSQL型を指定します。SQL型はjava.sql.Typesを使用して指定します。以前の例もあったように以下のように宣言します。

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

最初の行はSqlParameterでINパラメータを宣言しています。INパラメータはストアドプロシージャおよびSqlQueryと以降のセクションで解説するSqlQueryのサブクラスを使用するクエリで使用可能です。

その次の行はSqlOutParameterでストアドプロシージャコールで使うためのoutパラメータを宣言しています。またInOutパラメータにSqlInOutParameterがあり、これのパラメータにはプロシージャに渡すINを渡すと、と戻り値が得られます。

SqlParameterもしくはSqlInOutParameterとして宣言するパラメータは入力値を渡すためにだけ使います。StoredProcedureとは異なり、こちらは後方互換性を理由に入力値をSqlOutParameterで宣言するパラメータに渡すことが可能です*3

INパラメータでは、名前とSQL型に加えて、数値データのスケールやカスタムデータベース型の型名を指定可能です。outパラメータでは、REFカーソルから返される行のマッピング処理用にRowMapperを指定可能です。別の方法として、戻り値のカスタマイズ処理を定義するSqlReturnTypeを指定することも可能です。

18.5.8 Calling a stored function using SimpleJdbcCall

ストアトプロシージャ呼び出しと同様な方法でストアド関数を呼び出せます。この場合、プロシージャ名ではなく関数名を指定します。関数呼び出しを示すための設定としてwithFunctionNameメソッドを使用すると, and the corresponding string for a function call is generated. 特殊な実行呼び出しであるexecuteFunctionが関数呼び出しに使われ、指定型のオブジェクトとして関数の戻り値が返されます。つまり、結果mapから戻り値を取得してはいけません*4。似たようなメソッドexecuteObjectという簡易版があり、これはストアドプロシージャにも利用可能で、``outパラメータ一つのみ持ちます。以下の例はアクターのフルネームを返すget_actor_name```というストアド関数を使用しています。以下の関数はMySQLのものです。

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を再度生成します。

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

}

関数呼び出しから戻り値を含むStringが返されます。

18.5.9 Returning ResultSet/REF Cursor from a SimpleJdbcCall

結果セットを返すストアドプロシージャもしくは関数の呼び出しには若干の注意が必要です。DBによってはJDBCの結果処理中に結果セットを返すものや、明示的に特定の型をoutパラメータに登録する必要があります。どちらの場合も、result setのループ処理用と戻される行の処理に追加のコードが必要です。SimpleJdbcCallではreturningResultSetメソッドを使用して指定パラメータに対して使うRowMapperの実装を宣言します。この場合、結果セットは結果処理中に返され、名前の定義は無い(there are no names defined)ので、返される結果はRowMapper実装を宣言する順序に一致します。The name specified is still used to store the processed list of results in the results map that is returned from the execute statement.

次の例はINパラメータを取らないストアドプロシージャでt_actorテーブルから全行を取得します。以下のプロシージャはMySQLのものです。

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

このプロシージャを呼ぶにはRowMapperを宣言します。ここではJavaBeansでマッピングするクラスが必要なので、newInstanceマッピングのクラスを渡して生成するBeanPropertyRowMapperを使用可能です。

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

}

上記の呼び出しは引数を取らないので、空のMapを渡してexecuteを呼び出しています。Actorsのリストを結果mapから取得して、呼び出し元に返しています。

18.6 Modeling JDBC operations as Java objects

org.springframework.jdbc.objectパッケージのクラスを使うことでオブジェクト指向の作法に沿ってデータベースへのアクセスが可能になります。例として、リレーショナルのカラムデータをビジネスオブジェクトのプロパティにマップすることで、クエリの実行結果をビジネスオブジェクトのリストとして取得が可能です。また、ストアドプロシージャの実行や、update, delete, insertステートメントの実行も可能です。

以下で説明する各種のRDBMS操作クラスはJdbcTemplateへと置換可能である(StoredProcedureは例外)と、たいていのSpring開発者は考えています。JdbcTemplateメソッドを直接呼び出すようなDAOメソッドを書くことは簡単です(本格的なクラスでクラスをカプセル化することに比べると)。
ただし、RDBMS操作クラスを使用してmeasurable value*5を取得している場合、継続してそのクラスを使い続けてください。

18.6.1 SqlQuery

SqlQuerySQLクエリをカプセル化する再利用可能でスレッドセーフなクラスです。サブクラスはRowMapperを返すnewRowMapper(..)メソッドを実装する必要があります。RowMapperはクエリ実行中に生成されるResultSetのループ処理時に取得する行ごとに一オブジェクトを生成します。SqlQueryクラスを直接することは稀であり、サブクラスのMappingSqlQueryが行をJavaクラスへマッピングする便利な実装を提供しているためです。SqlQueryを拡張するその他の実装としてはMappingSqlQueryWithParametersUpdatableSqlQueryがあります。

18.6.2 MappingSqlQuery

ResultSetの各行を指定型のオブジェクトへ変換するためのmapRow(..)抽象メソッドを実装するサブクラスを用意することで、MappingSqlQueryは再利用可能なクエリとなります。以下の例はデータをt_actorリレーションからActorクラスのインスタンスにマップするカスタムクエリです。

public class ActorMappingQuery extends MappingSqlQuery<Actor> {

    public ActorMappingQuery(DataSource ds) {
        super(ds, "select id, first_name, last_name from t_actor where id = ?");
        super.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;
    }

}

MappingSqlQueryの拡張クラスはActor型のパラメータを付与しています。カスタムクエリのコンストラクタは単一のパラメータDataSourceを取ります。このコンストラクタではスーパークラスコンストラクタDataSourceSQLで呼び出しており、そのSQLは行を取得するために実行されるものです。このSQLPreparedStatementを使用するので、実行時にパラメータを渡すためのプレースホルダーを含めることが可能です。declareParameterメソッドSqlParameterを渡すことでパラメータを宣言する必要があります。SqlParameterは名前とjava.sql.Typesに定義されているJDBC型を取ります。すべてのパラメータ定義後に、compile()を呼び出すことでステートメントのpreparedと実行が可能になります。このクラスはコンパイル後にスレッドセーフとなるので、DAO初期化時にインスタンスを生成していれば、インスタンス変数としてキープすれば再利用となります。

private ActorMappingQuery actorMappingQuery;

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

public Customer getCustomer(Long id) {
    return actorMappingQuery.findObject(id);
}

上記例のメソッドは唯一のパラメータとして渡されるidでcustomerを取得します。この場合はオブジェクトを一つだけ返して欲しいので、パラメータにidを指定してfindObjectメソッドを呼びます。オブジェクトのリストを返すクエリを実行する場合、可変長引数にパラメータの配列を渡すexecuteメソッドのいずれかを使用します。

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

18.6.3 SqlUpdate

SqlUpdateSQLのudateをカプセル化するクラスです。query同様にupdateオブジェクトも再利用可能で、RdbmsOperationクラス群同様に、updateはパラメータを指定可能で、SQLを使用して定義出来ます。このクラスはqueryオブジェクトのexecute(..)と似たような構造で複数update(..)メソッドを持ちます。SQLUpdateクラスは具象クラスです。サブクラス化は可能で、例えば、以下のコード例のようにカスタムのupdateメソッドを追加可能です。このメソッドはただ単にexecuteを呼ぶだけです。ただし、SqlUpdateSQLの設定とパラメータ宣言によりパラメータ化が簡単なので、SqlUpdateのサブクラス化はしない方が良いです*6

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);
    }
}

18.6.4 StoredProcedure

StoredProcedureクラスはRDBMSのストアドプロシージャのオブジェクト抽象化用のスーパークラスです。このクラスはabstractで、各種のexecute(..)メソッドのアクセス制御はprotectedなので、サブクラス以外から使用することは出来ません。

sqlプロパティがRDBMSのストアドプロシージャ名になります。

StoredProcedureクラス用にパラメータを定義するには、SqlParameterかそのサブクラスを使います。以下のコード例のようにコンストラクタSQL型とパラメータ名の指定が必須です。SQL型はjava.sql.Typesに定義されています。

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

SqlParameter宣言の最初の列はINパラメータです。INパラメータはストアドプロシージャ呼び出しとSqlQueryおよび以降のセクションで解説するSqlQueryのサブクラスを使用するクエリの両方で使用可能です。

二行目のSqlOutParameterはストアドプロシージャ呼び出しで使うoutパラメータを宣言します。I nOutパラメータにはSqlInOutParameterも使用可能で、前者はプロシージャにinを、後者は戻り値のパラメータになります。

i nパラメータには、名前とSQL型に加えて、数値データの精度やカスタムデータベース型用の型名を指定可能です。outパラメータにはREFカーソルから返される行マッピングを処理するためのRowMapperを指定可能です。また、戻り値のカスタマイズ処理を定義するSqlReturnTypeも指定可能です。

以下の例は簡単なDAOで関数呼び出しにStoredProcedureを使い、Oracleデータベースのsysdate()を呼び出しています。ストアドプロシージャの機能を使うためにはStoredProcedureを拡張するクラスを用意します。この例では、StoredProcedureは内部クラスですが、StoredProcedureを他でも使いたいのであればトップレベルクラスとして宣言します。この例は入力パラメータを持たず、出力パラメータをSqlOutParameterクラスを使用してデータ型を宣言しています。execute()メソッドはプロシージャを実行して結果のMapから戻り値の日付を展開します。結果のMapは出力パラメータ宣言に対応するエントリを持ち、この例では1エントリだけで、キーはパラメータ名となります。

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

}

以下のStoredProcedure例は二つの出力パラメータを持ちます(この例ではOracleのREFカーソル)。

import org.springframework.jdbc.object.StoredProcedure;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

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>());
    }
}

TitlesAndGenresStoredProcedureコンストラクタで使っているdeclareParameter(..)オーバーロードメソッドRowMapper実装のインスタンスを渡している点に注意してください。このやり方は既存機能を利用する便利で強力な方法です。二つのRowMapper実装のコードは以下にあります。

TitleMapperクラスはResultSetの各行をTitleドメインオブジェクトにマッピングします。

import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import com.foo.domain.Title;

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

GenreMapperクラスはResultSetの各行をGenreドメインオブジェクトにマッピングします。

import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import com.foo.domain.Genre;

public final class GenreMapper implements RowMapper<Genre> {

    public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new Genre(rs.getString("name"));
    }
}

一つ以上のパラメータを持つストアドプロシージャにパラメータを渡すには、強い型付きのexecute(..)メソッドを自前で用意してスーパークラスの型付けが無いexecute(Map parameters)にデリゲートします。

import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;

import javax.sql.DataSource;

import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

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);
    }
}

18.7 Common problems with parameter and data value handling

18.9 Initializing a DataSource

org.springframework.jdbc.datasource.initパッケージは既存のDataSourceを初期化するサポートを提供します。組み込みデータベースサポートはアプリケーション向けのDataSourceの初期化と作成のオプションを提供しますが、場合によってはサーバーなどで実行するインスタンスを初期化する必要があります。

18.9.1 Initializing a database using Spring XML

データベースを初期化してDataSourceビーンの参照を取得したい場合、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>

上記の例はデータベースに対して二つのスクリプトを実行しており、前者はスキーマの生成、後者はテストデータセットの作成処理です。スクリプトの場所はSpringリソースで使われるantスタイルのワイルドカードパターン(classpath*:/com/foo/**/sql/*-data.sql)を使用可能です。パターンを使う場合、スクリプトの実行順序はURLやファイル名の辞書順となります。

データベース初期化のデフォルトの振る舞いは指定したスクリプトを無条件に実行します。この動作は常にユーザの期待通りというわけではなく、たとえば、テストデータ投入済みのデータベースに対してスクリプトを実行する場合などです。以下のような先ずテーブルを作成してからデータ投入するパターンに従うことで、不幸にもデータが削除されている状況の可能性は減らせます。テーブルが既に存在する場合には最初のステップが失敗します。

既存データの作成と削除をより細かく制御するには、XML名前空間がいくつかのオプションを提供します。一つ目は初期化のオンオフをスイッチするフラグです。この値は環境変数を設定可能です(例:システムプロパティもしくはenvironment beanからboolean値を取得する)。

<jdbc:initialize-database data-source="dataSource"
    enabled="#{systemProperties.INITIALIZE_DATABASE}">
    <jdbc:script location="..."/>
</jdbc:initialize-database>

二つ目のオプションは既存データに対して発生するエラーを許容するように制御します。スクリプト実行時の特定のSQLエラーを無視するようイニシャライザを制御可能です。

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
    <jdbc:script location="..."/>
</jdbc:initialize-database>

この例では、空のデータベースに対してスクリプトが実行される場合があると想定しており、その場合にはスクリプト中のDROPステートメントは失敗します。よって、DROPステートメントの失敗は無視しますが、その他のエラーは例外を吐きます。SQLダイアレクトがDROP ... IF EXISTSをサポートしないが、作成前に無条件にテストデータを全削除したい場合に便利です。その場合、最初のスクリプトDROPステートメントを実行し、次にCREATEステートメントを実行します。

ignore-failuresオプションに設定可能な値は、NONE(デフォルト)、DROPS(dropのエラーを無視)、ALL(すべてのエラーを無視)

XML名前空間で指定可能なものよりも細かく制御したい場合、DataSourceInitializerを直接使用可能で、アプリケーションのコンポーネントとして定義できます。

Initialization of other components that depend on the database

*1:potentially allowing for one thread connection per data source.が原文。これ1スレッド1コネクションという解釈で合ってるよね?

*2: This returns a java.lang.Number object with which you can create an instance of the numerical type that is used in our domain class.が原文。うまい日本語に出来なかった…

*3:which for backwards compatibility reasons allows input values to be provided for parameters declared as SqlOutParameter.が原文。ちょっと訳に自信が無い

*4:which means you do not have to retrieve the return value from the results map. が原文。訳に自信無い

*5:これ何?

*6:However, you don’t have to subclass the SqlUpdate class since it can easily be parameterized by setting SQL and declaring parameters.良く分からん