2010-05-18 3 views
34

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

SELECT * FROM MYTABLE WHERE MYCOL in (?) 

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

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

Ближайший question I've found had to do with C#, мне интересно, есть ли что-то другое для Java/JDBC.

ответ

3

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

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

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

+1

Это то, что мы делаем прямо сейчас, но то, что я надеялся, что существует единый способ сделать это без специального SQL ... – Uri

+0

Кроме того, если это что-то вроде Oracle, ему придется перепрофилировать большинство операторов. – orbfish

-1

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

PreparedStatement preparedStmt = conn.prepareStatement ("SELECT * FROM MYTABLE WHERE Mycol в()?");

... а потом ...

preparedStmt.setString (1, [ваши stringged PARAMS]);

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

+6

Это не сработает, поскольку он может создавать запрос типа '... WHERE MYCOL IN ('2,3,5,6')', который не тот, который вы пытаетесь сделать. – Progman

1

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

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

См Auto-expanding collections as JDBC parameters

(В статье первой обсуждает Hibernate, а затем переходит к обсуждению JDBC.)

36

Там нет действительно не простой способ сделать это в 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 позиций.

+1

Будет ли такой подход приводить к SQL-инъекции? –

+3

@ Кайлан: ни одна строка кода не добавляет управляемый пользователем входной сигнал в строку запроса SQL. Таким образом, определенно нет средств риска SQL-инъекций. – BalusC

+0

Хорошо. Спасибо за разъяснение –

0

См. Мою пробную версию и Успех. Говорят, что размер списка имеет потенциальное ограничение. Список l = Массивы.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); 
0

sormula делает этот простой (см): Example 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()); 
} 
12

Поскольку никто ответа на случай для большой в пункте (более 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 равно вашей текущей партии. После того, как вы сделали, вы удалите записи для вас.

+2

+1 Это будет достаточно эффективно для больших наборов данных, а не для разбивки базы данных. – jasonk

+0

Хм, не будет ли полусоединение (предикат 'IN' или' EXISTS'), возможно, превосходит внутреннее соединение? –

+0

@LukasEder YMMV с моим псевдо-кодом SQL. Как всегда тест/бенчмарк. Это интересная идея. Говоря о том, на что я должен пойти, посмотрим, на что наш фактический SQL, когда мы это делаем. –

7

Стандартный способ сделать это (если вы используете 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; 
} 
0

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

  1. Execute Одиночные запросы - медленно и не рекомендуется
  2. Использование хранимых процедур - базы данных конкретной
  3. Создание PreparedStatement запросов динамически - хорошая производительность, но ослабленные преимущества кэширования и требует перекомпиляции
  4. Использование NULL в PreparedStatement Query - Я думаю, что это хороший подход с оптимальной производительностью.

Подробнее об этом here.

2

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

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

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