2012-10-31 3 views
7

Ich versuche, eine gespeicherte Prozedur, die Standard (optional) Argumente hat, ohne sie zu übergeben, und es funktioniert nicht. Im Wesentlichen das gleiche Problem wie beschrieben here.Spring SimpleJdbcCall default (optional) Argumente

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

Und ich bekomme eine Ausnahme:

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

Wo Partname ist ein optionaler Parameter nach this. Auch bestätigt durch die Tatsache, dass ich diese Prozedur ohne das Argument PARTNAME manuell ausführen kann.

Antwort

3

Nachdem ich auf diese Frage verzichtet habe und nur alle Parameter übergeben habe, einschließlich der optionalen Parameter, habe ich festgestellt, dass boolesche Argumente nicht übergeben werden können, weil boolean kein SQL-Datentyp ist, sondern nur PL/SQL.

So meine aktuelle Lösung ist, dass JDBC nicht für die Ausführung von gespeicherten Prozeduren geeignet ist, und das ist, wie ich um ihn gerade arbeite:

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

wurde auch mit dem Problem zu kämpfen, und wollte nicht tun mit Schnüren. Es könnte interessantere Lösung geben, wenn wir Standardwerte aus Metadaten erhalten, die bei der Standardimplementierung nicht wichtig sind, aber ich gebe einfach Nullen dort ein. Die Lösung kam wie folgt aus:

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

Und überschriebenen 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

Hier ist ein anderer Ansatz, die ich genommen habe. Ich habe die Fähigkeit für den Benutzer hinzugefügt, die Anzahl der Parameter einzustellen, die sie bei dem Anruf bereitstellen werden. Dies ist die erste n Anzahl von Positionsparametern. Alle verbleibenden Parameter, die im Stored-Proc verfügbar sind, müssen über die Standardwertbehandlung der Datenbank festgelegt werden. Dadurch können neue Parameter am Ende der Liste mit Standardwerten hinzugefügt werden, oder sie können null-fähig sein, ohne Code zu unterbrechen, der keinen Wert angibt.

Ich unterklassifizierte SimpleJdbcCall und fügte die Methoden hinzu, um "maxParamCount" festzulegen. Ich habe auch ein böses Spiegelbild verwendet, um meine untergeordnete Version von CallMetaDataContext festzulegen.

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 meinem CallMetaDataContext Unterklasse, speichere ich die maxInParamCount, und es verwenden, um die Liste der Parameter bekannt zu trimmen in der gespeicherten proc existieren.

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

Die Verwendung ist grundsätzlich gleich, außer dass die maximale Anzahl der Parameter ermittelt wird.

KLEINER RANT: Es ist lustig, dass Frühling für seinen IOC Behälter gut bekannt ist. Aber innerhalb seiner Gebrauchsklassen muss ich auf Überlegungen zurückgreifen, um eine alternative Implementierung einer abhängigen Klasse zu ermöglichen.

0

Hat heute eine vernünftige Lösung gefunden, die mit Nicht-Null-Standardwerten zurechtkommt und keine fruchtigen Reflexionstechniken verwendet.Er erstellt den Metadatenkontext für die Funktion extern, um alle Parametertypen usw. abzurufen, und erstellt anschließend manuell den SimpleJdbcCall.

zunächst eine CallMetaDataContext für die Funktion erstellen:

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

Als Nächstes erstellen Sie den SimpleJdbcCall, aber es zwingt, nicht seinen eigenen Metadaten-Lookup:

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()); 
Verwandte Themen