Каков наилучший подход с использованием JDBC для параметризации предложения IN?

Скажем, что у меня есть запрос формы

SELECT * FROM MYTABLE WHERE MYCOL in (?)

И я хочу параметризовать аргументы в.

Есть ли простой способ сделать это в Java с JDBC, таким образом, чтобы работать с несколькими базами данных без изменения самого SQL?

Самый близкий вопрос, который я нашел, связан с С#, мне интересно, есть ли что-то другое для Java/JDBC.

Ответы

Ответ 1

В JDBC нет простого способа сделать это. Некоторые драйверы JDBC, похоже, поддерживают PreparedStatement#setArray() в предложении IN. Я не уверен, какие именно.

Вы можете просто использовать вспомогательный метод с String#join() и Collections#nCopies() для создания заполнителей для предложения IN и другого вспомогательного метода для установки всех значений в цикле с помощью PreparedStatement#setObject().

public static String preparePlaceHolders(int length) {
    return String.join(",", Collections.nCopies(length, "?"));
}

public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
    for (int i = 0; i < values.length; i++) {
        preparedStatement.setObject(i + 1, values[i]);
    }
}

Здесь вы можете использовать его:

private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";

public List<Entity> find(Set<Long> ids) throws SQLException {
    List<Entity> entities = new ArrayList<Entity>();
    String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));

    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
    ) {
        setValues(statement, ids.toArray());

        try (ResultSet resultSet = statement.executeQuery()) {
            while (resultSet.next()) {
                entities.add(map(resultSet));
            }
        }
    }

    return entities;
}

private static Entity map(ResultSet resultSet) throws SQLException {
    Enitity entity = new Entity();
    entity.setId(resultSet.getLong("id"));
    entity.setName(resultSet.getString("name"));
    entity.setValue(resultSet.getInt("value"));
    return entity;
}

Обратите внимание, что некоторые базы данных имеют ограничение допустимого количества значений в предложении IN. Oracle, например, имеет этот предел для 1000 элементов.

Ответ 2

Поскольку никто не отвечает в случае предложения большого IN (более 100), я брошу свое решение по этой проблеме, которая отлично работает для JDBC. Короче говоря, я заменяю IN на INNER JOIN на таблице tmp.

Что я делаю, так это то, что я называю таблицей пакетных идентификаторов, и в зависимости от СУБД я могу сделать это tmp-таблицу или таблицу памяти.

Таблица имеет два столбца. Один столбец с идентификатором из раздела IN и другой столбец с идентификатором пакета, который я генерирую на лету.

SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?

Перед тем, как вы выберете, вы переместите свои идентификаторы в таблицу с заданным идентификатором партии. Затем вы просто заменяете исходный запрос IN предложением с помощью комбинации INNER JOIN в вашей таблице идентификаторов. WHERE batch_id равно вашей текущей партии. После вашего завершения удалите записи для вас.

Ответ 3

Стандартный способ сделать это (если вы используете Spring JDBC) - использовать org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.

Используя этот класс, можно определить List как ваш параметр SQL и использовать NamedParameterJdbcTemplate для замены именованного параметра. Например:

public List<MyObject> getDatabaseObjects(List<String> params) {
    NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    String sql = "select * from my_table where my_col in (:params)";
    List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
    return result;
}

Ответ 4

Я решил это, построив строку SQL с таким количеством ?, как у меня есть значения для поиска.

SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)

Сначала я искал тип массива, который я могу передать в оператор, но все типы массивов JDBC специфичны для поставщика. Поэтому я остался с несколькими ?.

Ответ 5

Я получил ответ от docs.spring(19.7.3)

Стандарт SQL позволяет выбирать строки на основе выражения, содержащего список переменных. Типичным примером может быть выбор * из T_ACTOR, где id в (1, 2, 3). Этот список переменных напрямую не поддерживается для подготовленных операторов стандартом JDBC; вы не можете объявить переменное количество заполнителей. Вам нужно несколько вариантов с желаемым количеством подготовленных заполнителей или вам нужно генерировать строку SQL динамически, как только вы знаете, сколько заполнителей требуется. Именованный параметр поддержки, предоставляемый в NamedParameterJdbcTemplate и JdbcTemplate, использует последний подход. Передайте значения как java.util.List примитивных объектов. Этот список будет использоваться для вставки необходимых заполнителей и передачи значений во время выполнения инструкции.

Надеюсь, это поможет вам.

Ответ 6

AFAIK, в JDBC нет стандартной поддержки для обработки Коллекций в качестве параметров. Было бы здорово, если бы вы могли просто перейти в список, и это было бы расширено.

Spring Доступ JDBC поддерживает передачу коллекций в качестве параметров. Вы можете посмотреть, как это делается для вдохновения для безопасного кодирования.

См. Автоматическое расширение коллекций в качестве параметров JDBC

(В статье сначала обсуждается Hibernate, затем обсуждается JDBC.)

Ответ 7

См. мою пробную версию и успех. Говорят, что размер списка имеет потенциальное ограничение.   Список l = Arrays.asList(новое целое число [] {12496,12497,12498,12499});       Карта param = Collections.singletonMap( "goodsid", l);

    NamedParameterJdbcTemplate  namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
    String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
    List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);

Ответ 8

sormula делает это простым (см. Пример 4):

ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);

// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);

// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
for (Inventory inventory: inventoryTable.
    selectAllWhere("partNumberIn", partNumbers))    
{
    System.out.println(inventory.getPartNumber());
}

Ответ 9

Существуют различные альтернативные подходы, которые мы можем использовать.

  • Выполнять одиночные запросы - медленно и не рекомендуется
  • Использование хранимой процедуры - спецификация базы данных
  • Динамическое создание запроса PreparedStatement - хорошая производительность, но отсутствие преимуществ кэширования и перекомпиляции потребностей.
  • Использование NULL в запросе PreparedStatement - я думаю, что это хороший подход с оптимальной производительностью.

Подробнее об этих здесь.

Ответ 10

Один из способов, о котором я могу думать, - использовать java.sql.PreparedStatement и немного присяжных присяжных

PreparedStatement подготовленStmt = conn.prepareStatement( "SELECT * FROM MYTABLE WHERE MYCOL в (?)" );

... и затем...

prepareStmt.setString(1, [ваши строгие параметры]);

http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html