Каковы наилучшие обходные пути для использования предложения SQL IN
с экземплярами java.sql.PreparedStatement
, которое не поддерживается для нескольких значений из-за проблем безопасности атаки SQL-инъекцией: один ?
заполнитель представляет одно значение, а не список значений.
Рассмотрим следующий оператор SQL:
SELECT my_column FROM my_table where search_column IN (?)
Использование preparedStatement.setString( 1, "'A', 'B', 'C'" );
по сути является неработающей попыткой обойти причины использования ?
в первую очередь.
Какие обходные пути доступны?
Ответы:
Анализ различных доступных вариантов, а также плюсы и минусы каждого доступны здесь .
Предлагаемые варианты:
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 (?,?,?)
, поэтому я не знаю, почему блоггер даже предложил это.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 загружаются во время выполнения (например, из файла свойств), но требуют переменного количества параметров. В таких случаях сначала определите запрос:
Далее загрузите запрос. Затем определите количество параметров до его запуска. Когда число параметров известно, выполните:
Например:
Для некоторых баз данных, где передача массива через спецификацию JDBC 4 не поддерживается, этот метод может облегчить преобразование медленного
= ?
вIN (?)
условие более быстрого предложения, которое затем может быть расширено путем вызоваany
метода.источник
Решение для PostgreSQL:
или
источник
.createArrayOf()
, но я не уверен, что строгая семантика для пользователяArray
определяется спецификацией JDBC..createArrayOf
это не сработает, вы можете сделать собственное создание массива литералов вручнуюString arrayLiteral = "{A,\"B \", C,D}"
(обратите внимание, что у «B» есть пробел, а у C нет), а затем,statement.setString(1,arrayLiteral)
где подготовленный оператор... IN (SELECT UNNEST(?::VARCHAR[]))
или... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: я не думаю, чтоANY
работает сSELECT
.)Нет простого способа AFAIK. Если цель состоит в том, чтобы поддерживать высокий коэффициент кэширования операторов (т.е. не создавать оператор для каждого счетчика параметров), вы можете сделать следующее:
создайте оператор с несколькими (например, 10) параметрами:
... ГДЕ ВХОД (?,?,?,?,?,?,?,?,?,?) ...
Свяжите все действующие параметры
SetString (1, "Foo"); SetString (2, "бар");
Привязать остальное как NULL
setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)
NULL никогда не совпадает ни с чем, поэтому он оптимизируется разработчиком плана SQL.
Логика легко автоматизируется, когда вы передаете список в функцию DAO:
источник
NULL
в запросе совпадать соNULL
значением в базе данных?NOT IN
иIN
не обрабатывать нули таким же образом. Запустите это и посмотрите, что произойдет:select 'Matched' as did_it_match where 1 not in (5, null);
затем удалитеnull
и наблюдайте за магией.a IN (1,2,3,3,3,3,3)
же, какa IN (1,2,3)
. Он также работает сNOT IN
другимa NOT IN (1,2,3,null,null,null,null)
(который всегда не возвращает строк, какany_value != NULL
всегда false).Неприятный обходной путь, но, безусловно, выполнимый - это использование вложенного запроса. Создайте временную таблицу MYVALUES со столбцом в ней. Вставьте ваш список значений в таблицу MYVALUES. Затем выполните
Уродливая, но жизнеспособная альтернатива, если ваш список значений очень большой.
Этот метод имеет дополнительное преимущество, заключающееся в том, что оптимизатор может оптимизировать планы запросов (проверьте страницу на наличие нескольких значений, просмотр таблицы только один раз, а не один раз для каждого значения и т. Д.), Что может сэкономить накладные расходы, если ваша база данных не кэширует подготовленные операторы. Ваши «ВСТАВКИ» должны быть выполнены в пакетном режиме, а таблица MYVALUES может быть изменена, чтобы иметь минимальную блокировку или другие средства защиты от сильных накладных расходов.
источник
Ограничения оператора in () - корень всего зла.
Это работает для тривиальных случаев, и вы можете расширить его с помощью «автоматической генерации подготовленного оператора», однако оно всегда имеет свои пределы.
Подход in () может быть достаточно хорош для некоторых случаев, но не является доказательством ракет :)
Ракетно-стойким решением является передача произвольного числа параметров в отдельном вызове (например, путем передачи сгустка параметров), а затем представление (или любой другой способ) для представления их в SQL и использования в вашем каталоге where критерии.
Вариант грубой силы находится здесь http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
Однако, если вы можете использовать PL / SQL, этот беспорядок может стать довольно аккуратным.
Затем в параметре можно передать произвольное количество идентификаторов клиентов, разделенных запятыми, и:
Хитрость здесь в следующем:
Вид выглядит так:
где aux_in_list.getpayload ссылается на исходную строку ввода.
Возможный подход - передать массивы pl / sql (поддерживаются только Oracle), однако их нельзя использовать в чистом SQL, поэтому всегда необходим шаг преобразования. Преобразование не может быть выполнено в SQL, поэтому, в конце концов, передача clob со всеми параметрами в строке и преобразование его в представление является наиболее эффективным решением.
источник
Вот как я решил это в моем собственном приложении. В идеале вы должны использовать StringBuilder вместо + для строк.
Использование переменной, такой как x выше, вместо конкретных чисел очень помогает, если вы решите изменить запрос позже.
источник
Я никогда не пробовал, но .setArray () будет делать то, что вы ищете?
Обновление : очевидно нет. setArray, кажется, работает только с java.sql.Array, который поступает из столбца ARRAY, который вы получили из предыдущего запроса, или из подзапроса со столбцом ARRAY.
источник
Мой обходной путь:
Теперь вы можете использовать одну переменную для получения некоторых значений в таблице:
Итак, подготовленное заявление может быть:
С Уважением,
Хавьер Ибанез
источник
Я полагаю, что вы могли бы (используя базовые операции со строками) сгенерировать строку запроса в,
PreparedStatement
чтобы число?
совпадало с количеством элементов в вашем списке.Конечно, если вы делаете это, вы просто в шаге от генерации гигантского цепочки
OR
в вашем запросе, но без правильного числа?
в строке запроса, я не вижу, как еще вы можете обойти это.источник
Вы можете использовать метод setArray, как упомянуто в этом javadoc :
источник
Вы можете использовать
Collections.nCopies
для создания коллекции заполнителей и присоединиться к ним, используяString.join
:источник
Вот полное решение на Java для создания подготовленного оператора для вас:
источник
Spring позволяет передавать java.util.Lists в NamedParameterJdbcTemplate , который автоматизирует генерацию (?,?,?, ...,?), В зависимости от количества аргументов.
Для Oracle в этой публикации блога обсуждается использование oracle.sql.ARRAY (Connection.createArrayOf не работает с Oracle). Для этого вы должны изменить свой оператор SQL:
Функция оракула таблицы преобразует переданный массив в таблицу , как значения , используемые в
IN
заявлении.источник
попробовать использовать функцию instr?
затем
По общему признанию это - немного грязный взлом, но это действительно уменьшает возможности для инъекции sql. Работает в оракуле в любом случае.
источник
Sormula поддерживает оператор SQL IN, позволяя указывать объект java.util.Collection в качестве параметра. Создает подготовленное утверждение с? для каждого из элементов коллекции. См. Пример 4 (SQL в примере является комментарием, чтобы уточнить, что создано, но не используется Sormula).
источник
Вместо того, чтобы использовать
используйте Sql Statement как
и
или использовать хранимую процедуру, это было бы лучшим решением, так как операторы SQL будут скомпилированы и сохранены на сервере базы данных
источник
Я столкнулся с рядом ограничений, связанных с подготовленным заявлением:
Среди предложенных решений я бы выбрал то, которое не снижает производительность запросов и делает меньшее количество запросов. Это будет # 4 (пакетирование нескольких запросов) по ссылке @Don или указание значений NULL для ненужных '?' пометки как предложено @Dyladimir Dyuzhev
источник
Я только что разработал для этого специфичную для PostgreSQL опцию. Это что-то вроде хака, и оно имеет свои плюсы, минусы и ограничения, но, похоже, работает и не ограничивается конкретным языком разработки, платформой или драйвером PG.
Хитрость, конечно, заключается в том, чтобы найти способ передать коллекцию значений произвольной длины в виде одного параметра и заставить БД распознавать его как несколько значений. Решение, с которым я работаю, состоит в том, чтобы создать строку с разделителями из значений в коллекции, передать эту строку как один параметр и использовать string_to_array () с требуемым приведением для PostgreSQL, чтобы правильно использовать его.
Поэтому, если вы хотите найти «foo», «blah» и «abc», вы можете объединить их в одну строку как: «foo, blah, abc». Вот прямой SQL:
Очевидно, вы бы изменили явное приведение на то, что вы хотите, чтобы ваш результирующий массив значений был - int, text, uuid и т. Д. И поскольку функция принимает одно строковое значение (или два, я полагаю, если вы хотите настроить разделитель) также), вы можете передать его в качестве параметра в подготовленном выражении:
Это даже достаточно гибко, чтобы поддерживать такие вещи, как LIKE сравнения:
Опять же, без сомнения, это взлом, но он работает и позволяет вам по-прежнему использовать предварительно скомпилированные подготовленные операторы, которые принимают * ahem * дискретные параметры, с сопутствующими преимуществами безопасности и (возможно) производительности. Это целесообразно и действительно эффективно? Естественно, это зависит от того, как выполняется анализ строки и, возможно, приведение еще до того, как ваш запрос будет выполнен. Если вы ожидаете отправить три, пять, несколько десятков значений, конечно, это нормально. Несколько тысяч? Да, может быть, не так много. YMMV, ограничения и исключения применяются, никаких гарантий явных или подразумеваемых.
Но это работает.
источник
Просто для полноты картины : До тех пор , как множество значений не слишком велико, вы могли бы также просто строковую построить такое заявление
который вы можете затем передать в prepare (), а затем использовать setXXX () в цикле, чтобы установить все значения. Это выглядит отвратительно, но многие «большие» коммерческие системы обычно делают такие вещи, пока не достигнут конкретных ограничений по БД, таких как 32 КБ (я думаю, что так) для операторов в Oracle.
Конечно, вы должны убедиться, что набор никогда не будет чрезмерно большим, или делать перехват ошибок в случае, если это так.
источник
По идее Адама. Сделайте ваш подготовленный оператор вроде my_column из my_table, где search_column в (#) Создайте строку x и заполните ее числом «?,?,?» в зависимости от вашего списка значений Затем просто измените # в запросе для вашей новой строки x и заполнить
источник
Сгенерируйте строку запроса в PreparedStatement, чтобы число? Соответствовало количеству элементов в вашем списке. Вот пример:
источник
StringBuilder
. Но не так, как вы думаете. ДекомпилируяgenerateQsForIn
вы можете видеть, что для каждой итерации цикла выделяется два новыхStringBuilder
иtoString
вызывается для каждой.StringBuilder
Оптимизация только улавливает такие вещи , как ,"x" + i+ "y" + j
но не выходит за пределы одного выражения.ps.setObject(1,items)
вместо перебора списка, а затем установитьparamteres
?Существуют различные альтернативные подходы, которые мы можем использовать для предложения IN в PreparedStatement.
Используйте NULL в запросах PreparedStatement - Оптимальная производительность, отлично работает, когда вы знаете предел аргументов предложения IN. Если ограничений нет, вы можете выполнять запросы в пакетном режиме. Пример кода:
Вы можете проверить более подробную информацию об этих альтернативных подходах здесь .
источник
В некоторых ситуациях регулярное выражение может помочь. Вот пример, который я проверял на Oracle, и он работает.
Но есть ряд недостатков:
источник
Изучив различные решения на разных форумах и не найдя хорошего решения, я чувствую, что нижеприведенный хак, который я придумал, является самым простым для подражания и кода:
Пример. Предположим, у вас есть несколько параметров для передачи в предложении «IN». Просто поместите фиктивную строку в предложение IN, скажем, «ПАРАМ» означает список параметров, которые будут приходить на место этой фиктивной строки.
Вы можете собрать все параметры в одну строковую переменную в своем коде Java. Это можно сделать следующим образом:
Вы можете добавить все ваши параметры, разделенные запятыми, в одну строковую переменную 'param1', в нашем случае.
Собрав все параметры в одну строку, вы можете просто заменить фиктивный текст в запросе, например, «PARAM», на параметр String, т.е. param1. Вот что вам нужно сделать:
Теперь вы можете выполнить ваш запрос, используя метод executeQuery (). Просто убедитесь, что в вашем запросе нигде нет слова «ПАРАМ». Вы можете использовать комбинацию специальных символов и алфавитов вместо слова «PARAM», чтобы исключить вероятность появления такого слова в запросе. Надеюсь, у вас есть решение.
Примечание. Хотя это не подготовленный запрос, он выполняет ту работу, которую я хотел, чтобы мой код выполнял.
источник
Просто для полноты и потому, что я не видел, чтобы кто-то еще предложил это:
Перед реализацией любого из сложных предложений, приведенных выше, подумайте, действительно ли внедрение SQL-кода является проблемой в вашем сценарии.
Во многих случаях значение, предоставленное для IN (...), представляет собой список идентификаторов, которые были сгенерированы таким образом, что вы можете быть уверены, что инъекция невозможна ... (например, результаты предыдущего выбора some_id из some_table где some_condition.)
В этом случае вы можете просто объединить это значение и не использовать службы или подготовленный оператор для него или использовать их для других параметров этого запроса.
источник
PreparedStatement не предоставляет хорошего способа работы с предложением SQL IN. Согласно http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 «Вы не можете заменить вещи, которые должны стать частью оператора SQL. Это необходимо, потому что, если сам SQL может измениться, Драйвер не может предварительно скомпилировать оператор. У него также есть приятный побочный эффект - предотвращение атак SQL-инъекций. В итоге я использовал следующий подход:
источник
SetArray - лучшее решение, но оно недоступно для многих старых драйверов. Следующий обходной путь может быть использован в java8
Это решение лучше, чем другие уродливые решения цикла while, в которых строка запроса строится вручную.
источник
Это сработало для меня (psuedocode):
указать привязку:
источник
Мой пример для баз данных SQLite и Oracle.
Первый цикл For предназначен для создания объектов PreparedStatement.
Второй цикл For предназначен для предоставления значений для параметров PreparedStatement.
источник
Мой обходной путь (JavaScript)
SearchTerms
это массив, который содержит ваши входные данные / ключи / поля и т. д.источник