不同方法中存在参数和数据值的常见问题 由 Spring Framework 的 JDBC 支持提供。本节介绍如何解决这些问题。
为参数提供 SQL 类型信息
通常,Spring 会根据参数的类型来确定参数的 SQL 类型
传了进来。可以显式提供在设置时要使用的 SQL 类型
参数值。这有时对于正确设置值是必要的。NULL
您可以通过多种方式提供 SQL 类型信息:
-
的许多更新和查询方法都采用一个附加参数 数组的形式。此数组用于指示 通过使用类中的常量值来对应参数。提供 每个参数一个条目。
JdbcTemplate
int
java.sql.Types
-
您可以使用该类来包装需要此项的参数值 其他信息。为此,请为每个值创建一个新实例并传入 SQL 类型 以及构造函数中的参数值。您还可以提供可选的刻度 数值的参数。
SqlParameterValue
-
对于使用命名参数的方法,可以使用类或 .他们都有方法 用于注册任何命名参数值的 SQL 类型。
SqlParameterSource
BeanPropertySqlParameterSource
MapSqlParameterSource
处理 BLOB 和 CLOB 对象
您可以在数据库中存储图像、其他二进制数据和大块文本。这些
对于二进制数据,大型对象称为 BLOB(二进制大型 OBject),而 CLOB(字符
Large OBject) 用于字符数据。在 Spring 中,您可以使用
直接以及使用 RDBMS 提供的更高抽象时也是如此
对象和类。所有这些方法都使用
用于实际管理 LOB(大型 OBject)数据的接口。 通过以下方法提供对类的访问,
用于创建要插入的新 LOB 对象。JdbcTemplate
SimpleJdbc
LobHandler
LobHandler
LobCreator
getLobCreator
LobCreator
并为 LOB 输入和输出提供以下支持:LobHandler
-
BLOB
-
byte[]
:和getBlobAsBytes
setBlobAsBytes
-
InputStream
:和getBlobAsBinaryStream
setBlobAsBinaryStream
-
-
CLOB的
-
String
:和getClobAsString
setClobAsString
-
InputStream
:和getClobAsAsciiStream
setClobAsAsciiStream
-
Reader
:和getClobAsCharacterStream
setClobAsCharacterStream
-
下一个示例演示如何创建和插入 BLOB。稍后我们将展示如何阅读 它从数据库中返回。
此示例使用 .它实现一个方法,.此方法提供了一个我们用来设置
SQL insert 语句中的 LOB 列。JdbcTemplate
AbstractLobCreatingPreparedStatementCallback
setValues
LobCreator
在这个例子中,我们假设有一个变量,它已经是
设置为 的实例。通常通过以下方式设置此值
依赖注入。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 | 传入 that(在本例中)是一个普通的 .lobHandler DefaultLobHandler |
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(在本例中)是一个普通的 .lobHandler DefaultLobHandler |
2 | 使用该方法传入 CLOB 的内容。setClobAsCharacterStream |
3 | 使用该方法传入 BLOB 的内容。setBlobAsBinaryStream |
如果在返回的 上调用 、 或 方法,则可以选择指定负值
对于参数。如果指定的内容长度为负数,则使用 set-stream 方法的 JDBC 4.0 变体,而不使用
length 参数。否则,它会将指定的长度传递给驱动程序。 请参阅用于验证其支持流式处理的 JDBC 驱动程序的文档 一个 LOB,但不提供内容长度。 |
现在是时候从数据库中读取 LOB 数据了。同样,将 a 与相同的实例变量和对 .
以下示例演示如何执行此操作: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 | 使用该方法检索 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)
NamedParameterJdbcTemplate
java.util.List
Iterable
传入许多值时要小心。JDBC 标准不保证
表达式列表可以使用 100 个以上的值。各种数据库超过
这个数字,但它们通常对允许的值数有硬性限制。
例如,Oracle 的限制是 1000。IN |
除了值列表中的基元值之外,还可以创建对象数组。此列表可以支持为子句定义的多个表达式,例如 。当然,这需要您的数据库支持此语法。java.util.List
in
select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2,
'Harrop'))
处理存储过程调用的复杂类型
调用存储过程时,有时可以使用特定于
数据库。为了适应这些类型,Spring 提供了一个用于处理
当它们从存储过程调用返回时,以及当它们
作为参数传入存储过程。SqlReturnType
SqlTypeValue
接口具有一个方法(名为 ),该方法必须是
实现。此接口用作 .
以下示例演示如何返回用户的 Oracle 对象的值
声明类型:SqlReturnType
getTypeValue
SqlOutParameter
STRUCT
ITEM_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 对象的值(例如 )传递给
存储过程。该接口具有必须实现的单个方法(名为 )。活动连接传入,您
可以使用它来创建特定于数据库的对象,例如实例
或实例。以下示例创建一个实例:SqlTypeValue
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
的另一个用途是将值数组传递给存储的 Oracle
程序。Oracle 有自己的内部类,在这种情况下必须使用,并且
您可以使用 创建 Oracle 实例并填充
它带有来自 Java 的值,如以下示例所示:SqlTypeValue
ARRAY
SqlTypeValue
ARRAY
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)
}
}
}
}