パラメーターとデータ値の処理に関する一般的な問題
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
およびsetBlobAsBytes
InputStream
:getBlobAsBinaryStream
およびsetBlobAsBinaryStream
CLOB
String
:getClobAsString
およびsetClobAsString
InputStream
:getClobAsAsciiStream
およびsetClobAsAsciiStream
Reader
: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
val item = TestItem()
item.id = (attr[0] as Number).toLong()
item.description = attr[1] as String
item.expirationDate = attr[2] as Date
item
})
// ...
}
}
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)
}
}