2012-10-31 18 views
7

Sto provando a richiamare una stored procedure che ha argomenti predefiniti (facoltativi) senza passarli e non funziona. Sostanzialmente lo stesso problema descritto in here.Spring SimpleJdbcCall default (opzionale) argomenti

Il mio codice:

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

e ottengo un'eccezione:

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

Dove nomeparte è un parametro opzionale in base al this. Confermato anche dal fatto che posso eseguire questa procedura senza l'argomento PARTNAME manualmente.

risposta

3

Dopo aver rinunciato a questa domanda e passando solo tutti i parametri, compresi quelli facoltativi, mi sono imbattuto nella sua impossibilità di passare argomenti booleani, perché booleano non è un tipo di dati SQL, solo PL/SQL.

Quindi la mia soluzione attuale è che JDBC non è adatto per l'esecuzione di stored procedure e questo è il modo in cui sto lavorando intorno ad esso:

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

era anche alle prese con il problema, e non ha voluto affrontare con le stringhe. Potrebbe esserci una soluzione più interessante, se otteniamo valori predefiniti dai metadati, di cui molla non interessa l'implementazione predefinita, ma semplicemente li inserisco qui. La soluzione è venuto come il seguente:

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

E CallableStatementCreatorFactory sovresposta

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

Ecco un approccio diverso che ho preso. Ho aggiunto la possibilità per l'utente di impostare il numero di parametri che forniranno sulla chiamata. Questi saranno il primo n numero di parametri posizionali. Eventuali parametri rimanenti disponibili nel stored-proc, dovranno essere impostati tramite la gestione del valore predefinito del database. Ciò consente di aggiungere nuovi parametri alla fine dell'elenco con valori predefiniti o di essere nulli, senza che il codice di errore non sia in grado di fornire un valore.

I sottoclassed SimpleJdbcCall e ha aggiunto i metodi per impostare "maxParamCount". Ho anche usato un po 'un riflesso negativo per impostare la mia versione sottoclasse di 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); 
    } 
} 

Nel mio CallMetaDataContext sottoclasse, ho memorizzare il maxInParamCount, e usarlo per tagliare l'elenco dei parametri noti di esistere nel-proc memorizzato.

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

L'utilizzo è praticamente lo stesso tranne per il conteggio del numero massimo di parametri.

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

PICCOLO RANT: È buffo che la primavera sia ben nota per il suo container IOC. Ma, all'interno delle sue classi di utilità, devo ricorrere alla riflessione per fornire un'implementazione alternativa di una classe dipendente.

0

Oggi ho trovato una soluzione decente, che gestisce le impostazioni predefinite non nulle e non utilizza tecniche di riflessione fruttata.Funziona creando il contesto dei metadati per la funzione esternamente per recuperare tutti i tipi di parametri e così via, quindi costruendo manualmente SimpleJdbcCall.

In primo luogo, creare un CallMetaDataContext per la funzione:

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

Successivamente, creare il SimpleJdbcCall, ma costringerlo a non fare il proprio metadati di ricerca:

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()); 
Problemi correlati