不同方法中存在参数和数据值的常见问题 由 Spring Framework 的 JDBC 支持提供。本节介绍如何解决这些问题。

为参数提供 SQL 类型信息

通常,Spring 会根据参数的类型来确定参数的 SQL 类型 传了进来。可以显式提供在设置时要使用的 SQL 类型 参数值。这有时对于正确设置值是必要的。NULL

您可以通过多种方式提供 SQL 类型信息:

  • 的许多更新和查询方法都采用一个附加参数 数组的形式。此数组用于指示 通过使用类中的常量值来对应参数。提供 每个参数一个条目。JdbcTemplateintjava.sql.Types

  • 您可以使用该类来包装需要此项的参数值 其他信息。为此,请为每个值创建一个新实例并传入 SQL 类型 以及构造函数中的参数值。您还可以提供可选的刻度 数值的参数。SqlParameterValue

  • 对于使用命名参数的方法,可以使用类或 .他们都有方法 用于注册任何命名参数值的 SQL 类型。SqlParameterSourceBeanPropertySqlParameterSourceMapSqlParameterSource

处理 BLOB 和 CLOB 对象

您可以在数据库中存储图像、其他二进制数据和大块文本。这些 对于二进制数据,大型对象称为 BLOB(二进制大型 OBject),而 CLOB(字符 Large OBject) 用于字符数据。在 Spring 中,您可以使用 直接以及使用 RDBMS 提供的更高抽象时也是如此 对象和类。所有这些方法都使用 用于实际管理 LOB(大型 OBject)数据的接口。 通过以下方法提供对类的访问, 用于创建要插入的新 LOB 对象。JdbcTemplateSimpleJdbcLobHandlerLobHandlerLobCreatorgetLobCreator

LobCreator并为 LOB 输入和输出提供以下支持:LobHandler

  • BLOB

    • byte[]:和getBlobAsBytessetBlobAsBytes

    • InputStream:和getBlobAsBinaryStreamsetBlobAsBinaryStream

  • CLOB的

    • String:和getClobAsStringsetClobAsString

    • InputStream:和getClobAsAsciiStreamsetClobAsAsciiStream

    • Reader:和getClobAsCharacterStreamsetClobAsCharacterStream

下一个示例演示如何创建和插入 BLOB。稍后我们将展示如何阅读 它从数据库中返回。

此示例使用 .它实现一个方法,.此方法提供了一个我们用来设置 SQL insert 语句中的 LOB 列。JdbcTemplateAbstractLobCreatingPreparedStatementCallbacksetValuesLobCreator

在这个例子中,我们假设有一个变量,它已经是 设置为 的实例。通常通过以下方式设置此值 依赖注入。lobHandlerDefaultLobHandler

以下示例演示如何创建和插入 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 传入 that(在本例中)是一个普通的 .lobHandlerDefaultLobHandler
2 使用该方法传入 CLOB 的内容。setClobAsCharacterStream
3 使用该方法传入 BLOB 的内容。setBlobAsBinaryStream
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 传入 that(在本例中)是一个普通的 .lobHandlerDefaultLobHandler
2 使用该方法传入 CLOB 的内容。setClobAsCharacterStream
3 使用该方法传入 BLOB 的内容。setBlobAsBinaryStream

如果在返回的 上调用 、 或 方法,则可以选择指定负值 对于参数。如果指定的内容长度为负数,则使用 set-stream 方法的 JDBC 4.0 变体,而不使用 length 参数。否则,它会将指定的长度传递给驱动程序。setBlobAsBinaryStreamsetClobAsAsciiStreamsetClobAsCharacterStreamLobCreatorDefaultLobHandler.getLobCreator()contentLengthDefaultLobHandler

请参阅用于验证其支持流式处理的 JDBC 驱动程序的文档 一个 LOB,但不提供内容长度。

现在是时候从数据库中读取 LOB 数据了。同样,将 a 与相同的实例变量和对 . 以下示例演示如何执行此操作:JdbcTemplatelobHandlerDefaultLobHandler

  • 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 使用该方法检索 CLOB 的内容。getClobAsString
2 使用该方法检索 BLOB 的内容。getBlobAsBytes
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 使用该方法检索 CLOB 的内容。getClobAsString
2 使用该方法检索 BLOB 的内容。getBlobAsBytes

传入 IN 子句的值列表

SQL 标准允许根据包含 变量值列表。一个典型的例子是。此变量列表不直接支持预准备语句 JDBC标准。不能声明可变数量的占位符。你需要一个数字 的变体,并准备了所需数量的占位符,或者您需要生成 一旦知道需要多少个占位符,就会动态地使用 SQL 字符串。命名的 中提供的参数支持采用后一种方法。 您可以将值作为简单值的(或任何)传递。 此列表用于将所需的占位符插入到实际的 SQL 语句中 并在语句执行期间传入值。select * from T_ACTOR where id in (1, 2, 3)NamedParameterJdbcTemplatejava.util.ListIterable

传入许多值时要小心。JDBC 标准不保证 表达式列表可以使用 100 个以上的值。各种数据库超过 这个数字,但它们通常对允许的值数有硬性限制。 例如,Oracle 的限制是 1000。IN

除了值列表中的基元值之外,还可以创建对象数组。此列表可以支持为子句定义的多个表达式,例如 。当然,这需要您的数据库支持此语法。java.util.Listinselect * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))

处理存储过程调用的复杂类型

调用存储过程时,有时可以使用特定于 数据库。为了适应这些类型,Spring 提供了一个用于处理 当它们从存储过程调用返回时,以及当它们 作为参数传入存储过程。SqlReturnTypeSqlTypeValue

接口具有一个方法(名为 ),该方法必须是 实现。此接口用作 . 以下示例演示如何返回用户的 Oracle 对象的值 声明类型:SqlReturnTypegetTypeValueSqlOutParameterSTRUCTITEM_TYPE

  • 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)
		})
		// ...
	}
}

您可以使用将 Java 对象的值(例如 )传递给 存储过程。该接口具有必须实现的单个方法(名为 )。活动连接传入,您 可以使用它来创建特定于数据库的对象,例如实例 或实例。以下示例创建一个实例:SqlTypeValueTestItemSqlTypeValuecreateTypeValueStructDescriptorArrayDescriptorStructDescriptor

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

现在,您可以将其添加到包含用于调用存储过程的输入参数的 中。SqlTypeValueMapexecute

的另一个用途是将值数组传递给存储的 Oracle 程序。Oracle 有自己的内部类,在这种情况下必须使用,并且 您可以使用 创建 Oracle 实例并填充 它带有来自 Java 的值,如以下示例所示:SqlTypeValueARRAYSqlTypeValueARRAYARRAY

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