参数和数据值处理的常见问题
Spring Framework 的 JDBC 支持提供了不同的方法,其中存在参数和数据值处理的常见问题。本节将介绍如何解决这些问题。
为参数提供 SQL 类型信息
通常,Spring 会根据传入参数的类型来确定参数的 SQL 类型。也可以显式提供设置参数值时使用的 SQL 类型。这有时对于正确设置 NULL
值是必要的。
您可以通过以下几种方式提供 SQL 类型信息:
-
JdbcTemplate
的许多更新和查询方法都带有一个int
数组形式的附加参数。此数组用于通过使用java.sql.Types
类中的常量值来指示相应参数的 SQL 类型。为每个参数提供一个条目。 -
您可以使用
SqlParameterValue
类包装需要此附加信息的参数值。为此,为每个值创建一个新实例,并在构造函数中传入 SQL 类型和参数值。您还可以为数值提供一个可选的比例参数。 -
对于使用命名参数的方法,您可以使用
SqlParameterSource
类,BeanPropertySqlParameterSource
或MapSqlParameterSource
。它们都具有为任何命名参数值注册 SQL 类型的方法。
处理 BLOB 和 CLOB 对象
您可以将图像、其他二进制数据和大量文本存储在数据库中。这些大型对象对于二进制数据称为 BLOB(Binary Large OBject),对于字符数据称为 CLOB(Character Large OBject)。在 Spring 中,您可以通过直接使用 JdbcTemplate
以及使用 RDBMS 对象和 SimpleJdbc
类提供的高级抽象来处理这些大型对象。所有这些方法都使用 LobHandler
接口的实现来实际管理 LOB(Large OBject)数据。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
-
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) { [id="CO1-1"][id="CO1-1"][id="CO1-1"](1) protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException { ps.setLong(1, 1L); lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); [id="CO1-2"][id="CO1-2"][id="CO1-2"](2) lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); [id="CO1-3"][id="CO1-3"][id="CO1-3"](3) } } ); blobIs.close(); clobReader.close();
<1> 传入 `lobHandler`(在此示例中是一个普通的 `DefaultLobHandler`)。 <1> 使用 `setClobAsCharacterStream` 方法传入 CLOB 的内容。 <1> 使用 `setBlobAsBinaryStream` 方法传入 BLOB 的内容。
- Kotlin
-
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) { [id="CO2-1"][id="CO1-4"][id="CO2-1"](1) override fun setValues(ps: PreparedStatement, lobCreator: LobCreator) { ps.setLong(1, 1L) lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt()) [id="CO2-2"][id="CO1-5"][id="CO2-2"](2) lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn.length().toInt()) [id="CO2-3"][id="CO1-6"][id="CO2-3"](3) } } ) blobIs.close() clobReader.close()
<1> 传入 `lobHandler`(在此示例中是一个普通的 `DefaultLobHandler`)。 <1> 使用 `setClobAsCharacterStream` 方法传入 CLOB 的内容。 <1> 使用 `setBlobAsBinaryStream` 方法传入 BLOB 的内容。
如果您在 |
现在是时候从数据库中读取 LOB 数据了。同样,您使用 JdbcTemplate
,其中包含相同的实例变量 lobHandler
和对 DefaultLobHandler
的引用。以下示例展示了如何执行此操作:
- Java
-
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"); [id="CO3-1"][id="CO1-7"][id="CO3-1"](1) results.put("CLOB", clobText); byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); [id="CO3-2"][id="CO1-8"][id="CO3-2"](2) results.put("BLOB", blobBytes); return results; } });
<1> 使用 `getClobAsString` 方法检索 CLOB 的内容。 <1> 使用 `getBlobAsBytes` 方法检索 BLOB 的内容。
- Kotlin
-
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ -> val clobText = lobHandler.getClobAsString(rs, "a_clob") [id="CO4-1"][id="CO1-9"][id="CO4-1"](1) val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob") [id="CO4-2"][id="CO1-10"][id="CO4-2"](2) mapOf("CLOB" to clobText, "BLOB" to blobBytes) }
<1> 使用 `getClobAsString` 方法检索 CLOB 的内容。 <1> 使用 `getBlobAsBytes` 方法检索 BLOB 的内容。
为 IN 子句传入值列表
SQL 标准允许根据包含可变值列表的表达式选择行。一个典型的例子是 select * from T_ACTOR where id in (1, 2, 3)
。JDBC 标准不直接支持预处理语句的这个可变列表。您不能声明可变数量的占位符。您需要准备具有所需数量占位符的多个变体,或者一旦知道需要多少占位符,就需要动态生成 SQL 字符串。NamedParameterJdbcTemplate
中提供的命名参数支持采用后一种方法。您可以将值作为 java.util.List
(或任何 Iterable
)的简单值传入。此列表用于将所需的占位符插入到实际的 SQL 语句中,并在语句执行期间传入值。
传入大量值时要小心。JDBC 标准不保证您可以在 |
除了值列表中的原始值之外,您还可以创建对象数组的 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
的 java.sql.Struct
对象的值:
您可以使用 SqlTypeValue
将 Java 对象(例如 TestItem
)的值传递给存储过程。SqlTypeValue
接口有一个必须实现的单个方法(名为 createTypeValue
)。活动连接被传入,您可以使用它来创建特定于数据库的对象,例如 java.sql.Struct
实例或 java.sql.Array
实例。以下示例创建了一个 java.sql.Struct
实例:
现在您可以将此 SqlTypeValue
添加到包含存储过程 execute
调用的输入参数的 Map
中。
SqlTypeValue
的另一个用途是将值数组传递给 Oracle 存储过程。Oracle 在 OracleConnection
上有一个 createOracleArray
方法,您可以通过解包它来访问。您可以使用 SqlTypeValue
创建一个数组并用 Java java.sql.Array
中的值填充它,如下例所示: