【问题标题】:Call stored procedure with table-valued parameter from java从java调用带有表值参数的存储过程
【发布时间】:2013-04-16 21:55:19
【问题描述】:

在我的应用程序中,我想执行 SELECT * FROM tbl WHERE col IN (@list) 之类的查询,其中@list 可以有可变的值。我正在使用 MS SQL 服务器数据库。当我用谷歌搜索这个问题时,我发现了这个链接

http://www.sommarskog.se/arrays-in-sql-2008.html

此链接说使用表值参数。所以我使用 Microsoft SQL Server Management Studio 创建了用户定义的数据类型。

CREATE TYPE integer_list_tbltype AS TABLE (n int NOT NULL PRIMARY KEY)

然后我写了存储过程

CREATE PROCEDURE get_product_names @prodids integer_list_tbltype READONLY AS
   SELECT p.ProductID, p.ProductName
   FROM   Northwind.dbo.Products p
   WHERE  p.ProductID IN (SELECT n FROM @prodids)

然后我只使用管理工作室执行了这个过程

DECLARE @mylist integer_list_tbltype
INSERT @mylist(n) VALUES(9),(12),(27),(37)
EXEC get_product_names @mylist

它给了我正确的输出。但我想知道如何从 java 源代码调用这个存储过程。我知道如何调用具有常量参数的简单存储过程

CallableStatement proc_stmt = null;
proc_stmt = con.prepareCall("{call test(?)}");
proc_stmt.setString(1,someValue);

但是表值参数情况下如何调用存储过程呢?

【问题讨论】:

标签: sql sql-server stored-procedures jdbc parameters


【解决方案1】:

这在JDBC driver manual 中有记录。在你的情况下,你必须这样做:

try (SQLServerCallableStatement stmt =
    (SQLServerCallableStatement) con.prepareCall("{call test(?)}")) {

    SQLServerDataTable table = new SQLServerDataTable();   
    sourceDataTable.addColumnMetadata("n", java.sql.Types.INTEGER);   

    sourceDataTable.addRow(9);
    sourceDataTable.addRow(12);
    sourceDataTable.addRow(27);
    sourceDataTable.addRow(37);

    stmt.setStructured(1, "dbo.integer_list_tbltype", table);  
}

I've also recently documented this in an article.

【讨论】:

    【解决方案2】:

    看起来这是对 JDBC 的计划添加,但尚未实现:

    http://blogs.msdn.com/b/jdbcteam/archive/2012/04/03/how-would-you-use-table-valued-parameters-tvp.aspx

    将参数作为分隔字符串(“9,12,27,37”)传递,然后在 SQL Server 中创建一个名为“fnSplit”的表值函数或任何将在表中返回整数值的函数(只需搜索对于“sql server split function”,有数百万个)。

    【讨论】:

    • 谢谢@Kevin。除了使用 TVP 来解决问题之外,您还知道其他解决方案吗?
    • 您好,Pranay,不客气。检查我的原始答案以获取建议。将分隔字符串作为 varchar(max) 类型的单个参数传递。然后,在您的存储过程中,调用 SQL Server Split 用户定义函数(您需要创建它,只需搜索它,您会发现很多帮助)。 “Split”函数应该从一个分隔字符串返回一个表。祝你好运。
    • 只是为了添加到@Kevin 的有用答案,OP 的原始链接包括一个名为“iter_intlist_to_tbl”的函数示例,用于将 varchar 拆分为表变量 (sommarskog.se/arrays-in-sql-2005.html#iterative)!只是想我会提到它。
    • 2015年了,情况还是一样吗?是否仍然缺少这种支持?
    • @MárioMeyrelles - Microsoft 的 SQL Server JDBC 驱动程序现在支持表类型参数。详情请见this answer
    【解决方案3】:

    典型的答案(逗号分隔或 XML)都存在 SQL 注入问题。我需要一个允许我使用 PreparedStatement 的答案。所以我想出了这个:

    StringBuilder query = new StringBuilder();
    query.append(
      "DECLARE @mylist integer_list_tbltype;" +
      "INSERT @mylist(n) VALUES(?)");
    for (int i = 0; i < values.size() - 1; ++i) {
      query.append(",(?) ");
    }
    query.append("; EXEC get_product_names @mylist ");
    PreparedStatement preparedStmt = conn.prepareStatement(query.toString());
    for (int i = 0; i < values.size(); ++i) {
        preparedStmt.setObject(i + 1, itemLookupValues.get(i));
    }
    

    【讨论】:

      【解决方案4】:

      现在它已被添加到 JDBC Driver 6.0。 它还没有 CTP2。

      “此新驱动程序现在支持表值参数和 Azure Active Directory。除了这些新功能外,我们还为始终加密添加了额外功能。该驱动程序还支持国际化域名和参数化。”

      https://blogs.msdn.microsoft.com/jdbcteam/2016/04/04/get-the-new-microsoft-jdbc-driver-6-0-preview/

      下载链接:https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774

      这里是如何使用它的文档 https://msdn.microsoft.com/en-us/library/mt651781(v=sql.110).aspx

      【讨论】:

        【解决方案5】:

        搜索了一段时间后,我找到了这个问题的答案。特别是当您使用 IN 子句并且没有操作数是可变的时,您可以使用逗号分隔的值作为 IN 子句中的输入。

        下面是使用动态 SQL 的示例,该存储过程将检索提供的邮政编码中给定律师类型的所有律师。

        CREATE PROCEDURE [dbo].[GetLawyers] ( @ZIP CHAR(5), @LawyerTypeIDs VARCHAR(100) )
        AS
        
        DECLARE @SQL     VARCHAR(2000)
        
        SET @SQL = 'SELECT * FROM [dbo].[Lawyers]
                    WHERE [ZIP] = ' + @ZIP + ' AND
                          [LawyerTypeID] IN (' + @LawyerTypeIDs + ')'
        EXECUTE (@SQL)
        
        GO
        

        要执行存储过程,传递用户输入的邮政编码和选择的律师输入逗号分隔值:

        EXECUTE [dbo].[GetLawyers] '12345', '1,4'
        

        所以结论是你不需要使用 TVP。无论您使用哪种语言[Java、PHP],只需将参数作为逗号分隔的字符串传递给存储过程,它就会完美运行。

        所以在JAVA中你可以调用上面的存储过程:-

        proc_stmt = con.prepareCall("{call GetLawyers(?,?)}");
        proc_stmt.setString(1,"12345");
        proc_stmt.setString(2,"'1,4'");
        

        【讨论】:

        • 这种方法的问题是它可能会填满语句缓存。
        • @ngund 嗨,我试过这个解决方案,如果我理解得很好,我做了以下事情: m_cStatement.setString("'1,2,3'"));然后我得到以下异常:“操作数类型冲突:nvarchar 与 udt_UsersById 不兼容”。你能建议吗? (我在stackoverflow.com/questions/31161789/… 上发了一个问题,它被标记为重复。你能看一下吗?) 10X!
        • 这个问题是它可能会打开你的sql注入
        • 如何将@ZIP参数设置为"0; DROP TABLE [Lawyers]; --"
        猜你喜欢
        • 2019-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-07
        • 2013-06-24
        • 2018-04-28
        • 2018-05-29
        相关资源
        最近更新 更多