在 SQL Server JDBC 中使用表值参数
Posted
技术标签:
【中文标题】在 SQL Server JDBC 中使用表值参数【英文标题】:Using table-valued parameters with SQL Server JDBC 【发布时间】:2022-01-23 23:37:07 【问题描述】:谁能提供一些关于如何将表值参数 (TVP) 与 SQL Server JDBC 一起使用的指导?我正在使用微软提供的 6.0 版本的 SQL Server 驱动程序,我查看了official documentation 以及更有用的示例
How to pass Table-Valued parameters from java to sql server stored procedure?
这两个示例都显示了从Connection.prepareStatement
调用中获取SQLServerPreparedStatement
转换对象以调用setStructured
方法。这不会阻止标准连接池(例如 DBCP2)的使用吗?
我注意到另一个 Stack Overflow 评论中的一条评论说可以改用 stmt.setObject
方法:
作为转换 PreparedStatement 的替代方法,您可以将 SQLServerDataTable 实例传递给 PreparedStatement.setObject(int,Object) 方法。这适用于在 dbo 模式中定义的 TVP 类型。 – allenru 7 月 15 日 19:18
虽然我在尝试这个时遇到了错误,并且类型 is 在 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
感谢任何帮助或指导。谢谢!
【问题讨论】:
【参考方案1】:这是我最终使用的路线。 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;
【讨论】:
【参考方案2】:回复:使用PreparedStatement#setObject
我玩弄了一点,我也无法让它工作。也许这是 6.0 预览版中从最终版本中删除的一个功能。
re: 使用 SQLServerPreparedStatement
对象
我不明白为什么这会阻止使用 DBCP2(或任何其他连接池)本身,尽管它可能对 DBCP2 的 poolPreparedStatements
选项(默认为 false
)有影响)。尽管如此,如果一个人想创建一个“正确的”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);
但是,鉴于我们正在调用存储过程,使用 CallableStatement
对象(对于 setStructured
调用使用快速转换为 SQLServerCallableStatement
)而不是 PreparedStatement
对象可能是“好形式” .
【讨论】:
以上是关于在 SQL Server JDBC 中使用表值参数的主要内容,如果未能解决你的问题,请参考以下文章
通过在 SQL Server 2000 中传递变量参数来加入表值函数