Search code examples
javagenericsguavacovariance

How to prepare an argument of type List<Entry<? extends Class<?>, ?>>


The objective is to write a convenience method that return a ResultSet from a JDBC query with a simple client-side invocation form.

I have written something like this:

public class JdbcQueryManager {
  public static ResultSet executePreparedStatementWithParameters(
      Connection jdbcConnection, String sqlQuery,
      Map.Entry<? extends Class<?>, ?>... sqlQueryParameters)
      throws JdbcQueryFailureException {
    return executePreparedStatementWithParameters(jdbcConnection, sqlQuery,
        Arrays.asList(sqlQueryParameters), ResultSet.TYPE_FORWARD_ONLY,
        ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
  }
  private static ResultSet executePreparedStatementWithParameters(
      Connection jdbcConnection, String sqlQuery,
      List<Map.Entry<? extends Class<?>, ?>> sqlQueryParameters,
      int resultSetType, int resultSetConcurrency, int resultSetHoldability)
      throws JdbcQueryFailureException {
    try {
      PreparedStatement preparedStatement =
          jdbcConnection.prepareStatement(sqlQuery, resultSetType,
              resultSetConcurrency, resultSetHoldability);
      for (int i = 0; i < sqlQueryParameters.size(); i++) {
        int sqlQueryParameterIndex = i + 1; // SQL parameters are 1-based
        Entry<? extends Class<?>, ?> sqlQueryParameter =
            sqlQueryParameters.get(i);
        Class<?> sqlQueryParameterClass = sqlQueryParameter.getKey();
        if (sqlQueryParameterClass == Integer.class) {
          int sqlQueryParameterIntegerValue =
              (Integer) sqlQueryParameter.getValue();
          preparedStatement.setInt(sqlQueryParameterIndex,
              sqlQueryParameterIntegerValue);
        } else if (sqlQueryParameterClass == String.class) {
          String sqlQueryParameterStringValue =
              (String) sqlQueryParameter.getValue();
          preparedStatement.setString(sqlQueryParameterIndex,
              sqlQueryParameterStringValue);
          // TODO: accept other types, not just String and Integer
        } else {
          throw new JdbcQueryFailureException(new IllegalArgumentException(
              sqlQueryParameterClass.getName()));
        }
      }
      ResultSet resultSet = preparedStatement.executeQuery();
      return resultSet;
    } catch (SQLException sqlException) {
      throw new JdbcQueryFailureException(sqlException);
    }
  }
}

using this convenience class:

public class QueryParameter<T> extends AbstractMap.SimpleEntry<Class<T>, T> {
  @SuppressWarnings("unchecked")
  public QueryParameter(T parameterValue) {
    super((Class<T>) parameterValue.getClass(), parameterValue);
  }
}

to be able to execute a JDBC SQL statement like this:

ResultSet resultSet =
    JdbcQueryManager.executePreparedStatementWithParameters(jdbcConnection,
        sqlQuery, new QueryParameter<String>("AnswerRequest"),
        new QueryParameter<Integer>(42));

... how can I make it better?

Specifically, my perplexities lie in the use of this seemingly complicated, possibly unneeded form:

List<Map.Entry<? extends Class<?>, ?>>

Solution

  • There is no value in passing in a list of Map.Entry<? extends Class<?>, ?> - you are attempting to tell your method what class each parameter is. Don't do this!!!

    Contrary to popular belief, you don't need to use the various typed preparedStatement.setXXX() methods if you're using "basic" java objects (wrapped primitives and Dates), just use preparedStatement.setObject(index, object) and the jdbc driver will figure out what to do!

    The only time you need to use a typed setter is if your object is not one of the "basic" types. If you really need this, then just use instanceof to check each parameter, then you'd write some code to maybe extract a String value to use, but you could still call preparedStatement.setObject(index, object) with that String.


    I have written something like this myself and I simply used:

    public static ResultSet executePreparedStatementWithParameters(
        Connection jdbcConnection, String sqlQuery, Object... parameters)
    

    and it works just fine.