调用仅指定具有值的参数的存储过程

Call stored procedure specifying only parameters with a value

在 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 中以某种方式做到这一点吗?

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

Named parameters can be used to specify only the values that have no default value.

...我们似乎需要为每个可能的参数提供一个参数占位符,而且似乎没有办法为我们可能会忽略的参数指定 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);