此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.4spring-doc.cadn.net.cn

参数和数据值处理的常见问题

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

为参数提供 SQL 类型信息

通常,Spring 会根据参数的类型来确定参数的 SQL 类型 传入。可以显式提供在设置 参数值。这有时是正确设置NULL值。spring-doc.cadn.net.cn

您可以通过多种方式提供 SQL 类型信息:spring-doc.cadn.net.cn

  • 许多 update 和 query 方法JdbcTemplate在 其形式为int数组。此数组用于指示 相应的参数,方法是使用java.sql.Types类。提供 每个参数一个条目。spring-doc.cadn.net.cn

  • 您可以使用SqlParameterValueclass 来包装需要这个 其他信息。为此,请为每个值创建一个新实例并传入 SQL 类型 和构造函数中的参数值。您还可以提供可选的比例 参数表示数值。spring-doc.cadn.net.cn

  • 对于使用命名参数的方法,您可以使用SqlParameterSourceBeanPropertySqlParameterSourceMapSqlParameterSource.他们都有方法 用于注册任何命名参数值的 SQL 类型。spring-doc.cadn.net.cn

处理 BLOB 和 CLOB 对象

您可以在数据库中存储图像、其他二进制数据和大块文本。这些 对于二进制数据,大型对象称为 BLOB(二进制大型对象),对于 CLOB(字符 Large OBject) 获取字符数据。在 Spring 中,您可以使用 这JdbcTemplate直接使用,也可以在使用 RDBMS 提供的更高抽象时 Objects 和SimpleJdbc类。所有这些方法都使用 这LobHandler用于实际管理 LOB (Large OBject) 数据的接口。LobHandler提供对LobCreator类,通过getLobCreator方法 用于创建要插入的新 LOB 对象。spring-doc.cadn.net.cn

LobCreatorLobHandler为 LOB 输入和输出提供以下支持:spring-doc.cadn.net.cn

下一个示例演示如何创建和插入 BLOB。稍后我们将介绍如何阅读 it 从数据库返回。spring-doc.cadn.net.cn

此示例使用JdbcTemplate以及AbstractLobCreatingPreparedStatementCallback.它实现了一种方法setValues.此方法提供了一个LobCreator,我们用它来设置 SQL 插入语句中的 LOB 列。spring-doc.cadn.net.cn

在这个例子中,我们假设有一个变量lobHandler,这已经是 设置为DefaultLobHandler.通常通过 依赖注入。spring-doc.cadn.net.cn

以下示例演示如何创建和插入 BLOB:spring-doc.cadn.net.cn

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 的内容。

如果调用setBlobAsBinaryStream,setClobAsAsciiStreamsetClobAsCharacterStream方法上的LobCreator返回自DefaultLobHandler.getLobCreator()中,您可以选择指定负值 对于contentLength论点。如果指定的内容长度为负数,则DefaultLobHandler使用 set-stream 方法的 JDBC 4.0 变体,而不使用 length 参数。否则,它将指定的长度传递给驱动程序。spring-doc.cadn.net.cn

请参阅您所使用的 JDBC 驱动程序的文档,以验证它是否支持流式处理 一个 LOB,但未提供内容长度。spring-doc.cadn.net.cn

现在,可以从数据库中读取 LOB 数据。同样,您使用JdbcTemplate具有相同的实例变量lobHandler和对DefaultLobHandler. 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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 标准允许根据包含 variable 的值列表。一个典型的例子是select * from T_ACTOR where id in (1, 2, 3).准备好的语句不直接支持此变量列表 JDBC 标准。不能声明可变数量的占位符。您需要一个号码 的变体,并准备好所需数量的占位符,或者您需要生成 SQL 字符串,一旦您知道需要多少个占位符。命名的 参数支持在NamedParameterJdbcTemplate采用后一种方法。 您可以将值作为java.util.List(或任何Iterable) 的简单值。 此列表用于将所需的占位符插入到实际的 SQL 语句中 并在语句执行期间传入值。spring-doc.cadn.net.cn

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

除了值列表中的基元值外,您还可以创建一个java.util.List的对象数组。此列表可以支持为in子句,例如select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop')).当然,这需要您的数据库支持此语法。spring-doc.cadn.net.cn

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

调用存储过程时,有时可以使用特定于 数据库。为了适应这些类型, Spring 提供了一个SqlReturnType用于搬运 当它们从存储过程调用返回时,以及SqlTypeValue当他们 作为参数传递给存储过程。spring-doc.cadn.net.cn

SqlReturnTypeinterface 有一个方法(名为getTypeValue),该 实现。此接口用作SqlOutParameter. 以下示例显示了返回java.sql.Struct用户的对象 declared type (声明的类型)ITEM_TYPE:spring-doc.cadn.net.cn

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

You can use SqlTypeValue to pass the value of a Java object (such as TestItem) to a stored procedure. The SqlTypeValue interface has a single method (named createTypeValue) that you must implement. The active connection is passed in, and you can use it to create database-specific objects, such as java.sql.Struct instances or java.sql.Array instances. The following example creates a java.sql.Struct instance:spring-doc.cadn.net.cn

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

You can now add this SqlTypeValue to the Map that contains the input parameters for the execute call of the stored procedure.spring-doc.cadn.net.cn

Another use for the SqlTypeValue is passing in an array of values to an Oracle stored procedure. Oracle has an createOracleArray method on OracleConnection that you can access by unwrapping it. You can use the SqlTypeValue to create an array and populate it with values from the Java java.sql.Array, as the following example shows:spring-doc.cadn.net.cn

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