パラメーターとデータ値の処理に関する一般的な問題

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

    • InputStreamgetBlobAsBinaryStream および setBlobAsBinaryStream

  • CLOB

    • StringgetClobAsString および setClobAsString

    • InputStreamgetClobAsAsciiStream および setClobAsAsciiStream

    • ReadergetClobAsCharacterStream および 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();
1lobHandler を渡します(この例では)プレーン 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()
1lobHandler を渡します(この例では)プレーン DefaultLobHandler です。
2 メソッド setClobAsCharacterStream を使用して、CLOB のコンテンツを渡します。
3 メソッド setBlobAsBinaryStream を使用して、BLOB のコンテンツを渡します。

DefaultLobHandler.getLobCreator() から返された LobCreator で setBlobAsBinaryStreamsetClobAsAsciiStreamsetClobAsCharacterStream メソッドを呼び出す場合は、オプションで contentLength 引数に負の値を指定できます。指定されたコンテンツの長さが負の場合、DefaultLobHandler は、長さパラメーターのないセットストリームメソッドの JDBC 4.0 バリアントを使用します。それ以外の場合は、指定された長さをドライバーに渡します。

使用する 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 の Oracle STRUCT オブジェクトの値を返すことを示しています。

  • Java

  • Kotlin

public class TestItemStoredProcedure extends StoredProcedure {

	public TestItemStoredProcedure(DataSource dataSource) {
		// ...
		declareParameter(new SqlOutParameter("item", OracleTypes.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;
			}));
		// ...
	}
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {

	init {
		// ...
		declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
			val struct = cs.getObject(colIndx) as STRUCT
			val attr = struct.getAttributes()
			TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
		})
		// ...
	}
}

SqlTypeValue を使用して、Java オブジェクト(TestItem など)の値をストアドプロシージャに渡すことができます。SqlTypeValue インターフェースには、実装が必要な単一のメソッド(createTypeValue という名前)があります。アクティブな接続が渡され、それを使用して、StructDescriptor インスタンスや ArrayDescriptor インスタンスなどのデータベース固有のオブジェクトを作成できます。次の例では、StructDescriptor インスタンスを作成します。

  • Java

  • Kotlin

final 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 conn, int sqlType, String typeName) throws SQLException {
		StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
		Struct item = new STRUCT(itemDescriptor, conn,
		new Object[] {
			testItem.getId(),
			testItem.getDescription(),
			new java.sql.Date(testItem.getExpirationDate().getTime())
		});
		return item;
	}
};
val (id, description, expirationDate) = TestItem(123L, "A test item",
		SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))

val value = object : AbstractSqlTypeValue() {
	override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
		val itemDescriptor = StructDescriptor(typeName, conn)
		return STRUCT(itemDescriptor, conn,
				arrayOf(id, description, java.sql.Date(expirationDate.time)))
	}
}

これで、この SqlTypeValue を、ストアドプロシージャの execute 呼び出しの入力パラメーターを含む Map に追加できます。

SqlTypeValue のもう 1 つの用途は、値の配列を Oracle ストアドプロシージャに渡すことです。Oracle にはこの場合に使用する必要のある独自の内部 ARRAY クラスがあり、SqlTypeValue を使用して Oracle ARRAY のインスタンスを作成し、次の例に示すように Java ARRAY から値を取り込むことができます。

  • Java

  • Kotlin

final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
	protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
		ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
		ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
		return idArray;
	}
};
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {

	init {
		val ids = arrayOf(1L, 2L)
		val value = object : AbstractSqlTypeValue() {
			override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
				val arrayDescriptor = ArrayDescriptor(typeName, conn)
				return ARRAY(arrayDescriptor, conn, ids)
			}
		}
	}
}