Spring SimpleJdbcCall default (optional) arguments

I am trying to invoke a stored procedure which has default (optional) arguments without passing them and it is not working. Essentially the same problem as described here.

My code:

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

And I get an exception:

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

Where PARTNAME is an optional parameter according to this. Also confirmed by the fact that I can run this procedure w/o the PARTNAME argument manually.


Ater giving up on this question and just passing all the parameters, including optional ones I ran into its inability to pass boolean arguments, because boolean is not an SQL data type, only PL/SQL.

So my current solution is that JDBC is not suited for running stored procedures and this is how I'm working around it:

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

Here is a different approach that I have taken. I added the ability for the user to set the number of parameters they will be providing on the call. These will be the first n number of positional parameters. Any remaining parameters available in the stored-proc, will have to be set via the database's default value handling. This allows new parameters to be added to the end of the list with default values, or to be null-able, without breaking code that does not know to provide a value.

I sub-classed SimpleJdbcCall and added the methods to set the "maxParamCount". I also used a bit a evil reflection to set my sub-classed version of 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);
    }
}

In my CallMetaDataContext sub-class, I store the maxInParamCount, and use it to trim the list of parameters known to exist in the stored-proc.

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

Use is basically the same except for seeting the max parameter count.

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

SMALL RANT: It's funny that Spring is well know for its IOC container. But, within its utility classes, I have to resort to reflection to provide an alternate implementation of a dependent class.


Was also struggling with the problem, and didn't want to deal with strings. There could be more interesting solution, if we get default values from meta data, which spring doesn't care about in default implementation, but I simply put nulls there. The solution came like the following:

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

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

链接地址: http://www.djcxy.com/p/65486.html

上一篇: 32位版本在Visual Studio 2012中生成

下一篇: Spring SimpleJdbcCall默认(可选)参数