SimpleJdbc
クラスを使用した JDBC 操作の簡素化
SimpleJdbcInsert
および SimpleJdbcCall
クラスは、JDBC ドライバーを介して取得できるデータベースメタデータを利用することにより、簡素化された構成を提供します。これは、コードのすべての詳細を提供することを希望する場合、メタデータ処理をオーバーライドまたはオフにできますが、事前に構成する必要が少ないことを意味します。
SimpleJdbcInsert
を使用したデータの挿入
まず、最小限の構成オプションで SimpleJdbcInsert
クラスを調べることから始めます。データアクセスレイヤーの初期化メソッドで SimpleJdbcInsert
をインスタンス化する必要があります。この例では、初期化メソッドは setDataSource
メソッドです。SimpleJdbcInsert
クラスをサブクラス化する必要はありません。代わりに、withTableName
メソッドを使用して、新しいインスタンスを作成し、テーブル名を設定できます。このクラスの構成メソッドは、SimpleJdbcInsert
のインスタンスを返す fluid
スタイルに従います。これにより、すべての構成メソッドをチェーンにできます。次の例では、1 つの構成方法のみを使用します(複数の方法の例を後で示します)。
Java
Kotlin
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<>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... additional methods
}
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
に使用されるキーは、データベースで定義されているテーブルの列名と一致する必要があるということです。これは、メタデータを読み取って実際の挿入ステートメントを作成するためです。
SimpleJdbcInsert
を使用して自動生成されたキーを取得する
次の例では、前の例と同じ挿入を使用しますが、id
を渡す代わりに、自動生成キーを取得して、新しい Actor
オブジェクトに設定します。SimpleJdbcInsert
を作成するとき、テーブル名の指定に加えて、usingGeneratedKeyColumns
メソッドで生成されたキー列の名前を指定します。次のリストは、その仕組みを示しています。
Java
Kotlin
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<>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... additional methods
}
この 2 番目のアプローチを使用して挿入を実行する場合の主な違いは、id
を Map
に追加せず、executeAndReturnKey
メソッドを呼び出すことです。これは、ドメインクラスで使用される数値型のインスタンスを作成できる java.lang.Number
オブジェクトを返します。ここで特定の Java クラスを返すためにすべてのデータベースに依存することはできません。java.lang.Number
は、信頼できる基本クラスです。複数の自動生成列がある場合、または生成された値が非数値の場合、executeAndReturnKeyHolder
メソッドから返される KeyHolder
を使用できます。
SimpleJdbcInsert
の列の指定
次の例に示すように、usingColumns
メソッドを使用して列名のリストを指定することにより、挿入の列を制限できます。
Java
Kotlin
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<>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... additional methods
}
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
}
挿入の実行は、使用する列を決定するためにメタデータに依存していた場合と同じです。
SqlParameterSource
を使用してパラメーター値を提供する
Map
を使用してパラメーター値を提供することは正常に機能しますが、使用するのに最も便利なクラスではありません。Spring は、代わりに使用できる SqlParameterSource
インターフェースの実装をいくつか提供します。最初のものは BeanPropertySqlParameterSource
です。これは、値を含む JavaBean 準拠のクラスがある場合に非常に便利なクラスです。対応する getter メソッドを使用して、パラメーター値を抽出します。次の例は、BeanPropertySqlParameterSource
の使用方法を示しています。
Java
Kotlin
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
}
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
Kotlin
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
}
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
}
ご覧のとおり、構成は同じです。これらの代替入力クラスを使用するには、実行中のコードのみを変更する必要があります。
SimpleJdbcCall
を使用したストアドプロシージャの呼び出し
SimpleJdbcCall
クラスは、データベース内のメタデータを使用して in
および out
パラメーターの名前を検索するため、明示的に宣言する必要はありません。パラメーターを宣言することもできます (宣言する場合、または Java クラスに自動的にマッピングされないパラメーターがある場合)。最初の例は、MySQL データベースから VARCHAR
および DATE
形式のスカラー値のみを返す簡単な手順を示しています。この例の手順は、指定されたアクターエントリを読み取り、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
パラメーターには、検索しているアクターの id
が含まれています。out
パラメーターは、テーブルから読み取ったデータを返します。
SimpleJdbcInsert
の宣言と同様の方法で SimpleJdbcCall
を宣言できます。データアクセスレイヤーの初期化メソッドでクラスをインスタンス化して構成する必要があります。StoredProcedure
クラスと比較して、サブクラスを作成する必要はなく、データベースメタデータで検索できるパラメーターを宣言する必要もありません。次の SimpleJdbcCall
構成の例では、前述のストアドプロシージャを使用しています(DataSource
に加えて唯一の構成オプションは、ストアドプロシージャの名前です)。
Java
Kotlin
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
}
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
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... additional methods
}
このアクションを実行することにより、返される out
パラメーターの名前に使用される大文字小文字の競合を回避できます。
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
Kotlin
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
}
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 番目の例では、メタデータに依存するのではなく、すべての詳細を明示的に指定します。
SqlParameters
を定義する方法
SimpleJdbc
クラスおよび RDBMS 操作クラス(JDBC 操作を Java オブジェクトとしてモデル化するでカバー)のパラメーターを定義するには、SqlParameter
またはそのサブクラスの 1 つを使用できます。そのためには、通常、コンストラクターでパラメーター名と SQL 型を指定します。SQL 型は、java.sql.Types
定数を使用して指定されます。この章の前半で、次のような宣言を見ました。
Java
Kotlin
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
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
を指定することです。
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
Kotlin
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource 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
}
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
を返します。
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
Kotlin
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
}
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
を渡します。これは、この呼び出しがパラメーターを受け取らないためです。次に、アクターのリストが結果マップから取得され、呼び出し元に返されます。