2012-10-31 174 views
7

我想調用一個存儲過程,它有默認(可選)參數而不通過它們,它不工作。基本上與here所述相同的問題。春季SimpleJdbcCall默認(可選)參數

我的代碼:

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參數來確認。

回答

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

1

這裏是我已經採取了不同的方法。我增加了用戶設置他們將在通話中提供的參數數量的功能。這將是頭n個位置參數。 stored-proc中任何可用的參數都必須通過數據庫的默認值處理進行設置。這允許使用默認值將新參數添加到列表的末尾,或者可以無效,而不會破壞不知道提供值的代碼。

我分類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對於它的國際奧委會容器非常瞭解。但是,在其實用類中,我不得不採取反思來提供一個依賴類的替代實現。

0

今天想出了一個體面的解決方案,應付非空默認值,並且不使用水果反射技術。它通過從外部爲函數創建元數據上下文來檢索所有參數類型等等,然後從中手動構造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());