在 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 中传递变量参数来加入表值函数

SQL Server存储过程中使用表值作为输入参数示例

如何在 Python 中使用表值参数访问 SQL Server 2008 存储过程

在 SQL Server 的视图中使用表值函数

SQL Server 中函数的理解总结

SQL Server:表值函数与存储过程