在 SQL 服务器 JDBC 中使用 table 值参数
Using table-valued parameters with SQL Server JDBC
任何人都可以提供一些关于如何将 Table 值参数 (TVP) 与 SQL 服务器 JDBC 一起使用的指导吗?我使用的是 Microsoft 提供的 SQL 服务器驱动程序的 6.0 版本,我已经查看了 official documentation 以及更有帮助的示例
这两个示例都显示从 Connection.prepareStatement
调用中获取 SQLServerPreparedStatement
转换对象以调用 setStructured
方法。这不会阻止使用标准连接池(例如 DBCP2)吗?
我注意到其他 Stack Overflow 评论中的一条评论说可以改用 stmt.setObject
方法:
As an alternative to casting the PreparedStatement, you can pass the SQLServerDataTable instance to the PreparedStatement.setObject(int,Object) method. This worked for a TVP type that was defined in the dbo schema. – allenru Jul 15 at 19:18
虽然我在尝试这个时遇到错误,并且 dbo 架构中的类型 是 ...
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: The Table-Valued Parameter must have a valid type name.
这是我的代码:
// JAVA
private static void tableParameters(DataSource ds) throws SQLException {
final String sql = "EXEC dbo.GetAccountsFromTable @accountIds=?";
final List<Integer> accountIds = generateIntegers(50, 1_000_000);
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql))
{
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("item", java.sql.Types.INTEGER);
for (Integer aid : accountIds)
accounts.addRow(aid.toString());
stmt.setObject(1, accounts);
try (ResultSet rs = stmt.executeQuery())
{
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
}
}
-- TSQL
create type dbo.IntegerTable AS TABLE (item INT);
CREATE PROCEDURE dbo.GetAccountsFromTable(@accountIds dbo.IntegerTable READONLY)
AS
BEGIN
IF OBJECT_ID('tempdb..#AccountIds') IS NOT NULL
DROP TABLE #AccountIds
CREATE TABLE #AccountIds (id INTEGER)
INSERT INTO #AccountIds
SELECT * FROM @accountIds
SELECT * FROM #AccountIds
END
感谢任何帮助或指导。谢谢!
回复:使用 PreparedStatement#setObject
我把它弄糊涂了一点,但我也无法让它工作。也许这是 6.0 预览版中从最终版本中删除的功能。
回复:使用 SQLServerPreparedStatement
对象
我不明白为什么这会阻止使用 DBCP2(或任何其他连接池)本身,尽管它可能对 DBCP2 的 poolPreparedStatements
选项有影响(默认情况下是 false
)。尽管如此,如果有人想创建一个 "proper" PreparedStatement
对象并且仍然需要使用 setStructured
那么我刚才尝试时它工作正常:
String sql = "EXEC dbo.GetAccountsFromTable ?";
try (
Connection conn = DriverManager.getConnection(connectionUrl);
PreparedStatement stmt = conn.prepareStatement(sql)) {
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("item", java.sql.Types.INTEGER);
accounts.addRow(123);
accounts.addRow(234);
((SQLServerPreparedStatement) stmt).setStructured(1, "dbo.IntegerTable", accounts);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
但是,如果我们正在调用存储过程,则可能 "good form" 使用 CallableStatement
对象(对于 setStructured
快速转换为 SQLServerCallableStatement
调用)而不是 PreparedStatement
对象。
这是我最终使用的路线。 DBCP2 有一个 DelegatingStatement.getInnermostDelegate
方法来获取 Microsoft 驱动程序创建的 PreparedStatement 对象。我不太喜欢跳过这些障碍——即使添加了错误检查,代码看起来也很脆弱。 TVP 是特定于 SqlServer 的东西,因此使用所需的假设和强制转换可能还不错。
private static int testTvp(DataSource ds, List<Integer> accountIds) throws SQLException {
final String sql = "EXEC dgTest.GetAccountsFromTvp @accountIds=?";
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
DelegatingPreparedStatement dstmt = (DelegatingPreparedStatement)stmt;
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement)dstmt.getInnermostDelegate();
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("token", java.sql.Types.INTEGER);
for (Integer aid : accountIds)
accounts.addRow(aid);
pstmt.setStructured(1, "dgTest.IntegerTable", accounts);
//// NOTE: The below works for JTDS driver, official MS driver said no result sets were returned
//try (ResultSet rs = pstmt.executeQuery()) {
// return sumInts(rs);
//}
if (pstmt.execute()) {
try (ResultSet rs = pstmt.getResultSet()) {
return sumInts(rs);
}
}
return -1;
}
}
任何人都可以提供一些关于如何将 Table 值参数 (TVP) 与 SQL 服务器 JDBC 一起使用的指导吗?我使用的是 Microsoft 提供的 SQL 服务器驱动程序的 6.0 版本,我已经查看了 official documentation 以及更有帮助的示例
这两个示例都显示从 Connection.prepareStatement
调用中获取 SQLServerPreparedStatement
转换对象以调用 setStructured
方法。这不会阻止使用标准连接池(例如 DBCP2)吗?
我注意到其他 Stack Overflow 评论中的一条评论说可以改用 stmt.setObject
方法:
As an alternative to casting the PreparedStatement, you can pass the SQLServerDataTable instance to the PreparedStatement.setObject(int,Object) method. This worked for a TVP type that was defined in the dbo schema. – allenru Jul 15 at 19:18
虽然我在尝试这个时遇到错误,并且 dbo 架构中的类型 是 ...
Exception in thread "main" com.microsoft.sqlserver.jdbc.SQLServerException: The Table-Valued Parameter must have a valid type name.
这是我的代码:
// JAVA
private static void tableParameters(DataSource ds) throws SQLException {
final String sql = "EXEC dbo.GetAccountsFromTable @accountIds=?";
final List<Integer> accountIds = generateIntegers(50, 1_000_000);
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql))
{
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("item", java.sql.Types.INTEGER);
for (Integer aid : accountIds)
accounts.addRow(aid.toString());
stmt.setObject(1, accounts);
try (ResultSet rs = stmt.executeQuery())
{
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
}
}
-- TSQL
create type dbo.IntegerTable AS TABLE (item INT);
CREATE PROCEDURE dbo.GetAccountsFromTable(@accountIds dbo.IntegerTable READONLY)
AS
BEGIN
IF OBJECT_ID('tempdb..#AccountIds') IS NOT NULL
DROP TABLE #AccountIds
CREATE TABLE #AccountIds (id INTEGER)
INSERT INTO #AccountIds
SELECT * FROM @accountIds
SELECT * FROM #AccountIds
END
感谢任何帮助或指导。谢谢!
回复:使用 PreparedStatement#setObject
我把它弄糊涂了一点,但我也无法让它工作。也许这是 6.0 预览版中从最终版本中删除的功能。
回复:使用 SQLServerPreparedStatement
对象
我不明白为什么这会阻止使用 DBCP2(或任何其他连接池)本身,尽管它可能对 DBCP2 的 poolPreparedStatements
选项有影响(默认情况下是 false
)。尽管如此,如果有人想创建一个 "proper" PreparedStatement
对象并且仍然需要使用 setStructured
那么我刚才尝试时它工作正常:
String sql = "EXEC dbo.GetAccountsFromTable ?";
try (
Connection conn = DriverManager.getConnection(connectionUrl);
PreparedStatement stmt = conn.prepareStatement(sql)) {
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("item", java.sql.Types.INTEGER);
accounts.addRow(123);
accounts.addRow(234);
((SQLServerPreparedStatement) stmt).setStructured(1, "dbo.IntegerTable", accounts);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
但是,如果我们正在调用存储过程,则可能 "good form" 使用 CallableStatement
对象(对于 setStructured
快速转换为 SQLServerCallableStatement
调用)而不是 PreparedStatement
对象。
这是我最终使用的路线。 DBCP2 有一个 DelegatingStatement.getInnermostDelegate
方法来获取 Microsoft 驱动程序创建的 PreparedStatement 对象。我不太喜欢跳过这些障碍——即使添加了错误检查,代码看起来也很脆弱。 TVP 是特定于 SqlServer 的东西,因此使用所需的假设和强制转换可能还不错。
private static int testTvp(DataSource ds, List<Integer> accountIds) throws SQLException {
final String sql = "EXEC dgTest.GetAccountsFromTvp @accountIds=?";
try (Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
DelegatingPreparedStatement dstmt = (DelegatingPreparedStatement)stmt;
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement)dstmt.getInnermostDelegate();
SQLServerDataTable accounts = new SQLServerDataTable();
accounts.addColumnMetadata("token", java.sql.Types.INTEGER);
for (Integer aid : accountIds)
accounts.addRow(aid);
pstmt.setStructured(1, "dgTest.IntegerTable", accounts);
//// NOTE: The below works for JTDS driver, official MS driver said no result sets were returned
//try (ResultSet rs = pstmt.executeQuery()) {
// return sumInts(rs);
//}
if (pstmt.execute()) {
try (ResultSet rs = pstmt.getResultSet()) {
return sumInts(rs);
}
}
return -1;
}
}