|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
参数与数据值处理的常见问题
Spring框架的JDBC支持提供的不同方法中,参数和数据值的常见问题存在。本节将介绍如何解决这些问题。
为参数提供SQL类型信息
通常,Spring 会根据传入的参数类型来确定参数的 SQL 类型。可以在设置参数值时显式提供要使用的 SQL 类型。在正确设置 NULL 值时,这有时是必要的。
你可以通过几种方式提供SQL类型信息:
-
Many update and query methods of the
JdbcTemplatetake an additional parameter in the form of anintarray. This array is used to indicate the SQL type of the corresponding parameter by using constant values from thejava.sql.Typesclass. Provide one entry for each parameter. -
您可以使用
SqlParameterValue类来包装需要此附加信息的参数值。为此,为每个值创建一个新实例,并在构造函数中传入 SQL 类型和参数值。您还可以为数值提供一个可选的精度参数。 -
对于使用命名参数的方法,可以使用
SqlParameterSource类,BeanPropertySqlParameterSource或MapSqlParameterSource。它们都有方法 用于为任何命名参数值注册 SQL 类型。
处理BLOB和CLOB对象
您可以将图片、其他二进制数据和大块文本存储在数据库中。这些大型对象称为 BLOB(二进制大型对象)用于二进制数据,以及 CLOB(字符大型对象)用于字符数据。在 Spring 中,您可以通过直接使用 JdbcTemplate 以及使用 RDBMS Objects 和 SimpleJdbc 类提供的更高抽象来处理这些大型对象。所有这些方法都使用 LobHandler 接口的实现来实际管理 LOB(大型对象)数据。
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。该方法提供一个 LobCreator,我们用它来设置 SQL 插入语句中的 LOB 列的值。
在此示例中,我们假设有一个变量 lobHandler,它已经被设置为 DefaultLobHandler 的实例。您通常通过依赖注入来设置此值。
以下示例展示了如何创建并插入 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数据了。同样,你使用一个JdbcTemplate,相同的实例变量lobHandler以及对DefaultLobHandler的引用。
下面的例子展示了如何操作:
-
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
对象数组。此列表可以支持为 in
子句定义多个表达式,例如 select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2,
'Harrop'))。当然,这要求您的数据库支持此语法。
处理存储过程调用的复杂类型
当调用存储过程时,有时可以使用数据库特有的复杂类型。为了处理这些类型,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 添加到包含存储过程的 Map 调用输入参数的 execute 中。
SqlTypeValue 的另一种用途是将值数组传递给 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)
}
}
}
}