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 ]

1 голос
/ 24 августа 2013

вместо использования

SELECT my_column FROM my_table where search_column IN (?)

используйте Sql Statement как

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

и

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

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

1 голос
/ 21 ноября 2011

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

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

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

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

тогда

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

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

1 голос
/ 12 сентября 2013

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

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

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

1 голос
/ 01 июня 2015

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 голос
/ 20 апреля 2015

Вот полное решение на 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;

    }

}
0 голосов
/ 28 февраля 2018

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);
}

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

0 голосов
/ 08 июня 2017

PreparedStatement не предоставляет хорошего способа работы с предложением SQL IN. На http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Вы не можете заменить вещи, которые должны стать частью оператора SQL. Это необходимо, потому что, если сам SQL может измениться, драйвер не сможет предварительно скомпилировать оператор. Он также имеет хороший побочный эффект предотвращения атак 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 голосов
/ 01 февраля 2017

Мой обходной путь (JavaScript)

    var s1 = " SELECT "

 + "FROM   table t "

 + "  where t.field in ";

  var s3 = '(';

  for(var i =0;i<searchTerms.length;i++)
  {
    if(i+1 == searchTerms.length)
    {
     s3  = s3+'?)';
    }
    else
    {
        s3  = s3+'?, ' ;
    }
   }
    var query = s1+s3;

    var pstmt = connection.prepareStatement(query);

     for(var i =0;i<searchTerms.length;i++)
    {
        pstmt.setString(i+1, searchTerms[i]);
    }

SearchTerms - это массив, содержащий ваши входные данные / ключи / поля и т. Д.

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

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

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

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

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

...