it-swarm.com.ru

PreparedStatement В предложении альтернативы?

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

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

SELECT my_column FROM my_table where search_column IN (?)

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

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

313
Chris Mazzola

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

Предлагаемые варианты:

  • Подготовьте SELECT my_column FROM my_table WHERE search_column = ?, выполните его для каждого значения и объедините результаты на стороне клиента. Требуется только одно подготовленное заявление. Медленно и больно.
  • Подготовьте SELECT my_column FROM my_table WHERE search_column IN (?,?,?) и выполните его. Требуется одно подготовленное утверждение на размер списка. Быстро и очевидно.
  • Подготовьте SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... и выполните его. [Или используйте UNION ALL вместо этих точек с запятой. --ed] Требуется одно подготовленное утверждение на размер списка. Глупо медленно, строго хуже, чем WHERE search_column IN (?,?,?), поэтому я не знаю, почему блоггер даже предложил это.
  • Используйте хранимую процедуру для построения набора результатов.
  • Подготовить N запросов разного размера в списке; скажем, с 2, 10 и 50 значениями. Чтобы найти IN-список с 6 различными значениями, заполните запрос size-10 так, чтобы он выглядел как SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6). Любой порядочный сервер оптимизирует дублирующиеся значения перед выполнением запроса.

Однако ни один из этих вариантов не является супер отличным.

В этих местах были даны ответы на повторяющиеся вопросы с одинаково разумными альтернативами, но ни один из них не был супер великим:

Правильный ответ, если вы используете JDBC4 и сервер, который поддерживает x = ANY(y), должен использовать PreparedStatement.setArray, как описано здесь:

Хотя, похоже, нет никакого способа заставить setArray работать с IN-списками.


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

query=SELECT * FROM table t WHERE t.column IN (?)

Далее загрузите запрос. Затем определите количество параметров до его запуска. Как только количество параметров известно, запустите:

sql = any( sql, count );

Например:

/**
 * Converts a SQL statement containing exactly one IN clause to an IN clause
 * using multiple comma-delimited parameters.
 *
 * @param sql The SQL statement string with one IN clause.
 * @param params The number of parameters the SQL statement requires.
 * @return The SQL statement with (?) replaced with multiple parameter
 * placeholders.
 */
public static String any(String sql, final int params) {
    // Create a comma-delimited list based on the number of parameters.
    final StringBuilder sb = new StringBuilder(
            new String(new char[params]).replace("\0", "?,")
    );

    // Remove trailing comma.
    sb.setLength(Math.max(sb.length() - 1, 0));

    // For more than 1 parameter, replace the single parameter with
    // multiple parameter placeholders.
    if (sb.length() > 1) {
        sql = sql.replace("(?)", "(" + sb + ")");
    }

    // Return the modified comma-delimited list of parameters.
    return sql;
}

Для некоторых баз данных, где передача массива через спецификацию JDBC 4 не поддерживается, этот метод может облегчить преобразование медленного = ? в более быстрое условие предложения IN (?), которое затем можно расширить, вызвав метод any.

174
Dónal

Решение для PostgreSQL:

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}

или же

final PreparedStatement statement = connection.prepareStatement(
        "SELECT my_column FROM my_table " + 
        "where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
    while(rs.next()) {
        // do some...
    }
} finally {
    rs.close();
}
109
Boris

Нет простого способа AFAIK . Если цель состоит в том, чтобы поддерживать высокий коэффициент кэширования операторов (т.е. не создавать оператор для каждого счетчика параметров), вы можете сделать следующее:

  1. создайте оператор с несколькими (например, 10) параметрами:

    ... ГДЕ ВХОД (?,?,?,?,?,?,?,?,?,?) ...

  2. Привязать все действующие параметры

    setString (1, "Foo");. .__ SetString (2, "бар");

  3. Привязать остальное как NULL

    setNull (3, Types.VARCHAR) ... SetNull (10, Types.VARCHAR)

NULL никогда не совпадает ни с чем, поэтому он оптимизируется разработчиком плана SQL.

Логика легко автоматизируется, когда вы передаете список в функцию DAO:

while( i < param.size() ) {
  ps.setString(i+1,param.get(i));
  i++;
}

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}
18
Vladimir Dyuzhev

Неприятный обходной путь, но, безусловно, выполнимый - использовать вложенный запрос. Создайте временную таблицу MYVALUES со столбцом в ней. Вставьте ваш список значений в таблицу MYVALUES. Затем выполните 

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

Уродливая, но жизнеспособная альтернатива, если ваш список значений очень большой.

У этого метода есть дополнительное преимущество, заключающееся в том, что планы оптимизатора запросов могут быть лучше (проверьте страницу на наличие нескольких значений, сканирование таблицы только один раз, а не один раз для каждого значения и т.д.), Что может сэкономить накладные расходы, если ваша база данных не кэширует подготовленные операторы. Ваши «ВСТАВКИ» должны быть выполнены в пакетном режиме, а таблицу MYVALUES, возможно, потребуется настроить, чтобы иметь минимальную блокировку или другие средства защиты от высоких накладных расходов.

10
James Schek

Ограничения оператора in () - корень всего зла.

Это работает для тривиальных случаев, и вы можете расширить его с помощью «автоматической генерации подготовленного оператора», однако оно всегда имеет свои пределы.

  • если вы создаете оператор с переменным числом параметров, это приведет к дополнительным затратам на анализ SQL при каждом вызове
  • на многих платформах количество параметров оператора in () ограничено 
  • на всех платформах общий размер текста SQL ограничен, что делает невозможным отправку 2000 заполнителей для параметров in
  • отправка переменных связывания 1000-10k невозможна, так как драйвер JDBC имеет свои ограничения

Подход in () может быть достаточно хорош для некоторых случаев, но не является доказательством ракет :)

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

Вариант грубой силы здесь http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

Однако, если вы можете использовать PL/SQL, этот беспорядок может стать довольно аккуратным.

function getCustomers(in_customerIdList clob) return sys_refcursor is 
begin
    aux_in_list.parse(in_customerIdList);
    open res for
        select * 
        from   customer c,
               in_list v
        where  c.customer_id=v.token;
    return res;
end;

Затем в параметре можно передать произвольное количество идентификаторов клиентов, разделенных запятыми, и:

  • не получит задержки разбора, так как SQL для выбора стабилен
  • нет сложности конвейерных функций - это всего лишь один запрос
  • sQL использует простое соединение вместо оператора IN, что довольно быстро
  • в конце концов, это хорошее эмпирическое правило, когда not поражает базу данных любым простым выбором или DML, поскольку это Oracle, который предлагает световые годы больше, чем MySQL или аналогичные простые движки баз данных. PL/SQL позволяет эффективно скрывать модель хранения от модели предметной области вашего приложения.

Хитрость здесь в следующем:

  • нам нужен вызов, который принимает длинную строку и сохраняет где-нибудь, где сессия db может получить к ней доступ (например, простая переменная пакета или dbms_session.set_context)
  • тогда нам нужно представление, которое может разобрать это на строки
  • и затем у вас есть представление, содержащее идентификаторы, которые вы запрашиваете, поэтому все, что вам нужно, - это простое соединение с запрашиваемой таблицей.

Вид выглядит так:

create or replace view in_list
as
select
    trim( substr (txt,
          instr (txt, ',', 1, level  ) + 1,
          instr (txt, ',', 1, level+1)
             - instr (txt, ',', 1, level) -1 ) ) as token
    from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1

где aux_in_list.getpayload ссылается на исходную строку ввода.


Возможный подход - передать массивы pl/sql (поддерживаются только Oracle), однако их нельзя использовать в чистом SQL, поэтому всегда необходим шаг преобразования. Преобразование не может быть выполнено в SQL, поэтому, в конце концов, передача clob со всеми параметрами в строке и преобразование его в представление является наиболее эффективным решением.

8
Gee Bee

Я никогда не пробовал, но .setArray () будет делать то, что вы ищете?

Обновление : Очевидно, нет. setArray, похоже, работает только с Java.sql.Array, который поступает из столбца ARRAY, который вы получили из предыдущего запроса, или из подзапроса со столбцом ARRAY.

5
Paul Tomblin

Вот как я решил это в моем собственном приложении. В идеале вы должны использовать StringBuilder вместо + для строк.

    String inParenthesis = "(?";
    for(int i = 1;i < myList.size();i++) {
      inParenthesis += ", ?";
    }
    inParenthesis += ")";

    try(PreparedStatement statement = SQLite.connection.prepareStatement(
        String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
      int x = 1;
      statement.setLong(x++, race.startTime);
      statement.setString(x++, race.name);
      statement.setInt(x++, traderIdx);

      for(String str : race.betFair.winners) {
        statement.setString(x++, str);
      }

      int effected = statement.executeUpdate();
    }

Использование переменной типа x вместо конкретных чисел очень помогает, если вы решите изменить запрос позже.

5
m.sabouri

Мой обходной путь:

create or replace type split_tbl as table of varchar(32767);
/

create or replace function split
(
  p_list varchar2,
  p_del varchar2 := ','
) return split_tbl pipelined
is
  l_idx    pls_integer;
  l_list    varchar2(32767) := p_list;
  l_value    varchar2(32767);
begin
  loop
    l_idx := instr(l_list,p_del);
    if l_idx > 0 then
      pipe row(substr(l_list,1,l_idx-1));
      l_list := substr(l_list,l_idx+length(p_del));
    else
      pipe row(l_list);
      exit;
    end if;
  end loop;
  return;
end split;
/

Теперь вы можете использовать одну переменную для получения некоторых значений в таблице:

select * from table(split('one,two,three'))
  one
  two
  three

select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
  value1 AAA
  value2 BBB

Итак, подготовленное заявление может быть:

  "select * from TABLE where COL in (select * from table(split(?)))"

С Уважением,

Хавьер Ибанез

5
Javier Ibanez

Я предполагаю, что вы могли бы (используя базовые операции со строками) сгенерировать строку запроса в PreparedStatement, чтобы количество ? соответствовало количеству элементов в вашем списке. 

Конечно, если вы делаете это, вы в шаге от генерации гигантской цепочки OR в своем запросе, но без правильного числа ? в строке запроса, я не вижу, как еще вы можете обойти это ,.

3
Adam Bellaire

Вы можете использовать метод setArray, как упомянуто в это javadoc :

PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();
2
Panky031

Spring позволяет передавать Java.util.Lists в NamedParameterJdbcTemplate , который автоматизирует генерацию (?,?,?, ...,?), В зависимости от количества аргументов.

Для Oracle, в этой публикации блога обсуждается использование Oracle.sql.ARRAY (Connection.createArrayOf не работает с Oracle). Для этого вы должны изменить свой оператор SQL:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

табличная функция Oracle преобразует переданный массив в табличное значение, используемое в операторе IN.

1
Hans-Peter Störr

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

  1. Подготовленные операторы кэшируются только внутри одного сеанса (Postgres), поэтому он действительно будет работать только с пулами соединений
  2. Многие различные подготовленные операторы, предложенные @BalusC, могут привести к переполнению кэша, а ранее кэшированные операторы будут удалены.
  3. Запрос должен быть оптимизирован и использовать индексы. Звучит очевидно, однако, например, оператор ANY (ARRAY ...), предложенный @Boris в одном из лучших ответов, не может использовать индексы, и запрос будет медленным, несмотря на кэширование
  4. Подготовленный оператор также кэширует план запроса, и фактические значения любых параметров, указанных в операторе, недоступны.

Среди предложенных решений я бы выбрал то, которое не снижает производительность запросов и делает меньше запросов. Это будет # 4 (пакетирование нескольких запросов) по ссылке @Don или указание значений NULL для ненужных '?' пометки как предложено @Dyladimir Dyuzhev 

1
Alexander

вместо того, чтобы использовать

SELECT my_column FROM my_table where search_column IN (?)

использовать оператор Sql как 

select id, name from users where id in (?, ?, ?)

а также

preparedStatement.setString( 1, 'A');
preparedStatement.setString( 2,'B');
preparedStatement.setString( 3, 'C');

или использовать хранимую процедуру, это было бы лучшим решением, так как операторы SQL будут скомпилированы и сохранены на сервере базы данных

1
kapil das

Sormula поддерживает оператор SQL IN, позволяя указывать объект Java.util.Collection в качестве параметра. Создает подготовленное утверждение с? для каждого из элементов коллекции. См. Пример 4 (SQL в примере - это комментарий для пояснения того, что создано, но не используется Сормулой).

1
Jeff Miller

попробовать использовать функцию instr?

select my_column from my_table where  instr(?, ','||search_column||',') > 0

затем

ps.setString(1, ",A,B,C,"); 

По общему признанию это - немного грязный взлом, но это действительно уменьшает возможности для инъекции sql. В любом случае работает в Oracle.

1
stjohnroe

Вот полное решение на Java для создания подготовленного оператора для вас:

/*usage:

Util u = new Util(500); //500 items per bracket. 
String sqlBefore  = "select * from myTable where (";
List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); 
string sqlAfter = ") and foo = 'bar'"; 

PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId");
*/



import Java.sql.Connection;
import Java.sql.PreparedStatement;
import Java.sql.SQLException;
import Java.util.ArrayList;
import Java.util.List;

public class Util {

    private int numValuesInClause;

    public Util(int numValuesInClause) {
        super();
        this.numValuesInClause = numValuesInClause;
    }

    public int getNumValuesInClause() {
        return numValuesInClause;
    }

    public void setNumValuesInClause(int numValuesInClause) {
        this.numValuesInClause = numValuesInClause;
    }

    /** Split a given list into a list of lists for the given size of numValuesInClause*/
    public List<List<Integer>> splitList(
            List<Integer> values) {


        List<List<Integer>> newList = new ArrayList<List<Integer>>(); 
        while (values.size() > numValuesInClause) {
            List<Integer> sublist = values.subList(0,numValuesInClause);
            List<Integer> values2 = values.subList(numValuesInClause, values.size());   
            values = values2; 

            newList.add( sublist);
        }
        newList.add(values);

        return newList;
    }

    /**
     * Generates a series of split out in clause statements. 
     * @param sqlBefore ""select * from dual where ("
     * @param values [1,2,3,4,5,6,7,8,9,10]
     * @param "sqlAfter ) and id = 5"
     * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)"
     */
    public String genInClauseSql(String sqlBefore, List<Integer> values,
            String sqlAfter, String identifier) 
    {
        List<List<Integer>> newLists = splitList(values);
        String stmt = sqlBefore;

        /* now generate the in clause for each list */
        int j = 0; /* keep track of list:newLists index */
        for (List<Integer> list : newLists) {
            stmt = stmt + identifier +" in (";
            StringBuilder innerBuilder = new StringBuilder();

            for (int i = 0; i < list.size(); i++) {
                innerBuilder.append("?,");
            }



            String inClause = innerBuilder.deleteCharAt(
                    innerBuilder.length() - 1).toString();

            stmt = stmt + inClause;
            stmt = stmt + ")";


            if (++j < newLists.size()) {
                stmt = stmt + " OR ";
            }

        }

        stmt = stmt + sqlAfter;
        return stmt;
    }

    /**
     * Method to convert your SQL and a list of ID into a safe prepared
     * statements
     * 
     * @throws SQLException
     */
    public PreparedStatement prepareStatements(String sqlBefore,
            ArrayList<Integer> values, String sqlAfter, Connection c, String identifier)
            throws SQLException {

        /* First split our potentially big list into lots of lists */
        String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier);
        PreparedStatement ps = c.prepareStatement(stmt);

        int i = 1;
        for (int val : values)
        {

            ps.setInt(i++, val);

        }
        return ps;

    }

}
1
dwjohnston

SetArray - лучшее решение, но оно недоступно для многих старых драйверов. Следующий обходной путь может быть использован в Java8

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)"

String markersString = inputArray.stream().map(e -> "?").collect(joining(","));
String sqlQuery = String.format(baseSQL, markersString);

//Now create Prepared Statement and use loop to Set entries
int index=1;

for (String input : inputArray) {
     preparedStatement.setString(index++, input);
}

Это решение лучше, чем другие уродливые решения цикла while, в которых строка запроса строится вручную. 

0
Raheel

Вы можете использовать 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
Gurwinder Singh

Сгенерируйте строку запроса в 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
neu242

PreparedStatement не предоставляет хорошего способа работы с предложением SQL IN. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Вы не можете заменить вещи, которые должны стать частью оператора SQL. Это необходимо, потому что если сам SQL может измениться, драйвер не может предварительно скомпилировать оператор. Он также имеет побочный эффект Nice, предотвращающий атаки SQL-инъекций ». Я закончил тем, что использовал следующий подход:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)";
query = query.replace("$searchColumns", "'A', 'B', 'C'");
Statement stmt = connection.createStatement();
boolean hasResults = stmt.execute(query);
do {
    if (hasResults)
        return stmt.getResultSet();

    hasResults = stmt.getMoreResults();

} while (hasResults || stmt.getUpdateCount() != -1);
0
pedram bashiri

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

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

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

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

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
0
epeleg

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

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

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

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

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

Пример. Предположим, у вас есть несколько параметров для передачи в предложении «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 (). Просто убедитесь, что в вашем запросе нигде нет слова «ПАРАМ». Вы можете использовать комбинацию специальных символов и алфавитов вместо слова «ПАРАМ», чтобы исключить возможность появления такого слова в запросе. Надеюсь, у вас есть решение.

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

0
bnsk

Существуют различные альтернативные подходы, которые мы можем использовать для предложения 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);
        }
    

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

0
Pankaj

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

0
Yorch

Я только что разработал для этого специфичную для 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[]);

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

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

0
Joel Fouse

Просто для полноты: до тех пор, пока набор значений не слишком велик, вы можете также просто создаете строковое выражение вроде

... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?

который вы могли бы затем передать в prepare (), а затем использовать setXXX () в цикле, чтобы установить все значения. Это выглядит отвратительно, но многие «большие» коммерческие системы обычно делают подобные вещи, пока не достигнут конкретных ограничений по БД, таких как 32 КБ (я думаю, что так) для операторов в Oracle.

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

0
Carl Smotricz