Spring 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)
где PARTNAME является необязательным параметром в соответствии с этой. Также подтверждается тем, что я могу запустить эту процедуру без аргумента PARTNAME вручную.
4 ответов
после отказа от этого вопроса и просто передачи всех параметров, включая необязательные, я столкнулся с невозможностью передать логические аргументы, потому что 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
}
}
);
вот другой подход, который я принял. Я добавил возможность для пользователя установить количество параметров, которые они будут предоставлять по вызову. Это будет первое n число позиционных параметров. Все остальные параметры, доступные в stored-proc, должны быть установлены с помощью обработки значений по умолчанию базы данных. Это позволяет добавлять новые параметры в конец списка со значениями по умолчанию или быть null-able, не нарушая код, который не знает, чтобы предоставить значение.
Я подкласс 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 и использую его для обрезки списка параметров, известных для существования в сохраненном 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;
}
}
использование в основном то же самое, за исключением seeting максимальное количество параметров.
SimpleJdbcCall call = new MySimpleJdbcCall(jdbcTemplate)
.withMaxParamCount(3)
.withProcedureName("MayProc");
небольшой RANT: забавно, что весна хорошо известна своим контейнером МОК. Но в рамках его классов полезности я должен прибегнуть к рефлексии, чтобы обеспечить альтернативную реализацию зависимого класса.
придумал достойное решение для этого сегодня, которое справляется с ненулевыми значениями по умолчанию и не использует методы фруктового отражения. Он работает путем создания контекста метаданных для функции извне, чтобы получить все типы параметров и т. д., а затем вручную построить 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());
также боролся с проблемой и не хотел иметь дело со строками. Может быть более интересное решение, если мы получим значения по умолчанию из метаданных, о которых spring не заботится в реализации по умолчанию, но я просто помещаю туда нули. Решение пришло следующим образом:
переопределено simpleJdbcCall
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);
}