此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
参数和数据值处理的常见问题
参数和数据值的常见问题存在于不同的方法中 由 Spring Framework 的 JDBC 支持提供。本节介绍如何解决这些问题。
为参数提供 SQL 类型信息
通常,Spring 会根据参数的类型来确定参数的 SQL 类型
传入。可以显式提供在设置
参数值。这有时是正确设置NULL
值。
您可以通过多种方式提供 SQL 类型信息:
-
许多 update 和 query 方法
JdbcTemplate
在 其形式为int
数组。此数组用于指示 相应的参数,方法是使用java.sql.Types
类。提供 每个参数一个条目。 -
您可以使用
SqlParameterValue
class 来包装需要这个 其他信息。为此,请为每个值创建一个新实例并传入 SQL 类型 和构造函数中的参数值。您还可以提供可选的比例 参数表示数值。 -
对于使用命名参数的方法,您可以使用
SqlParameterSource
类BeanPropertySqlParameterSource
或MapSqlParameterSource
.他们都有方法 用于注册任何命名参数值的 SQL 类型。
处理 BLOB 和 CLOB 对象
您可以在数据库中存储图像、其他二进制数据和大块文本。这些
对于二进制数据,大型对象称为 BLOB(二进制大型对象),对于 CLOB(字符
Large OBject) 获取字符数据。在 Spring 中,您可以使用
这JdbcTemplate
直接使用,也可以在使用 RDBMS 提供的更高抽象时
Objects 和SimpleJdbc
类。所有这些方法都使用
这LobHandler
用于实际管理 LOB (Large OBject) 数据的接口。LobHandler
提供对LobCreator
类,通过getLobCreator
方法
用于创建要插入的新 LOB 对象。
LobCreator
和LobHandler
为 LOB 输入和输出提供以下支持:
-
斑点
-
byte[]
:getBlobAsBytes
和setBlobAsBytes
-
InputStream
:getBlobAsBinaryStream
和setBlobAsBinaryStream
-
-
CLOB
-
String
:getClobAsString
和setClobAsString
-
InputStream
:getClobAsAsciiStream
和setClobAsAsciiStream
-
Reader
:getClobAsCharacterStream
和setClobAsCharacterStream
-
下一个示例演示如何创建和插入 BLOB。稍后我们将介绍如何阅读 it 从数据库返回。
此示例使用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 标准允许根据包含
variable 的值列表。一个典型的例子是select * from T_ACTOR where id in
(1, 2, 3)
.准备好的语句不直接支持此变量列表
JDBC 标准。不能声明可变数量的占位符。您需要一个号码
的变体,并准备好所需数量的占位符,或者您需要生成
SQL 字符串,一旦您知道需要多少个占位符。命名的
参数支持在NamedParameterJdbcTemplate
采用后一种方法。
您可以将值作为java.util.List
(或任何Iterable
) 的简单值。
此列表用于将所需的占位符插入到实际的 SQL 语句中
并在语句执行期间传入值。
传入许多值时要小心。JDBC 标准不保证
您可以使用 100 多个值来表示IN expression 列表。各种数据库超过
这个数字,但它们通常对允许的值数量有硬性限制。
例如,Oracle 的限制为 1000。 |
除了值列表中的基元值外,您还可以创建一个java.util.List
的对象数组。此列表可以支持为in
子句,例如select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2,
'Harrop'))
.当然,这需要您的数据库支持此语法。
处理存储过程调用的复杂类型
调用存储过程时,有时可以使用特定于
数据库。为了适应这些类型, Spring 提供了一个SqlReturnType
用于搬运
当它们从存储过程调用返回时,以及SqlTypeValue
当他们
作为参数传递给存储过程。
这SqlReturnType
interface 有一个方法(名为getTypeValue
),该
实现。此接口用作SqlOutParameter
.
以下示例显示了返回java.sql.Struct
用户的对象
declared type (声明的类型)ITEM_TYPE
:
-
Java
-
Kotlin
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
})
// ...
}
}
您可以使用SqlTypeValue
要将 Java 对象(例如TestItem
) 转换为
stored 过程。这SqlTypeValue
interface 有一个方法(名为createTypeValue
) 实现。活动连接传入,您
可以使用它来创建特定于数据库的对象,例如java.sql.Struct
实例
或java.sql.Array
实例。以下示例创建一个java.sql.Struct
实例:
-
Java
-
Kotlin
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)
}
}
您现在可以添加此SqlTypeValue
到Map
,其中包含execute
调用存储过程。
另一个用途SqlTypeValue
将值数组传递给 Oracle 存储的
程序。Oracle 有一个createOracleArray
method 开启OracleConnection
你可以
访问。您可以使用SqlTypeValue
创建数组并填充
it 替换为 Java 中的java.sql.Array
,如下例所示:
-
Java
-
Kotlin
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)
}
}