【问题标题】:Using table-valued parameters with SQL Server JDBC在 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

感谢任何帮助或指导。谢谢!

【问题讨论】:

    标签: java sql-server jdbc table-valued-parameters


    【解决方案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 对象可能是“好形式” .

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多