【问题标题】:Call stored procedure specifying only parameters with a value调用存储过程,仅指定带值的参数
【发布时间】:2018-04-28 23:44:00
【问题描述】:

在 SQL Server 2016 的一个实例中,我有一个包含几十个参数的存储过程。例如:

CREATE PROCEDURE spName (
    @par1 INT = NULL,
    @par2 VARCHAR(10) = NULL,
        ....
        ....
    @par98 INT = NULL,
    @par99 INT = NULL,
) AS
BEGIN
    ....
    ....
END

我有一个用 C# 编写的客户端,它调用存储过程,只指定带有值的参数。例如:

SqlCommand cmd = new SqlCommand();

cmd.CommandText = "spName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = dbConn;

cmd.Parameters.Add(new SqlParameter("par1", "val1"));
cmd.Parameters.Add(new SqlParameter("par47", "val47"));

...

cmd.ExecuteNonQuery();

完美运行!因此,该过程被执行并且只有 2 个参数(par1 和 par47)有一个值。其他参数保持默认值(NULL)。

我会从使用 Microsoft JDBC 驱动程序 6.2 的 Java 客户端执行相同的操作。 我用List<Map<String, Object>> 指定参数,所以是一对parameterName-->parameterValue 的列表。以下方法构建PreparedStatement 对象:

private CallableStatement prepareStatement(String spName, Map<String, ?> parameters) throws SQLException {
    setupConnection();
    CallableStatement stmt = null;
    try {
        stmt = conn.prepareCall(getSpCallString(spName, parameters));
        if (parameters != null) {
            for (String parName : parameters.keySet())
                stmt.setObject(parName, parameters.get(parName));
        }
    } catch (SQLException e) {
        ApplicationLogging.severe("Cannot prepare callable statement", e);
        throw e;
    }
    return stmt;
}

getSpCallString() 方法生成{ call spName ?,?, ... , ? } 类型的字符串,其中? 的数量作为传递给过程的值的参数数量,因此不是所有99 个参数。如果我有 2 个参数,它会生成字符串 { call spName ?,? }。 例如,通过传递 par15=val15 和 par47=val47 会引发以下异常:

com.microsoft.sqlserver.jdbc.SQLServerException: The index 2 is out of range.

我可以在调用命令中解决这个问题! 在 C# 中,这很容易解决,因为参数只分配了它们的名称,所以参数的数量和顺序实际上可以是一个黑盒子。

我可以在 Java 中以某种方式做到这一点吗?

【问题讨论】:

  • 什么是 valorized 参数?我不熟悉这个词。
  • 我的意思是我给存储过程的参数(有一个值)。其他参数不传递,所以保持默认值(NULL)。
  • 你不能,除非不生成一个特定于 SQL Server 的存储过程调用,它显式地为命名参数赋值。
  • 我想你的意思是你想使用可选参数?您必须使用命名参数才能使其工作,并且您必须在过程定义中分配一个默认值(可以是 NULL)。但是你怎么能不知道一个存储过程中有多少个参数呢?如果您不知道参数,也许您应该考虑不调用该过程。或者至少花一点时间来看看这个过程,这样你就知道你在做什么。
  • 嗨,肖恩。我有一个存储过程,可以通过许多参数提取数据过滤。然后我有各种用户界面。它们都调用相同的存储过程,但每个都有不同的参数传递给存储过程(参数的子集)。处理客户端请求时,仅将来自调用视图的参数传递给存储过程,而不是全部。

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


【解决方案1】:

这是一个confirmed deficiency,在mssql-jdbc 驱动程序中对CallableStatement 的命名参数支持的当前实现。尽管 JDBC 4.2 规范的第 13.3.2 节声明 ...

命名参数只能用于指定没有默认值的值。

...我们似乎需要为每个可能的参数提供参数占位符,而且似乎没有办法为我们可能会简单省略的参数指定 DEFAULT

作为一种解决方法,我们可以使用这样的代码

public static ResultSet executeStoredProcedureQuery(
        Connection conn, String spName, Map<String, Object> paramItems) 
        throws SQLException {
    StringBuffer sqlBuf = new StringBuffer("EXEC ");
    sqlBuf.append(spName);
    int paramCount = 1;
    for (String paramName : paramItems.keySet()) {
        sqlBuf.append(
                (paramCount++ > 1 ? ", " : " ") + 
                (paramName.startsWith("@") ? "" : "@") + paramName + "=?");
    }
    String sql = sqlBuf.toString();
    myLogger.log(Level.INFO, sql);
    // e.g., EXEC dbo.BreakfastSP @helpings=?, @person=?, @food=?
    PreparedStatement ps = conn.prepareStatement(sql);
    paramCount = 1;
    for (String paramName : paramItems.keySet()) {
        ps.setObject(paramCount++, paramItems.get(paramName));
    }
    return ps.executeQuery();
}

我们可以这样称呼

// test data
Map<String, Object> paramItems = new HashMap<>();
paramItems.put("@person", "Gord");
paramItems.put("@food", "bacon");
paramItems.put("@helpings", 3);
//
ResultSet rs = executeStoredProcedureQuery(conn, "dbo.BreakfastSP", paramItems);

【讨论】:

  • 谢谢戈德。我还认为这是 MS Jdbs 驱动程序的“错误”或缺失功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
相关资源
最近更新 更多