【问题标题】:Spring SimpleJdbcCall default (optional) argumentsSpring SimpleJdbcCall 默认(可选)参数
【发布时间】:2012-10-21 03:40:27
【问题描述】:

我正在尝试调用具有默认(可选)参数而不传递它们的存储过程,但它不起作用。与here 描述的问题基本相同。

我的代码:

  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 是根据this 的可选参数。也证实了我可以在没有 PARTNAME 参数的情况下手动运行此过程。

【问题讨论】:

  • 第二个超链接显示 404 。

标签: java spring stored-procedures jdbc


【解决方案1】:

在放弃这个问题并只传递所有参数(包括可选参数)后,我遇到了它无法传递布尔参数的问题,因为布尔不是 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
           }
        }
  );

【讨论】:

    【解决方案2】:

    也在努力解决这个问题,不想处理字符串。 如果我们从元数据中获取默认值,可能会有更有趣的解决方案,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);
    }
    

    【讨论】:

      【解决方案3】:

      这是我采用的另一种方法。我添加了让用户设置他们将在通话中提供的参数数量的功能。这些将是前 n 个位置参数。存储过程中可用的任何剩余参数都必须通过数据库的默认值处理进行设置。这允许使用默认值将新参数添加到列表的末尾,或者可以为空,而不会破坏不知道提供值的代码。

      我对 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 以其 IOC 容器而闻名。但是,在它的实用程序类中,我必须借助反射来提供依赖类的替代实现。

      【讨论】:

        【解决方案4】:

        今天想出了一个不错的解决方案,它可以处理非空默认值,并且不使用果味反射技术。它的工作原理是在外部为函数创建元数据上下文以检索所有参数类型等,然后从中手动构造 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());
        

        【讨论】:

          【解决方案5】:

          我使用这个 util 方法:

          public <T> void setOptionalParameter(MapSqlParameterSource parameters, String name, T value) {
              if (value == null)
                  parameters.addValue(name, value, Types.NULL);
              else
                  parameters.addValue(name, value);
          }
          

          【讨论】:

            【解决方案6】:

            我找到了 SimpleJdbcCall 和 Spring 5.2.1、Java 8、Oracle 12 的解决方案。

            你需要:

            1. 使用 .withoutProcedureColumnMetaDataAccess()
            2. 使用 .withNamedBinding()
            3. 声明参数,你知道在 .declareParameters() 调用。仅使用在此方法中声明的参数调用过程。默认参数,不想设置,这里就不写了。

            示例调用如下

            final String dataParamName = "P_DATA";
            final String ageParamName = "P_AGE";
            final String genderParamName = "P_GENDER";
            
            final String acceptedParamName = "P_ACCEPTED";
            
            
            SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(getJdbcTemplate())
                    .withCatalogName("PKG_USER")
                    .withProcedureName("USER_CHECK")
                    .withoutProcedureColumnMetaDataAccess()
                    .withNamedBinding()
                    .declareParameters(
                            new SqlParameter(dataParamName, OracleTypes.VARCHAR),
                            new SqlParameter(ageParamName, OracleTypes.NUMBER),
                            new SqlParameter(genderParamName, OracleTypes.VARCHAR),
                            new SqlOutParameter(acceptedParamName, OracleTypes.NUMBER)
                    );
            
            SqlParameterSource parameterSource = new MapSqlParameterSource()
                    .addValue(dataParamName, data)
                    .addValue(ageParamName, age)
                    .addValue(genderParamName, gender);
            
            Map<String, Object> out = simpleJdbcCall.execute(parameterSource);
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-05-26
              • 2013-10-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-12
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多