パラメーターとデータ値の処理に関する一般的な問題
Spring Framework の JDBC サポートによって提供されるさまざまなアプローチには、パラメーターとデータ値に関する一般的な問題が存在します。このセクションでは、それらに対処する方法について説明します。
パラメーターの SQL 型情報の提供
通常、Spring は、渡されたパラメーターの型に基づいてパラメーターの SQL 型を決定します。パラメーター値の設定時に使用される SQL 型を明示的に提供することができます。これは、NULL 値を正しく設定するために必要な場合があります。
SQL 型情報は、いくつかの方法で提供できます。
JdbcTemplateの多くの更新およびクエリメソッドは、int配列の形式で追加のパラメーターを取ります。この配列は、java.sql.Typesクラスの定数値を使用して、対応するパラメーターの SQL 型を示すために使用されます。パラメーターごとに 1 つのエントリを提供します。SqlParameterValueクラスを使用して、この追加情報を必要とするパラメーター値をラップできます。そのためには、値ごとに新しいインスタンスを作成し、コンストラクターで SQL 型とパラメーター値を渡します。数値にオプションのスケールパラメーターを指定することもできます。名前付きパラメーターで機能するメソッドの場合、
SqlParameterSourceクラス、BeanPropertySqlParameterSourceまたはMapSqlParameterSourceを使用できます。どちらにも、名前付きパラメーター値のいずれかに SQL 型を登録するためのメソッドがあります。
BLOB および CLOB オブジェクトの処理
イメージ、その他のバイナリデータ、大量のテキストをデータベースに保存できます。これらのラージオブジェクトは、バイナリデータでは BLOB(バイナリラージ OBject)、文字データでは CLOB(キャラクターラージ OBject)と呼ばれます。Spring では、JdbcTemplate を直接使用することにより、また RDBMS オブジェクトおよび SimpleJdbc クラスにより提供されるより高い抽象化を使用することにより、これらの大きなオブジェクトを処理できます。これらのアプローチはすべて、LOB(Large OBject)データの実際の管理に LobHandler インターフェースの実装を使用します。LobHandler は、getLobCreator メソッドを介して LobCreator クラスへのアクセスを提供します。このメソッドは、挿入される新しい LOB オブジェクトの作成に使用されます。
LobCreator および LobHandler は、LOB 入力および出力に対して以下のサポートを提供します。
BLOB
byte[]:getBlobAsBytesおよびsetBlobAsBytesInputStream:getBlobAsBinaryStreamおよびsetBlobAsBinaryStream
CLOB
String:getClobAsStringおよびsetClobAsStringInputStream:getClobAsAsciiStreamおよびsetClobAsAsciiStreamReader:getClobAsCharacterStreamおよびsetClobAsCharacterStream
次の例は、BLOB を作成および挿入する方法を示しています。後で、データベースからそれを読み戻す方法を示します。
この例では、JdbcTemplate と AbstractLobCreatingPreparedStatementCallback の実装を使用しています。setValues という 1 つのメソッドを実装します。このメソッドは、SQL insert ステートメントの LOB 列の値を設定するために使用する LobCreator を提供します。
この例では、DefaultLobHandler のインスタンスにすでに設定されている変数 lobHandler があると想定しています。通常、この値は依存性注入を通じて設定します。
次の例は、BLOB を作成および挿入する方法を示しています。
Java
Kotlin
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);
jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
new AbstractLobCreatingPreparedStatementCallback(lobHandler) { (1)
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
ps.setLong(1, 1L);
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); (2)
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); (3)
}
}
);
blobIs.close();
clobReader.close();| 1 | lobHandler を渡します(この例では)プレーン DefaultLobHandler です。 |
| 2 | メソッド setClobAsCharacterStream を使用して、CLOB のコンテンツを渡します。 |
| 3 | メソッド setBlobAsBinaryStream を使用して、BLOB のコンテンツを渡します。 |
val blobIn = File("spring2004.jpg")
val blobIs = FileInputStream(blobIn)
val clobIn = File("large.txt")
val clobIs = FileInputStream(clobIn)
val clobReader = InputStreamReader(clobIs)
jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
object: AbstractLobCreatingPreparedStatementCallback(lobHandler) { (1)
override fun setValues(ps: PreparedStatement, lobCreator: LobCreator) {
ps.setLong(1, 1L)
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt()) (2)
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn.length().toInt()) (3)
}
}
)
blobIs.close()
clobReader.close()| 1 | lobHandler を渡します(この例では)プレーン DefaultLobHandler です。 |
| 2 | メソッド setClobAsCharacterStream を使用して、CLOB のコンテンツを渡します。 |
| 3 | メソッド setBlobAsBinaryStream を使用して、BLOB のコンテンツを渡します。 |
使用する JDBC ドライバーのドキュメントを参照して、コンテンツの長さを指定せずに LOB のストリーミングをサポートしていることを確認してください。 |
次に、データベースから LOB データを読み取ります。再び、同じインスタンス変数 lobHandler と DefaultLobHandler への参照を持つ JdbcTemplate を使用します。次の例は、その方法を示しています。
Java
Kotlin
List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
new RowMapper<Map<String, Object>>() {
public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
Map<String, Object> results = new HashMap<String, Object>();
String clobText = lobHandler.getClobAsString(rs, "a_clob"); (1)
results.put("CLOB", clobText);
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); (2)
results.put("BLOB", blobBytes);
return results;
}
});| 1 | メソッド getClobAsString を使用して、CLOB の内容を取得します。 |
| 2 | メソッド getBlobAsBytes を使用して、BLOB のコンテンツを取得します。 |
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ ->
val clobText = lobHandler.getClobAsString(rs, "a_clob") (1)
val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob") (2)
mapOf("CLOB" to clobText, "BLOB" to blobBytes)
}| 1 | メソッド getClobAsString を使用して、CLOB の内容を取得します。 |
| 2 | メソッド getBlobAsBytes を使用して、BLOB のコンテンツを取得します。 |
IN 句の値のリストを渡す
SQL 標準では、値の変数リストを含む式に基づいて行を選択できます。典型的な例は select * from T_ACTOR where id in (1, 2, 3) です。この変数リストは、JDBC 標準による準備済みステートメントでは直接サポートされていません。可変数のプレースホルダーを宣言することはできません。必要な数のプレースホルダーを備えた多数のバリエーションを準備する必要があるか、必要なプレースホルダーの数がわかったら SQL 文字列を動的に生成する必要があります。NamedParameterJdbcTemplate で提供される名前付きパラメーターのサポートは、後者のアプローチを採用しています。値を単純な値の java.util.List (または任意の Iterable) として渡すことができます。このリストは、必要なプレースホルダーを実際の SQL ステートメントに挿入し、ステートメントの実行中に値を渡すために使用されます。
多くの値を渡すときは注意してください。JDBC 標準では、IN 式リストに 100 を超える値を使用できることを保証していません。さまざまなデータベースがこの数を超えていますが、通常、許可される値の数には厳しい制限があります。例: Oracle の制限は 1000 です。 |
値リストのプリミティブ値に加えて、オブジェクト配列の java.util.List を作成できます。このリストは、select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop')) など、in 節に対して定義されている複数の式をサポートできます。もちろん、これにはデータベースがこの構文をサポートしている必要があります。
ストアドプロシージャコールの複合型の処理
ストアドプロシージャを呼び出すときに、データベース固有の複合型を使用できる場合があります。これらの型に対応するために、Spring は、ストアドプロシージャコールから返されるときに処理する SqlReturnType と、ストアドプロシージャにパラメーターとして渡されるときに SqlTypeValue を提供します。
SqlReturnType インターフェースには、実装する必要がある単一のメソッド ( getTypeValue という名前) があります。このインターフェースは、SqlOutParameter の宣言の一部として使用されます。次の例は、ユーザーが宣言した型 ITEM_TYPE の java.sql.Struct オブジェクトの値を返す方法を示しています。
Java
Kotlin
import java.sql.CallableStatement;
import java.sql.Struct;
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class TestItemStoredProcedure extends StoredProcedure {
public TestItemStoredProcedure(DataSource dataSource) {
super(dataSource, "get_item");
declareParameter(new SqlOutParameter("item", Types.STRUCT, "ITEM_TYPE",
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
Struct struct = (Struct) cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String) attr[1]);
item.setExpirationDate((java.util.Date) attr[2]);
return item;
}));
// ...
}
}import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.`object`.StoredProcedure
import java.sql.CallableStatement
import java.sql.Struct
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, "get_item") {
init {
declareParameter(SqlOutParameter("item",Types.STRUCT,"ITEM_TYPE") {
cs: CallableStatement, colIndx: Int, _: Int, _: String? ->
val struct = cs.getObject(colIndx) as Struct
val attr = struct.attributes
TestItem(
(attr[0] as Number).toLong(),
attr[1] as String,
attr[2] as Date
)
})
// ...
}
}SqlTypeValue を使用して、Java オブジェクト(TestItem など)の値をストアドプロシージャに渡すことができます。SqlTypeValue インターフェースには、実装が必要な単一のメソッド(createTypeValue という名前)があります。アクティブな接続が渡され、それを使用して、java.sql.Struct インスタンスや java.sql.Array インスタンスなどのデータベース固有のオブジェクトを作成できます。次の例では、java.sql.Struct インスタンスを作成します。
Java
Kotlin
TestItem testItem = new TestItem(123L, "A test item",
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection connection, int sqlType, String typeName) throws SQLException {
Object[] item = new Object[] { testItem.getId(), testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime()) };
return connection.createStruct(typeName, item);
}
};val testItem = TestItem(123L, "A test item",
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(connection: Connection, sqlType: Int, typeName: String?): Any {
val item = arrayOf<Any>(testItem.id, testItem.description,
Date(testItem.expirationDate.time))
return connection.createStruct(typeName, item)
}
} これで、この SqlTypeValue を、ストアドプロシージャの execute 呼び出しの入力パラメーターを含む Map に追加できます。
SqlTypeValue のもう 1 つの用途は、値の配列を Oracle ストアドプロシージャに渡すことです。Oracle には、OracleConnection に createOracleArray メソッドがあり、これをアンラップすることでアクセスできます。次の例に示すように、SqlTypeValue を使用して配列を作成し、Java java.sql.Array からの値で配列を設定できます。
Java
Kotlin
Long[] ids = new Long[] {1L, 2L};
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
return conn.unwrap(OracleConnection.class).createOracleArray(typeName, ids);
}
};val ids = arrayOf(1L, 2L)
val value: SqlTypeValue = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
return conn.unwrap(OracleConnection::class.java).createOracleArray(typeName, ids)
}
}