it-swarm.com.de

PreparedStatement IN-Klauselalternativen?

Was sind die besten Problemumgehungen für die Verwendung einer IN-Klausel von SQL mit Instanzen von Java.sql.PreparedStatement, die aufgrund von SQL-Injection-Angriffssicherheitsproblemen nicht für mehrere Werte unterstützt wird: Ein ?-Platzhalter repräsentiert einen Wert und keine Liste von Werten.

Betrachten Sie die folgende SQL-Anweisung:

SELECT my_column FROM my_table where search_column IN (?)

Die Verwendung von preparedStatement.setString( 1, "'A', 'B', 'C'" ); ist im Wesentlichen ein Versuch, die Ursachen für die Verwendung von ? zu umgehen. 

Welche Problemumgehungen gibt es?

313
Chris Mazzola

Eine Analyse der verschiedenen verfügbaren Optionen sowie der Vor- und Nachteile der einzelnen Optionen ist verfügbar hier .

Die vorgeschlagenen Optionen sind:

  • Bereiten Sie SELECT my_column FROM my_table WHERE search_column = ? vor, führen Sie ihn für jeden Wert aus und UNION die Ergebnisse auf der Clientseite. Benötigt nur eine vorbereitete Anweisung. Langsam und schmerzhaft.
  • Bereiten Sie SELECT my_column FROM my_table WHERE search_column IN (?,?,?) vor und führen Sie es aus. Benötigt eine vorbereitete Anweisung für die Größe der IN-Liste. Schnell und offensichtlich.
  • Bereiten Sie SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... vor und führen Sie es aus. [Oder verwenden Sie UNION ALL anstelle dieser Semikolons. --ed] Erfordert eine vorbereitete Anweisung pro IN-Listengröße. Dumm langsam, streng schlechter als WHERE search_column IN (?,?,?), daher weiß ich nicht, warum der Blogger es überhaupt vorgeschlagen hat.
  • Verwenden Sie eine gespeicherte Prozedur, um die Ergebnismenge zu erstellen.
  • Bereiten Sie N verschiedene Listengrößenabfragen vor. sagen wir mit 2, 10 und 50 Werten. Um nach einer IN-Liste mit 6 verschiedenen Werten zu suchen, füllen Sie die Abfrage Größe 10 so aus, dass sie wie SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6) aussieht. Jeder anständige Server optimiert die doppelten Werte, bevor die Abfrage ausgeführt wird.

Keine dieser Optionen ist jedoch super.

Doppelte Fragen wurden an diesen Orten mit ebenso vernünftigen Alternativen beantwortet, aber keine davon war wirklich großartig:

Die richtige Antwort, wenn Sie JDBC4 und einen Server verwenden, der x = ANY(y) unterstützt, ist PreparedStatement.setArray wie hier beschrieben:

Es scheint jedoch keine Möglichkeit zu geben, setArray mit IN-Listen zu arbeiten.


Manchmal werden SQL-Anweisungen zur Laufzeit geladen (z. B. aus einer Eigenschaftendatei), erfordern jedoch eine variable Anzahl von Parametern. In solchen Fällen definieren Sie zuerst die Abfrage:

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

Laden Sie anschließend die Abfrage. Bestimmen Sie dann die Anzahl der Parameter, bevor Sie sie ausführen. Sobald die Parameteranzahl bekannt ist, führen Sie Folgendes aus:

sql = any( sql, count );

Zum Beispiel:

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

Bei bestimmten Datenbanken, bei denen die Weitergabe eines Arrays über die JDBC 4-Spezifikation nicht unterstützt wird, kann diese Methode die Umwandlung des langsamen = ? in die Bedingung der schnelleren IN (?)-Klausel erleichtern, die dann durch Aufruf der any-Methode erweitert werden kann.

174
Dónal

Lösung für 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();
}

oder

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

Kein einfacher Weg AFAIK . Wenn das Ziel das Cache-Verhältnis für Anweisungen hoch halten soll (d. H. Keine Anweisung für jede Parameteranzahl erstellen), können Sie Folgendes tun:

  1. erstellen Sie eine Anweisung mit einigen (z. B. 10) Parametern:

    ... WO EIN IN (?,?,?,?,?,?,?,?,?,?) ...

  2. Binden Sie alle aktuellen Parameter

    setString (1, "foo"); setString (2, "bar");

  3. Binden Sie den Rest als NULL

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

NULL stimmt nie mit irgendetwas überein und wird daher vom SQL-Planersteller optimiert.

Die Logik lässt sich leicht automatisieren, wenn Sie eine Liste in eine DAO-Funktion übergeben:

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

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

Eine unangenehme Lösung, aber durchaus machbar, ist die Verwendung einer verschachtelten Abfrage. Erstellen Sie eine temporäre Tabelle MYVALUES mit einer Spalte darin. Fügen Sie Ihre Werteliste in die Tabelle MYVALUES ein. Dann ausführen 

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

Hässlich, aber eine brauchbare Alternative, wenn Ihre Werteliste sehr groß ist.

Diese Technik hat den zusätzlichen Vorteil, dass möglicherweise bessere Abfragepläne vom Optimierungsprogramm bereitgestellt werden (prüfen Sie eine Seite auf mehrere Werte, Tabellen können nur einmal stattdessen einmal pro Wert abrufen, usw.). Dies kann zu zusätzlichen Kosten führen, wenn Ihre Datenbank vorbereitete Anweisungen nicht zwischenspeichert. Ihre "INSERTS" müssten im Batch ausgeführt werden, und die MYVALUES-Tabelle muss möglicherweise angepasst werden, um ein Minimum an Sperren oder andere Schutzvorrichtungen zu gewährleisten.

10
James Schek

Einschränkungen des in () - Operators sind die Wurzel allen Übels.

Es funktioniert für triviale Fälle, und Sie können es mit "automatischer Generierung der vorbereiteten Anweisung" erweitern, es hat jedoch immer seine Grenzen.

  • wenn Sie eine Anweisung mit einer variablen Anzahl von Parametern erstellen, führt dies bei jedem Aufruf zu einem SQL-Analyse-Overhead
  • auf vielen Plattformen ist die Anzahl der Parameter des in () - Operators begrenzt 
  • auf allen Plattformen ist die Gesamtgröße von SQL-Text begrenzt, was das Senden von 2000 Platzhaltern für die in-Parameter unmöglich macht
  • das Senden von Bindungsvariablen von 1000-10k ist nicht möglich, da der JDBC-Treiber Einschränkungen hat

Die in () - Methode kann für einige Fälle gut genug sein, aber nicht raketenfest :)

Die raketenfeste Lösung besteht darin, die willkürliche Anzahl von Parametern in einem separaten Aufruf zu übergeben (z. B. durch ein Clob von Params) und dann eine Ansicht (oder eine andere Möglichkeit) zu haben, um sie in SQL darzustellen und in Ihrem Where zu verwenden Kriterien.

Eine Brute-Force-Variante ist hier http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

Wenn Sie jedoch PL/SQL verwenden können, kann dieses Durcheinander ziemlich ordentlich werden.

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;

Dann können Sie im Parameter eine beliebige Anzahl von durch Kommas getrennten Kundennummern übergeben.

  • wird keine Analyseverzögerung erhalten, da SQL für select stabil ist
  • keine Komplexität der Pipeline-Funktionen - es ist nur eine Abfrage
  • sQL verwendet einen einfachen Join anstelle eines IN-Operators, was ziemlich schnell ist
  • schließlich ist es eine gute Faustregel, dass not die Datenbank mit einer einfachen Auswahl oder DML schlägt, da es Oracle ist, das Lichtjahre von mehr als MySQL oder ähnlichen einfachen Datenbank-Engines bietet. Mit PL/SQL können Sie das Speichermodell effektiv von Ihrem Anwendungsdomänenmodell ausblenden.

Der Trick hier ist:

  • wir brauchen einen Aufruf, der den langen String akzeptiert, und irgendwo speichern, wo die Datenbanksitzung darauf zugreifen kann (z. B. einfache Paketvariable oder dbms_session.set_context).
  • dann brauchen wir eine Sicht, die dies in Zeilen parsen kann
  • und dann haben Sie eine Ansicht, die die abgefragten IDs enthält. Alles, was Sie brauchen, ist ein einfacher Join zur abgefragten Tabelle.

Die Ansicht sieht wie folgt aus:

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

dabei bezieht sich aux_in_list.getpayload auf die ursprüngliche Eingabezeichenfolge.


Ein möglicher Ansatz wäre die Übergabe von pl/sql-Arrays (nur von Oracle unterstützt). Sie können diese jedoch nicht in reinem SQL verwenden. Daher ist immer ein Konvertierungsschritt erforderlich. Die Konvertierung kann nicht in SQL ausgeführt werden. Daher ist das Übergeben eines Clobs mit allen Parametern in einer Zeichenfolge und das Konvertieren in eine Ansicht die effizienteste Lösung.

8
Gee Bee

Ich habe es noch nie probiert, würde aber .setArray () machen, wonach Sie suchen?

Update : Offensichtlich nicht. setArray scheint nur mit einer Java.sql.Array zu funktionieren, die aus einer ARRAY-Spalte stammt, die Sie aus einer vorherigen Abfrage abgerufen haben, oder einer Unterabfrage mit einer ARRAY-Spalte.

5
Paul Tomblin

So habe ich es in meiner eigenen Anwendung gelöst. Im Idealfall sollten Sie einen StringBuilder anstelle von + für Strings verwenden.

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

Die Verwendung einer Variablen wie x anstelle von konkreten Zahlen ist sehr hilfreich, wenn Sie die Abfrage später ändern möchten.

5
m.sabouri

Mein Workaround ist:

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

Jetzt können Sie eine Variable verwenden, um einige Werte in einer Tabelle abzurufen:

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

Die vorbereitete Aussage könnte also lauten:

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

Grüße,

Javier Ibanez

5
Javier Ibanez

Ich vermute, Sie könnten (mit Hilfe der einfachen Zeichenfolgenmanipulation) die Abfragezeichenfolge in der Variablen PreparedStatement generieren, um eine Anzahl von ?s zu erhalten, die der Anzahl der Elemente in Ihrer Liste entspricht. 

Wenn Sie dies tun, sind Sie natürlich nur einen Schritt davon entfernt, eine riesige verkettete OR in Ihrer Abfrage zu generieren, aber ohne die richtige Anzahl von ? in der Abfragezeichenfolge zu haben, sehe ich nicht, wie Sie dies umgehen können .

3
Adam Bellaire

Sie können die setArray-Methode wie in this javadoc erwähnt verwenden:

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 erlaubt , Java.util.Lists an NamedParameterJdbcTemplate zu übergeben, wodurch die Generierung von (?,?,?, ...,?) Entsprechend der Anzahl der Argumente automatisiert wird.

Für Oracle beschreibt dieses Blogposting die Verwendung von Oracle.sql.ARRAY (Connection.createArrayOf funktioniert nicht mit Oracle). Dazu müssen Sie Ihre SQL-Anweisung ändern:

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

Die Oracle-Tabellenfunktion wandelt das übergebene Array in einen tabellenartigen Wert um, der in der IN-Anweisung verwendet werden kann.

1

Ich bin auf eine Reihe von Einschränkungen gestoßen, die mit der vorbereiteten Aussage zusammenhängen:

  1. Die vorbereiteten Anweisungen werden nur in derselben Sitzung (Postgres) zwischengespeichert, sodass sie wirklich nur mit Verbindungspooling funktioniert
  2. Eine Vielzahl von verschiedenen vorbereiteten Anweisungen, wie von @BalusC vorgeschlagen, kann dazu führen, dass der Cache überfüllt wird und zuvor zwischengespeicherte Anweisungen gelöscht werden
  3. Die Abfrage muss optimiert werden und Indizes verwenden. Klingt offensichtlich, jedoch z. Die von @Boris in einer der Top-Antworten vorgeschlagene ANY (ARRAY ...) -Anweisung kann keine Indizes verwenden, und die Abfrage wird trotz Zwischenspeicherung langsam
  4. Die vorbereitete Anweisung speichert auch den Abfrageplan zwischen, und die tatsächlichen Werte aller in der Anweisung angegebenen Parameter sind nicht verfügbar.

Unter den vorgeschlagenen Lösungen würde ich diejenige auswählen, die die Abfrageleistung nicht beeinträchtigt und die Anzahl der Abfragen verringert. Dies ist die # 4 (einige Abfragen zusammenstellen) aus dem @ Don-Link oder die Angabe von NULL-Werten für nicht benötigte '?' Marken, wie von @Vladimir Dyuzhev vorgeschlagen 

1
Alexander

anstatt zu verwenden

SELECT my_column FROM my_table where search_column IN (?)

verwenden Sie die SQL-Anweisung als 

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

und

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

oder verwenden Sie eine gespeicherte Prozedur. Dies ist die beste Lösung, da die SQL-Anweisungen kompiliert und im DataBase-Server gespeichert werden

1
kapil das

Sormula unterstützt den SQL IN-Operator, indem Sie ein Java.util.Collection-Objekt als Parameter angeben können. Es erstellt eine vorbereitete Anweisung mit einem? für jedes der elemente die sammlung. Siehe Beispiel 4 (SQL ist in diesem Beispiel ein Kommentar, um zu klären, was erstellt wird, aber nicht von Sormula verwendet wird).

1
Jeff Miller

versuchen Sie es mit der instr-Funktion?

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

dann

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

Dies ist zwar ein bisschen schmutzig, aber die Möglichkeiten für die SQL-Injektion werden reduziert. Funktioniert trotzdem in Oracle.

1
stjohnroe

Hier ist eine Komplettlösung in Java, um die vorbereitete Anweisung für Sie zu erstellen:

/*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 ist die beste Lösung, aber für viele ältere Treiber nicht verfügbar. Die folgende Problemumgehung kann in Java8 verwendet werden

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

Diese Lösung ist besser als andere hässliche Schleifenlösungen, bei denen die Abfragezeichenfolge durch manuelle Iterationen erstellt wird 

0
Raheel

Sie können Collections.nCopies verwenden, um eine Sammlung von Platzhaltern zu generieren und diese mit String.join zu verbinden:

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

Generieren Sie die Abfragezeichenfolge in PreparedStatement, damit die Anzahl der Elemente mit der Anzahl der Elemente in Ihrer Liste übereinstimmt. Hier ist ein Beispiel:

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 bietet keine gute Möglichkeit, mit der SQL-IN-Klausel umzugehen. Per http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 "Sie können keine Dinge ersetzen, die Teil der SQL-Anweisung werden sollen. Dies ist notwendig, wenn die SQL-Anweisung selbst vorhanden ist Wenn sich der Treiber ändern kann, kann er die Anweisung nicht vorkompilieren. Außerdem hat dies den Nebeneffekt von Nice, SQL-Injektionsangriffe zu verhindern. " Am Ende habe ich folgenden Ansatz verwendet:

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

Nur der Vollständigkeit halber und weil ich niemanden anders gesehen habe:

Bevor Sie einen der oben genannten komplizierten Vorschläge implementieren, sollten Sie prüfen, ob die SQL-Injection tatsächlich ein Problem in Ihrem Szenario ist. 

In vielen Fällen handelt es sich bei dem für IN (...) bereitgestellten Wert um eine Liste von IDs, die auf eine Weise generiert wurden, dass Sie sicher sein können, dass keine Injektion möglich ist ... einige_bedingung.)

Wenn dies der Fall ist, können Sie diesen Wert nur verketten und nicht die Services oder die vorbereitete Anweisung dafür verwenden oder sie für andere Parameter dieser Abfrage verwenden. 

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

In einigen Situationen kann regexp helfen. Hier ist ein Beispiel, das ich auf Oracle überprüft habe, und es funktioniert. 

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

Es gibt jedoch eine Reihe von Nachteilen:

  1. Jede verwendete Spalte sollte zumindest implizit in varchar/char konvertiert werden.
  2. Seien Sie vorsichtig mit Sonderzeichen.
  3. Dies kann die Leistung verlangsamen - in meinem Fall verwendet die IN-Version den Index- und Bereichsscan, und die REGEXP-Version führt einen vollständigen Scan durch.
0
Vasili

Nachdem ich verschiedene Lösungen in verschiedenen Foren untersucht und keine gute Lösung gefunden hatte, habe ich das Gefühl, dass der folgende Hack, den ich mir ausgedacht habe, am einfachsten zu folgen ist und Folgendes kodiert:

Beispiel: Angenommen, Sie müssen mehrere Parameter in der 'IN'-Klausel übergeben. Fügen Sie einfach einen Dummy-String in die 'IN'-Klausel ein, sagen Sie, "PARAM" bezeichnet die Liste der Parameter, die an die Stelle dieses Dummy-Strings kommen.

    select * from TABLE_A where ATTR IN (PARAM);

Sie können alle Parameter in einer einzigen String-Variablen in Ihrem Java-Code sammeln. Dies kann wie folgt durchgeführt werden:

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

Sie können alle Ihre Parameter durch Kommas getrennt an eine einzige String-Variable 'param1' anhängen.

Nachdem Sie alle Parameter in einem einzigen String gesammelt haben, können Sie einfach den Dummy-Text in Ihrer Abfrage, d. H. "PARAM", in diesem Fall durch den Parameter String, d. H. Param1, ersetzen. Folgendes müssen Sie tun:

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

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

Sie können Ihre Abfrage jetzt mit der Methode executeQuery () ausführen. Stellen Sie nur sicher, dass das Wort "PARAM" nirgendwo in Ihrer Abfrage enthalten ist. Sie können anstelle von Word "PARAM" eine Kombination aus Sonderzeichen und Alphabeten verwenden, um sicherzustellen, dass keine Möglichkeit besteht, dass ein solches Word in der Abfrage angezeigt wird. Hoffe du hast die Lösung.

Anmerkung: Obwohl dies keine vorbereitete Abfrage ist, führt sie die Arbeit aus, die ich mit meinem Code ausführen wollte.

0
bnsk

Es gibt verschiedene alternative Ansätze, die wir für die IN-Klausel in PreparedStatement verwenden können.

  1. Einzelabfragen verwenden - langsamste Leistung und ressourcenintensiv
  2. StoredProcedure verwenden - Schnell, aber datenbankspezifisch
  3. Dynamische Abfrage für PreparedStatement erstellen - Gute Leistung, jedoch kein Vorteil der Zwischenspeicherung. PreparedStatement wird jedes Mal neu kompiliert.
  4. Verwenden Sie NULL in PreparedStatement-Abfragen - Optimale Leistung, funktioniert bestens, wenn Sie die Begrenzung der IN-Klauselargumente kennen. Wenn es keine Begrenzung gibt, können Sie Abfragen im Stapel ausführen. Beispielcode-Snippet ist;

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

Weitere Informationen zu diesen alternativen Ansätzen finden Sie hier hier .

0
Pankaj

Der Idee von Adam folgen. Machen Sie Ihre vorbereitete Anweisung so, dass Sie my_column aus my_table auswählen, wobei search_column in (#) Erstellen Sie einen String x und füllen Sie ihn mit einer Zahl von "?,?,?" Abhängig von Ihrer Werteliste Dann ändern Sie einfach das # in der Abfrage für Ihren neuen String x und füllen auf

0
Yorch

Ich habe gerade eine PostgreSQL-spezifische Option dafür ausgearbeitet. Es ist ein bisschen ein Hack und hat seine eigenen Vor- und Nachteile und Einschränkungen, aber es scheint zu funktionieren und ist nicht auf eine bestimmte Entwicklungssprache, Plattform oder einen PG-Treiber beschränkt.

Der Trick besteht natürlich darin, einen Weg zu finden, eine beliebig lange Sammlung von Werten als einen einzigen Parameter zu übergeben und die Datenbankeinheit als mehrere Werte erkennen zu lassen. Die Lösung, die ich arbeite, besteht darin, aus den Werten in der Auflistung einen begrenzten String zu erstellen, diesen String als einen einzelnen Parameter zu übergeben und string_to_array () mit dem erforderlichen Casting für PostgreSQL zu verwenden, um ihn ordnungsgemäß zu verwenden.

Wenn Sie also nach "foo", "blah" und "abc" suchen möchten, können Sie sie zu einer einzigen Zeichenfolge zusammenfassen: "foo, blah, abc". Hier ist die direkte SQL:

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

Sie würden natürlich die explizite Umwandlung in das ändern, in dem Sie das Ergebnis-Array haben wollen - int, text, uuid usw. Und weil die Funktion einen einzelnen Zeichenfolgewert (oder zwei, nehme ich an, wenn Sie das Trennzeichen anpassen möchten) Sie können es auch als Parameter in einer vorbereiteten Anweisung übergeben:

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

Dies ist sogar flexibel genug, um Dinge wie LIKE Vergleiche zu unterstützen:

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

Wiederum keine Frage, es ist ein Hack, aber es funktioniert und erlaubt Ihnen, noch vorkompilierte vorbereitete Anweisungen zu verwenden, die * hem * diskrete Parameter mit den begleitenden Sicherheits- und (möglicherweise) Leistungsvorteilen verwenden. Ist es ratsam und tatsächlich performant? Das hängt natürlich davon ab, dass Sie das String-Parsing und möglicherweise Casting ausführen, bevor Ihre Abfrage überhaupt ausgeführt wird. Wenn Sie erwarten, drei, fünf, ein paar Dutzend Werte zu senden, ist es wahrscheinlich in Ordnung. Ein paar tausend Ja, vielleicht nicht so sehr. Für YMMV gelten Einschränkungen und Ausschlüsse, keine ausdrückliche oder stillschweigende Garantie.

Aber es funktioniert.

0
Joel Fouse

Der Vollständigkeit halber: Solange die Menge der Werte nicht zu groß ist, können Sie () auch einfach eine Anweisung wie eine Zeichenfolge konstruieren

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

die Sie dann an prep () übergeben könnten, und dann setXXX () in einer Schleife verwenden, um alle Werte festzulegen. Das sieht zwar gut aus, aber viele "große" kommerzielle Systeme machen diese Art von Routine routinemäßig, bis sie an DB-spezifische Grenzen stoßen, wie zum Beispiel 32 KB (ich glaube, es ist) für Anweisungen in Oracle.

Natürlich müssen Sie sicherstellen, dass das Set niemals unangemessen groß ist, oder Sie sollten Fehler einfangen, falls dies der Fall ist.

0
Carl Smotricz