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

Arm*_*ile 6 sql-server parameters stored-procedures jdbc mssql-jdbc

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

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

我有一个用 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();
Run Code Online (Sandbox Code Playgroud)

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

我会从使用 Microsoft JDBC 驱动程序 6.2 的 Java 客户端执行相同的操作。我用 指定参数List<Map<String, Object>>,所以有一个参数名称-->参数值的列表。以下方法构建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;
}
Run Code Online (Sandbox Code Playgroud)

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

com.microsoft.sqlserver.jdbc.SQLServerException: The index 2 is out of range.
Run Code Online (Sandbox Code Playgroud)

我可以解决这个问题,将调用命令中的参数数量?与存储过程的参数数量相同,但是......我不知道每个存储过程的参数数量(及其位置)!在 C# 中,这个问题可以简单解决,因为参数仅分配其名称,因此参数的数量和顺序实际上可以是一个黑盒子。

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

Gor*_*son 3

这是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();
}
Run Code Online (Sandbox Code Playgroud)

我们可以这样称呼

// 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);
Run Code Online (Sandbox Code Playgroud)