调用仅指定具有值的参数的存储过程
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);
在 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);