14

我正在尝试调用具有默认(可选)参数而不传递它们的存储过程,并且它不起作用。基本上与此处描述的问题相同。

我的代码:

  SqlParameterSource in = new MapSqlParameterSource()
        .addValue("ownname", "USER")
        .addValue("tabname", cachedTableName)
        .addValue("estimate_percent", 20)
        .addValue("method_opt", "FOR ALL COLUMNS SIZE 1")
        .addValue("degree", 0)
        .addValue("granularity", "AUTO")
        .addValue("cascade", Boolean.TRUE)
        .addValue("no_invalidate", Boolean.FALSE)
        .addValue("force", Boolean.FALSE);

我得到一个例外:

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Required input parameter 'PARTNAME' is missing
    at org.springframework.jdbc.core.CallableStatementCreatorFactory$CallableStatementCreatorImpl.createCallableStatement(CallableStatementCreatorFactory.java:209)

其中 PARTNAME 是根据this的可选参数。也证实了我可以手动运行此过程而无需 PARTNAME 参数。

4

6 回答 6

6

Ater 放弃了这个问题,只传递了所有参数,包括可选参数,我遇到了它无法传递布尔参数,因为布尔不是 SQL 数据类型,只有 PL/SQL。

所以我目前的解决方案是 JDBC 不适合运行存储过程,这就是我正在解决的问题:

  jdbcTemplate.execute(
        new CallableStatementCreator() {
           public CallableStatement createCallableStatement(Connection con) throws SQLException{
              CallableStatement cs = con.prepareCall("{call sys.dbms_stats.gather_table_stats(ownname=>user, tabname=>'" + cachedMetadataTableName + "', estimate_percent=>20, method_opt=>'FOR ALL COLUMNS SIZE 1', degree=>0, granularity=>'AUTO', cascade=>TRUE, no_invalidate=>FALSE, force=>FALSE) }");
              return cs;
           }
        },
        new CallableStatementCallback() {
           public Object doInCallableStatement(CallableStatement cs) throws SQLException{
              cs.execute();
              return null; // Whatever is returned here is returned from the jdbcTemplate.execute method
           }
        }
  );
于 2012-10-31T19:23:38.570 回答
3

今天想出了一个不错的解决方案,可以处理非空默认值,并且不使用果味反射技术。它的工作原理是在外部为函数创建元数据上下文以检索所有参数类型等,然后从中手动构造 SimpleJdbcCall。

首先,为函数创建一个 CallMetaDataContext:

    CallMetaDataContext context = new CallMetaDataContext();
    context.setFunction(true);
    context.setSchemaName(schemaName);
    context.setProcedureName(functionName);
    context.initializeMetaData(jdbcTemplate.getDataSource());
    context.processParameters(Collections.emptyList());

接下来,创建 SimpleJdbcCall,但强制它不进行自己的元数据查找:

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate);
// This forces the call object to skip metadata lookup, which is the part that forces all parameters
simpleJdbcCall.setAccessCallParameterMetaData(false);

// Now go back to our previously created context and pull the parameters we need from it
simpleJdbcCall.addDeclaredParameter(context.getCallParameters().get(0));
for (int i = 0; i < params.length; ++i) {
    simpleJdbcCall.addDeclaredParameter(context.getCallParameters().get(i));
}
// Call the function and retrieve the result
Map<String, Object> resultsMap = simpleJdbcCall
                        .withSchemaName(schemaName)
                        .withFunctionName(functionName)
                        .execute(params);
Object returnValue = resultsMap.get(context.getScalarOutParameterName());
于 2017-05-18T00:24:04.983 回答
2

我使用 SimpleJdbcCall 和 Spring 5.2.1、Java 8、Oracle 12 为我的案例找到了解决方案。

你需要:

  1. 使用.withoutProcedureColumnMetaDataAccess()
  2. 使用.withNamedBinding()
  3. 声明参数,你知道在.declareParameters()调用中。仅使用在此方法中声明的参数调用过程。默认参数,你不想设置,这里就不写了。

示例调用如下

final String dataParamName = "P_DATA";
final String ageParamName = "P_AGE";
final String genderParamName = "P_GENDER";

final String acceptedParamName = "P_ACCEPTED";


SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(getJdbcTemplate())
        .withCatalogName("PKG_USER")
        .withProcedureName("USER_CHECK")
        .withoutProcedureColumnMetaDataAccess()
        .withNamedBinding()
        .declareParameters(
                new SqlParameter(dataParamName, OracleTypes.VARCHAR),
                new SqlParameter(ageParamName, OracleTypes.NUMBER),
                new SqlParameter(genderParamName, OracleTypes.VARCHAR),
                new SqlOutParameter(acceptedParamName, OracleTypes.NUMBER)
        );

SqlParameterSource parameterSource = new MapSqlParameterSource()
        .addValue(dataParamName, data)
        .addValue(ageParamName, age)
        .addValue(genderParamName, gender);

Map<String, Object> out = simpleJdbcCall.execute(parameterSource);
于 2019-12-07T14:19:26.747 回答
1

这是我采取的另一种方法。我添加了让用户设置他们将在通话中提供的参数数量的功能。这些将是前 n 个位置参数。存储过程中可用的任何剩余参数都必须通过数据库的默认值处理进行设置。这允许使用默认值将新参数添加到列表的末尾,或者可以为空,而不会破坏不知道提供值的代码。

我对 SimpleJdbcCall 进行了子类化,并添加了设置“maxParamCount”的方法。我还使用了一点邪恶的反射来设置我的 CallMetaDataContext 的子类版本。

public class MySimpleJdbcCall extends SimpleJdbcCall
{
    private final MyCallMetaDataContext callMetaDataContext = new MyCallMetaDataContext();

    public MySimpleJdbcCall(DataSource dataSource)
    {
        this(new JdbcTemplate(dataSource));
    }

    public MySimpleJdbcCall(JdbcTemplate jdbcTemplate)
    {
        super(jdbcTemplate);

        try
        {
            // Access private field
            Field callMetaDataContextField = AbstractJdbcCall.class.getDeclaredField("callMetaDataContext");
            callMetaDataContextField.setAccessible(true);

            // Make it non-final
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(callMetaDataContextField, callMetaDataContextField.getModifiers() & ~Modifier.FINAL);

            // Set field
            callMetaDataContextField.set(this, this.callMetaDataContext);
        }
        catch (NoSuchFieldException | IllegalAccessException ex)
        {
            throw new RuntimeException("Exception thrown overriding AbstractJdbcCall.callMetaDataContext field", ex);
        }
    }

    public MySimpleJdbcCall withMaxParamCount(int maxInParamCount)
    {
        setMaxParamCount(maxInParamCount);
        return this;
    }

    public int getMaxParamCount()
    {
        return this.callMetaDataContext.getMaxParamCount();
    }

    public void setMaxParamCount(int maxInParamCount)
    {
        this.callMetaDataContext.setMaxParamCount(maxInParamCount);
    }
}

在我的 CallMetaDataContext 子类中,我存储了 maxInParamCount,并使用它来修剪已知存在于存储过程中的参数列表。

public class MyCallMetaDataContext extends CallMetaDataContext
{
    private int maxParamCount = Integer.MAX_VALUE;

    public int getMaxParamCount()
    {
        return maxParamCount;
    }

    public void setMaxParamCount(int maxInParamCount)
    {
        this.maxParamCount = maxInParamCount;
    }

    @Override
    protected List<SqlParameter> reconcileParameters(List<SqlParameter> parameters)
    {
        List<SqlParameter> limittedParams = new ArrayList<>();
        int paramCount = 0;
        for(SqlParameter param : super.reconcileParameters(parameters))
        {
            if (!param.isResultsParameter())
            {
                paramCount++;
                if (paramCount > this.maxParamCount)
                    continue;
            }

            limittedParams.add(param);
        }
        return limittedParams;
    }
}

除了查看最大参数计数外,使用基本相同。

SimpleJdbcCall call = new MySimpleJdbcCall(jdbcTemplate)
        .withMaxParamCount(3)
        .withProcedureName("MayProc");

SMALL RANT:有趣的是,Spring 以其 IOC 容器而闻名。但是,在它的实用程序类中,我不得不求助于反射来提供依赖类的替代实现。

于 2016-03-31T14:40:31.567 回答
0

也在努力解决这个问题,并且不想处理字符串。可能有更有趣的解决方案,如果我们从元数据中获取默认值,spring 在默认实现中并不关心,但我只是将空值放在那里。解决方案如下:

重写的 simpleJdbcCall

 private class JdbcCallWithDefaultArgs extends SimpleJdbcCall {

    CallableStatementCreatorFactory callableStatementFactory;

    public JdbcCallWithDefaultArgs(JdbcTemplate jdbcTemplate) {
        super(jdbcTemplate);
    }

    @Override
    protected CallableStatementCreatorFactory getCallableStatementFactory() {
        return callableStatementFactory;
    }

    @Override
    protected void onCompileInternal() {
        callableStatementFactory =
                new CallableStatementCreatorWithDefaultArgsFactory(getCallString(), this.getCallParameters());
        callableStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());

    }


    @Override
    public Map<String, Object> execute(SqlParameterSource parameterSource) {
        ((CallableStatementCreatorWithDefaultArgsFactory)callableStatementFactory).cleanupParameters(parameterSource);
        return super.doExecute(parameterSource);
    }
}

并覆盖 CallableStatementCreatorFactory

public class CallableStatementCreatorWithDefaultArgsFactory extends CallableStatementCreatorFactory {

private final Logger logger = LoggerFactory.getLogger(getClass());
private final List<SqlParameter> declaredParameters;

public CallableStatementCreatorWithDefaultArgsFactory(String callString, List<SqlParameter> declaredParameters) {
    super(callString, declaredParameters);
    this.declaredParameters = declaredParameters;
}

protected void cleanupParameters(SqlParameterSource sqlParameterSource) {
    MapSqlParameterSource mapSqlParameterSource = (MapSqlParameterSource) sqlParameterSource;
    Iterator<SqlParameter> declaredParameterIterator = declaredParameters.iterator();
    Set<String> parameterNameSet = mapSqlParameterSource.getValues().keySet();
    while (declaredParameterIterator.hasNext()) {
        SqlParameter parameter = declaredParameterIterator.next();
        if (!(parameter instanceof SqlOutParameter) &&
                (!mapContainsParameterIgnoreCase(parameter.getName(), parameterNameSet))) {
            logger.warn("Missing value parameter "+parameter.getName() + " will be replaced by null!");
            mapSqlParameterSource.addValue(parameter.getName(), null);
        }
    }
}

private boolean mapContainsParameterIgnoreCase(String parameterName, Set<String> parameterNameSet) {
    String lowerParameterName = parameterName.toLowerCase();
    for (String parameter : parameterNameSet) {
        if (parameter.toLowerCase().equals(lowerParameterName)) {
            return true;
        }
    }
    return false;
}

@Override
public void addParameter(SqlParameter param) {
    this.declaredParameters.add(param);
}

于 2015-12-24T08:21:56.730 回答
0

我使用这个 util 方法:

public <T> void setOptionalParameter(MapSqlParameterSource parameters, String name, T value) {
    if (value == null)
        parameters.addValue(name, value, Types.NULL);
    else
        parameters.addValue(name, value);
}
于 2019-11-01T13:06:08.143 回答