Spring SimpleJdbcCall默认(可选)参数

时间:2012-10-31 16:01:06

标签: java spring stored-procedures jdbc

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

我的代码:

  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)

根据this,PARTNAME是可选参数。我还可以通过手动运行此程序来确认PARTNAME参数。

6 个答案:

答案 0 :(得分:3)

Ater放弃了这个问题,只是传递了所有参数,包括我遇到的无法传递布尔参数的可选参数,因为boolean不是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
           }
        }
  );

答案 1 :(得分: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容器。但是,在其实用程序类中,我不得不求助于反射来提供依赖类的替代实现。

答案 2 :(得分:1)

今天提出了一个很好的解决方案,它可以应对非null默认值,并且不使用水果反射技术。它的工作原理是从外部创建函数的元数据上下文以检索所有参数类型等,然后从中手动构造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());

答案 3 :(得分:0)

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

重写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);
    }
}

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);
}

答案 4 :(得分: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);
}

答案 5 :(得分:0)

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

您需要:

  1. 使用 .withoutProcedureColumnMetaDataAccess()
  2. 使用 .withNamedBinding()
  3. 您可以在 .declareParameters()调用中了解
  4. 声明参数。仅使用在此方法中声明的参数调用过程。默认参数,您不想设置,请在此处编写。

示例呼叫如下

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);
相关问题