369

INのインスタンスで SQL 句を使用するための最善の回避策は何ですかjava.sql.PreparedStatement。これは、SQL インジェクション攻撃のセキュリティ上の問題により、複数の値に対してサポートされていません: 1 つの?プレースホルダーは、値のリストではなく 1 つの値を表します。

次の SQL ステートメントを検討してください。

SELECT my_column FROM my_table where search_column IN (?)

Usingは、本質的には、そもそも preparedStatement.setString( 1, "'A', 'B', 'C'" );使用する理由の回避策として機能しない試みです。?

どのような回避策がありますか?

4

31 に答える 31

211

利用可能なさまざまなオプションの分析と、それぞれの長所と短所については、こちらを参照してください。

推奨されるオプションは次のとおりです。

  • 準備SELECT my_column FROM my_table WHERE search_column = ?し、値ごとに実行し、クライアント側で結果を UNION します。準備済みステートメントは 1 つだけ必要です。遅くて痛い。
  • 準備SELECT my_column FROM my_table WHERE search_column IN (?,?,?)して実行します。size-of-IN-list ごとに 1 つの準備済みステートメントが必要です。迅速かつ明白。
  • 準備SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...して実行します。[またはUNION ALL、これらのセミコロンの代わりに使用します。--ed] size-of-IN-list ごとに 1 つの準備済みステートメントが必要です。ばかげて遅く、 よりも厳密に悪いのでWHERE search_column IN (?,?,?)、なぜブロガーがそれを提案したのかわかりません。
  • ストアド プロシージャを使用して結果セットを作成します。
  • N 個の異なるサイズの IN リスト クエリを準備します。たとえば、2、10、および 50 の値があります。6 つの異なる値を持つ IN リストを検索するには、次のようにサイズ 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(
        String.join(", ", Collections.nCopies(possibleValue.size(), "?")));

    // 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

于 2008-10-09T22:13:14.000 に答える
20

私の知る限り、簡単な方法はありません。目標がステートメント キャッシュ率を高く維持すること (つまり、パラメーター カウントごとにステートメントを作成しないこと) である場合は、次のようにすることができます。

  1. いくつか (たとえば 10 個) のパラメーターを持つステートメントを作成します。

    ... どこに (?,?,?,?,?,?,?,?,?,?) ...

  2. すべての実パラメータをバインド

    setString(1,"foo"); setString(2,"バー");

  3. 残りを NULL としてバインドします

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

NULL は何にも一致しないため、SQL プラン ビルダーによって最適化されます。

List を DAO 関数に渡すと、ロジックは簡単に自動化できます。

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

while( i < MAX_PARAMS ) {
  ps.setNull(i+1,Types.VARCHAR);
  i++;
}
于 2008-10-09T21:52:49.640 に答える
11

不愉快な回避策ですが、確かに実現可能な方法は、ネストされたクエリを使用することです。列を含む一時テーブル MYVALUES を作成します。値のリストを MYVALUES テーブルに挿入します。次に実行

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

見苦しいですが、値のリストが非常に大きい場合は実行可能な代替手段です。

この手法には、オプティマイザからの潜在的に優れたクエリ プランという追加の利点があります (複数の値についてページをチェックする、値ごとに 1 回ではなくテーブルスキャンを 1 回のみにするなど)。データベースが準備済みステートメントをキャッシュしない場合、オーバーヘッドを節約できます。「INSERTS」はバッチで実行する必要があり、MYVALUES テーブルを微調整して、最小限のロックまたはその他の高オーバーヘッド保護を行う必要がある場合があります。

于 2008-10-07T23:49:31.420 に答える
9

in() 演算子の制限はすべての悪の根源です。

些細なケースで機能し、「準備されたステートメントの自動生成」で拡張できますが、常に制限があります。

  • 可変数のパラメーターを持つステートメントを作成している場合、呼び出しごとに sql 解析のオーバーヘッドが発生します
  • 多くのプラットフォームでは、in() 演算子のパラメーターの数が制限されています
  • すべてのプラットフォームで、SQL テキストの合計サイズが制限されているため、in パラメーターの 2000 個のプレースホルダーを送信することは不可能です。
  • JDBC ドライバーには制限があるため、1000 ~ 10k のバインド変数を送信することはできません。

in() アプローチは、場合によっては十分ですが、ロケットプルーフではありません:)

ロケットプルーフのソリューションは、別の呼び出しで任意の数のパラメーターを渡し (たとえば、params の塊を渡すことにより)、ビュー (またはその他の方法) を使用してそれらを SQL で表し、where で使用することです。基準。

ブルートフォースの変種はこちら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;

次に、パラメーターで任意の数のコンマ区切りの顧客 ID を渡すことができます。

  • select の SQL は安定しているため、解析の遅延は発生しません。
  • パイプライン化された関数の複雑さはありません - クエリは 1 つだけです
  • SQL は、IN 演算子の代わりに単純な結合を使用しています。これは非常に高速です。
  • 結局のところ、単純な select または DML でデータベースにアクセスしないのは経験則として適切です。なぜなら、MySQL や同様の単純なデータベース エンジンよりも何光年も多くを提供するのは Oracle だからです。PL/SQL を使用すると、効果的な方法でストレージ モデルをアプリケーション ドメイン モデルから隠すことができます。

ここでのトリックは次のとおりです。

  • 長い文字列を受け入れる呼び出しが必要であり、db セッションがアクセスできる場所に保存します (単純なパッケージ変数、または dbms_session.set_context など)。
  • 次に、これを行に解析できるビューが必要です
  • そして、クエリしているIDを含むビューがあるので、必要なのは、クエリされたテーブルへの単純な結合だけです。

ビューは次のようになります。

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 を渡し、それをビュー内で変換するのが最も効率的なソリューションです。

于 2016-02-17T14:44:18.823 に答える
7

これが私が自分のアプリケーションでそれを解決した方法です。理想的には、文字列に + を使用する代わりに、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 のような変数を使用すると、非常に役立ちます。

于 2016-04-08T05:06:57.013 に答える
5

私の回避策は次のとおりです。

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;
/

これで、1つの変数を使用して、テーブル内のいくつかの値を取得できます。

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(?)))"

よろしく、

ハビエル・イバネス

于 2011-02-24T12:44:47.207 に答える
5

試したことはありませんが、 .setArray() はあなたが探していることをしますか?

更新:明らかにそうではありません。setArray は、以前のクエリから取得した ARRAY 列、または ARRAY 列を含むサブクエリに由来する java.sql.Array でのみ機能するようです。

于 2008-10-07T13:45:29.267 に答える
3

(基本的な文字列操作を使用して)リスト内のアイテムの数と一致する数の PreparedStatementクエリ文字列をで生成できると思います。?

もちろん、それを実行している場合はOR、クエリで巨大なチェーンを生成することから一歩離れていますが、クエリ文字列に適切な数が?含まれていないと、他にこれを回避する方法がわかりません。

于 2008-10-07T13:47:36.063 に答える
2

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;

    }

}
于 2015-04-20T04:46:38.823 に答える
2

Spring では、引数の数に応じて (?, ?, ?, ..., ?) の生成を自動化する NamedParameterJdbcTemplate に java.util.Lists を渡すことができます。

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ステートメントで使用可能な値のような表に変換します。

于 2015-06-01T12:18:02.207 に答える
1

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;
}
于 2009-12-16T12:42:48.790 に答える
1

使用する代わりに

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 ステートメントがコンパイルされてデータベース サーバーに格納されるため、これが最適なソリューションです。

于 2013-08-24T19:29:46.653 に答える
1

準備済みステートメントに関連する多くの制限に遭遇しました。

  1. 準備されたステートメントは同じセッション (Postgres) 内でのみキャッシュされるため、実際には接続プーリングでのみ機能します。
  2. @BalusC によって提案されたさまざまな準備済みステートメントが多数あると、キャッシュがいっぱいになり、以前にキャッシュされたステートメントが削除される可能性があります
  3. クエリを最適化し、インデックスを使用する必要があります。明らかなように聞こえますが、たとえば、@Boris が上位の回答の 1 つで提案した ANY(ARRAY...) ステートメントは、インデックスを使用できず、キャッシュにもかかわらずクエリが遅くなります。
  4. 準備されたステートメントはクエリ プランもキャッシュし、ステートメントで指定されたパラメーターの実際の値は使用できません。

提案された解決策の中から、クエリのパフォーマンスが低下せず、クエリの数が少ないものを選択します。これは、@Don リンクからの #4 (いくつかのクエリのバッチ処理) になるか、不要な「?」に NULL 値を指定します。@Vladimir Dyuzhevによって提案されたマーク

于 2013-09-12T07:21:19.157 に答える
1

instr 関数を使用してみてください。

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

それから

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

確かに、これは少し汚いハックですが、SQL インジェクションの機会を減らします。とにかくオラクルで動作します。

于 2008-10-07T14:13:09.070 に答える
1

Sormulaは、java.util.Collection オブジェクトをパラメーターとして指定できるようにすることで、SQL IN 演算子をサポートします。? を使用して準備済みステートメントを作成します。コレクションの要素ごとに。例 4を参照してください(例の SQL は、何が作成され、Sormula によって使用されないかを明確にするためのコメントです)。

于 2011-11-21T19:30:54.997 に答える
0

アダムの考えに従う。プリペアドステートメントをmy_tableからselectmy_columnのように作成します。ここで、(#)のsearch_columnは文字列xを作成し、「?、?、?」の数字を入力します。値のリストに応じて、新しい文字列xのクエリの#を変更するだけです。

于 2008-10-07T15:49:34.023 に答える
0

さまざまなフォーラムでさまざまな解決策を調べましたが、良い解決策が見つかりませんでした。

例: 'IN' 句で渡すパラメータが複数あるとします。「IN」句の中にダミー文字列を入れるだけです。たとえば、「PARAM」は、このダミー文字列の代わりに来るパラメーターのリストを示します。

    select * from TABLE_A where ATTR IN (PARAM);

Java コードですべてのパラメーターを 1 つの String 変数にまとめることができます。これは次のように行うことができます。

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

この場合、コンマで区切られたすべてのパラメーターを単一の文字列変数「param1」に追加できます。

すべてのパラメータを 1 つの文字列に集めた後、クエリのダミー テキスト (この場合は「PARAM」) をパラメータ文字列 (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」という単語の代わりに、特殊文字とアルファベットの組み合わせを使用して、そのような単語がクエリに含まれないようにすることができます。あなたが解決策を得たことを願っています。

注: これは準備されたクエリではありませんが、コードに実行させたい作業を実行します。

于 2015-03-15T14:56:44.103 に答える
0

完全を期すために、他の誰かがそれを提案しているのを見なかったので:

上記の複雑な提案を実装する前に、実際に SQL インジェクションが問題になるかどうかを検討してください。

多くの場合、IN (...) に提供される値は、注入が不可能であることを確認できる方法で生成された ID のリストです... (たとえば、前の select some_id from some_table where の結果some_condition.)

その場合は、この値を連結するだけで、サービスや準備済みステートメントを使用したり、このクエリの他のパラメーターに使用したりすることはできません。

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";
于 2015-04-01T18:12:37.637 に答える
0

完全を期すために:値のセットが大きすぎない限り、次のようなステートメントを単純に文字列で構築することもできます

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

これを prepare() に渡し、ループで setXXX() を使用してすべての値を設定できます。これは厄介に見えますが、多くの「大きな」商用システムは、Oracle のステートメントで 32 KB (そうだと思います) などの DB 固有の制限に達するまで、この種のことを日常的に行っています。

もちろん、セットが不当に大きくならないようにするか、そうである場合にエラー トラップを実行する必要があります。

于 2008-10-07T14:15:46.303 に答える
0

状況によっては、regexp が役立つ場合があります。これは私がOracleでチェックした例で、うまくいきます。

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

しかし、それにはいくつかの欠点があります。

  1. 適用された列は、少なくとも暗黙的に varchar/char に変換する必要があります。
  2. 特殊文字には注意が必要です。
  3. パフォーマンスが低下する可能性があります-私の場合、INバージョンはインデックスと範囲のスキャンを使用し、REGEXPバージョンはフルスキャンを行います.
于 2015-01-02T06:23:42.467 に答える
0

PreparedStatement の IN 句に使用できるさまざまな代替アプローチがあります。

  1. 単一クエリの使用 - パフォーマンスが最も遅く、リソースを集中的に使用する
  2. StoredProcedure の使用 - 最速だがデータベース固有
  3. PreparedStatement の動的クエリの作成 - パフォーマンスは良好ですが、キャッシュのメリットが得られず、PreparedStatement が毎回再コンパイルされます。
  4. PreparedStatement クエリで NULL を使用する - 最適なパフォーマンス。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);
        }
    

これらの代替アプローチの詳細については、こちらで確認できます。

于 2014-01-26T09:32:03.250 に答える
-3

私の回避策 (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入力/キー/フィールドなどを含む配列です

于 2017-02-01T19:28:25.117 に答える