PreparedStatement В предложении альтернативы? - PullRequest
320 голосов
/ 07 октября 2008

Каковы лучшие обходные пути для использования предложения SQL IN с экземплярами java.sql.PreparedStatement, которое не поддерживается для нескольких значений из-за проблем безопасности атаки SQL-инъекцией: один ? заполнитель представляет одно значение, а не список значений.

Рассмотрим следующий оператор SQL:

SELECT my_column FROM my_table where search_column IN (?)

Использование preparedStatement.setString( 1, "'A', 'B', 'C'" );, по сути, является нерабочей попыткой обойти причины, по которым сначала стоит использовать ?.

Какие обходные пути доступны?

Ответы [ 28 ]

0 голосов
/ 07 октября 2008

По идее Адама. Сделайте свое подготовленное утверждение вроде my_column из my_table где search_column in (#) Создайте строку x и заполните ее числом «?,?,?» в зависимости от вашего списка значений Затем просто измените # в запросе для вашей новой строки x и заполните

0 голосов
/ 16 декабря 2009

Создайте строку запроса в PreparedStatement, чтобы число? Соответствовало количеству элементов в вашем списке. Вот пример:

public void myQuery(List<String> items, int other) {
  ...
  String q4in = generateQsForIn(items.size());
  String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?";
  PreparedStatement ps = connection.prepareStatement(sql);
  int i = 1;
  for (String item : items) {
    ps.setString(i++, item);
  }
  ps.setInt(i++, other);
  ResultSet rs = ps.executeQuery();
  ...
}

private String generateQsForIn(int numQs) {
    String items = "";
    for (int i = 0; i < numQs; i++) {
        if (i != 0) items += ", ";
        items += "?";
    }
    return items;
}
0 голосов
/ 01 апреля 2015

Просто для полноты и потому что я не видел, чтобы кто-нибудь еще предложил это:

Перед реализацией любого из сложных предложений, приведенных выше, подумайте, действительно ли внедрение SQL-кода является проблемой в вашем сценарии.

Во многих случаях значение, предоставленное для IN (...), представляет собой список идентификаторов, которые были сгенерированы таким образом, что вы можете быть уверены, что инъекция невозможна ... (например, результаты предыдущего выбора some_id из some_table, где some_condition.)

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

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
0 голосов
/ 15 марта 2015

Изучив различные решения на разных форумах и не найдя хорошего решения, я чувствую, что нижеприведенный хак, который я придумал, является самым простым для подражания и кода:

Пример. Предположим, у вас есть несколько параметров для передачи в предложении «IN». Просто поместите фиктивную строку внутри предложения IN, скажем, «ПАРАМ» означает список параметров, которые будут приходить на место этой фиктивной строки.

    select * from TABLE_A where ATTR IN (PARAM);

Вы можете собрать все параметры в одну строковую переменную в своем коде Java. Это можно сделать следующим образом:

    String param1 = "X";
    String param2 = "Y";
    String param1 = param1.append(",").append(param2);

Вы можете добавить все свои параметры, разделенные запятыми, в одну строковую переменную 'param1', в нашем случае.

После сбора всех параметров в одну строку вы можете просто заменить фиктивный текст в вашем запросе, т.е. в данном случае «PARAM», на параметр String, то есть param1. Вот что вам нужно сделать:

    String query = query.replaceFirst("PARAM",param1); where we have the value of query as 

    query = "select * from TABLE_A where ATTR IN (PARAM)";

Теперь вы можете выполнить ваш запрос, используя метод executeQuery (). Просто убедитесь, что в вашем запросе нигде нет слова «PARAM». Вы можете использовать комбинацию специальных символов и алфавитов вместо слова «PARAM», чтобы исключить вероятность появления такого слова в запросе. Надеюсь, у вас есть решение.

Примечание. Хотя это не подготовленный запрос, он выполняет ту работу, которую я хотел, чтобы мой код выполнял.

0 голосов
/ 02 января 2015

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

select * from my_table where REGEXP_LIKE (search_column, 'value1|value2')

Но есть ряд недостатков:

  1. Любой столбец, который он применял, должен быть преобразован в varchar / char, по крайней мере, неявно.
  2. Нужно быть осторожным со специальными символами.
  3. Это может снизить производительность - в моем случае IN версия использует сканирование по индексу и диапазону, а версия REGEXP выполняет полное сканирование.
0 голосов
/ 29 апреля 2018

Вы можете использовать Collections.nCopies, чтобы создать коллекцию заполнителей и присоединиться к ним, используя String.join:

List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try (   Connection connection = getConnection();
        PreparedStatement ps = connection.prepareStatement(sql)) {
    int i = 1;
    for (String param : params) {
        ps.setString(i++, param);
    }
    /*
     * Execute query/do stuff
     */
}
0 голосов
/ 11 января 2019

Я только что разработал для этого специфичную для PostgreSQL опцию. Это что-то вроде хака, и оно имеет свои плюсы и минусы и ограничения, но, похоже, работает и не ограничивается конкретным языком разработки, платформой или драйвером PG.

Конечно, хитрость заключается в том, чтобы найти способ передать коллекцию значений произвольной длины в качестве одного параметра и сделать так, чтобы БД распознал его как несколько значений. Решение, с которым я работаю, состоит в том, чтобы создать строку с разделителями из значений в коллекции, передать эту строку в качестве единственного параметра и использовать string_to_array () с необходимым приведением для PostgreSQL, чтобы правильно использовать его.

Так что, если вы хотите найти «foo», «blah» и «abc», вы можете объединить их в одну строку как: «foo, blah, abc». Вот прямой SQL:

select column from table
where search_column = any (string_to_array('foo,blah,abc', ',')::text[]);

Вы, очевидно, изменили бы явное приведение к тому, что вы хотели, чтобы ваш результирующий массив значений был - int, text, uuid и т. Д. И поскольку функция принимает одно строковое значение (или два, я полагаю, если вы хотите также настроить разделитель), вы можете передать его в качестве параметра в подготовленном выражении:

select column from table
where search_column = any (string_to_array($1, ',')::text[]);

Это даже достаточно гибко, чтобы поддерживать такие вещи, как LIKE сравнения:

select column from table
where search_column like any (string_to_array('foo%,blah%,abc%', ',')::text[]);

Опять же, без сомнения, это взлом, но он работает и позволяет вам по-прежнему использовать предварительно скомпилированные подготовленные операторы, которые принимают * гм * дискретных параметров, с сопутствующими преимуществами безопасности и (возможно) производительности. Это целесообразно и действительно эффективно? Естественно, это зависит от того, как выполняется анализ строки и, возможно, приведение еще до того, как ваш запрос будет выполнен. Если вы ожидаете отправить три, пять, несколько десятков значений, конечно, это нормально. Несколько тысяч? Да, может быть, не так много. YMMV, применяются ограничения и исключения, без явных или подразумеваемых гарантий.

Но это работает.

0 голосов
/ 26 января 2014

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

  1. Использование одиночных запросов - самая низкая производительность и ресурсоемкость
  2. Использование StoredProcedure - самый быстрый, но специфичный для базы данных
  3. Создание динамического запроса для PreparedStatement - хорошая производительность, но не получает преимущества от кэширования, и PreparedStatement перекомпилируется каждый раз.
  4. Использование NULL в запросах PreparedStatement - оптимальная производительность, отлично работает, когда вы знаете предел аргументов предложения IN. Если нет ограничений, то вы можете выполнять запросы в пакетном режиме. Пример кода:

        int i = 1;
        for(; i <=ids.length; i++){
            ps.setInt(i, ids[i-1]);
        }
    
        //set null for remaining ones
        for(; i<=PARAM_SIZE;i++){
            ps.setNull(i, java.sql.Types.INTEGER);
        }
    

Подробнее об этих альтернативных подходах вы можете узнать здесь .

...