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

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

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

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

Actor actor = jdbcTemplate.queryForObject(
		"select first_name, last_name from t_actor where id = ?",
		(resultSet, rowNum) -> {
			Actor newActor = new Actor();
			newActor.setFirstName(resultSet.getString("first_name"));
			newActor.setLastName(resultSet.getString("last_name"));
			return newActor;
		},
		1212L);
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

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

fun findAllActors(): List<Actor> {
	return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
}

JdbcTemplate で(INSERTUPDATEDELETE)を更新

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

this.jdbcTemplate.update(
		"delete from t_actor where id = ?",
		Long.valueOf(actorId));
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())

その他の JdbcTemplate 操作

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

  • Java

  • Kotlin

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

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

  • Java

  • Kotlin

this.jdbcTemplate.update(
		"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
		Long.valueOf(unionId));
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

  • Kotlin

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

  • Kotlin

@Repository (1)
public class JdbcCorporateEventDao implements CorporateEventDao {

	private JdbcTemplate jdbcTemplate;

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

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

	private val jdbcTemplate = JdbcTemplate(dataSource) (3)

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

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

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

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

	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>

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

</beans>

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

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

NamedParameterJdbcTemplate を使用する

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

  • Java

  • Kotlin

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

  • Kotlin

// 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);
}
// some JDBC-backed DAO class...
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)

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

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

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

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

  • Java

  • Kotlin

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...
}
data class Actor(val id: Long, val firstName: String, val lastName: String)

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

  • Java

  • Kotlin

// 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);
}
// 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 ベストプラクティスも参照してください。

統合された JDBC クエリ / 更新操作: JdbcClient

6.1 では、NamedParameterJdbcTemplate の名前付きパラメーターステートメントと通常の JdbcTemplate の位置パラメーターステートメントは、流れるような対話モデルを備えた統合クライアント API を通じて利用できます。

例: 位置パラメーターを使用した場合:

private JdbcClient jdbcClient = JdbcClient.create(dataSource);

public int countOfActorsByFirstName(String firstName) {
	return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
			.param(firstName)
			.query(Integer.class).single();
}

例: 名前付きパラメーターの場合:

private JdbcClient jdbcClient = JdbcClient.create(dataSource);

public int countOfActorsByFirstName(String firstName) {
	return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
			.param("firstName", firstName)
			.query(Integer.class).single();
}

柔軟な結果解決を備えた RowMapper 機能も利用できます。

List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
		.query((rs, rowNum) -> new Actor(rs.getString("first_name"), rs.getString("last_name")))
		.list();

カスタム RowMapper の代わりに、マップ先のクラスを指定することもできます。例: Actor に、レコードクラス、カスタムコンストラクター、Bean プロパティ、またはプレーンフィールドとして firstName および lastName プロパティがあると仮定します。

List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
		.query(Actor.class)
		.list();

必要な単一オブジェクトの結果:

Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.single();

java.util.Optional の結果:

Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.optional();

そして更新ステートメントの場合:

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
		.param("Leonor").param("Watling")
		.update();

または、名前付きパラメーターを含む update ステートメント:

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
		.param("firstName", "Leonor").param("lastName", "Watling")
		.update();

個々の名前付きパラメーターの代わりに、パラメーターソースオブジェクト (たとえば、レコードクラス、Bean プロパティを持つクラス、または上記の Actor クラスなどの firstName および lastName プロパティを提供するプレーンフィールドホルダー) を指定することもできます。

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
		.paramSource(new Actor("Leonor", "Watling")
		.update();

パラメーターの自動 Actor クラスマッピングと上記のクエリ結果は、直接使用することもできる暗黙的な SimplePropertySqlParameterSource および SimplePropertyRowMapper 戦略を通じて提供されます。これらは、BeanPropertySqlParameterSource および BeanPropertyRowMapper/DataClassRowMapper の一般的な代替として機能し、また JdbcTemplate および NamedParameterJdbcTemplate 自体とも使用できます。

JdbcClient は、JDBC クエリ / 更新ステートメント用の柔軟かつ簡素化されたファサードです。バッチ挿入やストアドプロシージャ呼び出しなどの高度な機能には通常、追加のカスタマイズが必要です。JdbcClient で利用できないそのような機能については、Spring の SimpleJdbcInsert および SimpleJdbcCall クラス、または単純な直接 JdbcTemplate の使用を検討してください。

SQLExceptionTranslator を使用する

SQLExceptionTranslator は、SQLException と Spring 独自の org.springframework.dao.DataAccessException の間で変換できるクラスによって実装されるインターフェースであり、データアクセス戦略に関しては依存しません。実装は汎用 (たとえば、JDBC の SQLState コードを使用) にすることも、精度を高めるために独自仕様 (たとえば、Oracle エラーコードを使用) にすることもできます。この例外変換メカニズムは、SQLException ではなく DataAccessException を伝播する共通の JdbcTemplate および JdbcTransactionManager エントリポイントの背後で使用されます。

6.0 の時点で、デフォルトの例外トランスレーターは SQLExceptionSubclassTranslator で、いくつかの追加チェックと SQLStateSQLExceptionTranslator を介した SQLState イントロスペクションへのフォールバックで JDBC 4 SQLException サブクラスを検出します。通常、一般的なデータベースアクセスにはこれで十分であり、ベンダー固有の検出は必要ありません。下位互換性を確保するには、以下で説明するように、カスタムエラーコードマッピングを使用して SQLErrorCodeSQLExceptionTranslator を使用することを検討してください。

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

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

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

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

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

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

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

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

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

  • Java

  • Kotlin

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

  • Kotlin

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);
}
// 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 でエラーコードを検索するために、カスタムトランスレーターにデータソースが渡されます。

ステートメントの実行

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

  • Java

  • Kotlin

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

クエリの実行

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

  • Java

  • Kotlin

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

  • Kotlin

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

データベースの更新

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

  • Java

  • Kotlin

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);
	}
}
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 として、またはオブジェクトの配列として渡すことができます。プリミティブラッパークラスでプリミティブを明示的にラップするか、自動ボクシングを使用する必要があります。

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

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

  • Java

  • Kotlin

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

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

// keyHolder.getKey() now contains the generated key
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