Was sind die besten Problemumgehungen für die Verwendung einer SQL- IN
Klausel mit Instanzen von java.sql.PreparedStatement
, die aufgrund von Sicherheitsproblemen bei SQL-Injection-Angriffen nicht für mehrere Werte unterstützt wird: Ein ?
Platzhalter steht für einen Wert und nicht für eine Liste von Werten.
Betrachten Sie die folgende SQL-Anweisung:
SELECT my_column FROM my_table where search_column IN (?)
Die Verwendung preparedStatement.setString( 1, "'A', 'B', 'C'" );
ist im Wesentlichen ein nicht funktionierender Versuch, die Gründe für ?
die erstmalige Verwendung zu umgehen.
Welche Problemumgehungen sind verfügbar?
Antworten:
Eine Analyse der verschiedenen verfügbaren Optionen sowie der Vor- und Nachteile der einzelnen Optionen finden Sie hier .
Die vorgeschlagenen Optionen sind:
SELECT my_column FROM my_table WHERE search_column = ?
, führen Sie es für jeden Wert aus und UNION die Ergebnisse clientseitig. Benötigt nur eine vorbereitete Anweisung. Langsam und schmerzhaft.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
und führen Sie es aus. Erfordert eine vorbereitete Anweisung pro Größe der IN-Liste. Schnell und offensichtlich.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
und führen Sie es aus. [OderUNION ALL
anstelle dieser Semikolons verwenden. --ed] Erfordert eine vorbereitete Anweisung pro Größe der IN-Liste. Dumm langsam, streng schlimmer alsWHERE search_column IN (?,?,?)
, also weiß ich nicht, warum der Blogger es überhaupt vorgeschlagen hat.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Jeder anständige Server optimiert die doppelten Werte, bevor die Abfrage ausgeführt wird.Keine dieser Optionen ist jedoch super toll.
An diesen Stellen wurden doppelte Fragen mit ebenso vernünftigen Alternativen beantwortet, von denen immer noch keine besonders gut ist:
Die richtige Antwort, wenn Sie JDBC4 und einen Server verwenden, der dies unterstützt
x = ANY(y)
, lautet wie folgtPreparedStatement.setArray
: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:
Laden Sie als Nächstes die Abfrage. Bestimmen Sie dann die Anzahl der Parameter, bevor Sie sie ausführen. Sobald die Parameteranzahl bekannt ist, führen Sie Folgendes aus:
Zum Beispiel:
Bei bestimmten Datenbanken, bei denen die Übergabe eines Arrays über die JDBC 4-Spezifikation nicht unterstützt wird, kann diese Methode die Umwandlung der langsamen
= ?
in die schnellereIN (?)
Klauselbedingung erleichtern , die dann durch Aufrufen derany
Methode erweitert werden kann.quelle
Lösung für PostgreSQL:
oder
quelle
.createArrayOf()
Teils mehr JDBC4-spezifisch als PostgreSQL-spezifisch, aber ich bin nicht sicher, ob die strenge Semantik für BenutzerArray
durch die JDBC-Spezifikation definiert ist..createArrayOf
nicht funktioniert, können Sie Ihre eigene manuelle Erstellung von Arrayliteral wie tunString arrayLiteral = "{A,\"B \", C,D}"
(beachten Sie, dass „B“ einen Raum hat , während C nicht) und dann ,statement.setString(1,arrayLiteral)
wenn die vorbereitete Anweisung ist... IN (SELECT UNNEST(?::VARCHAR[]))
oder... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: Ich glaube nicht, dass esANY
mit a funktioniertSELECT
.)Kein einfacher Weg AFAIK. Wenn das Ziel darin besteht, das Verhältnis des Anweisungscaches hoch zu halten (dh nicht für jede Parameteranzahl eine Anweisung zu erstellen), können Sie Folgendes tun:
Erstellen Sie eine Anweisung mit einigen (z. B. 10) Parametern:
... WO EIN IN (?,?,?,?,?,?,?,?,?,?) ...
Binden Sie alle aktuellen Parameter
setString (1, "foo"); setString (2, "bar");
Binden Sie den Rest als NULL
setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)
NULL stimmt nie mit etwas überein, daher wird es vom SQL Plan Builder optimiert.
Die Logik ist einfach zu automatisieren, wenn Sie eine Liste an eine DAO-Funktion übergeben:
quelle
NULL
die Abfrage mit einemNULL
Wert in der Datenbank übereinstimmen ?NOT IN
undIN
behandeln Sie Nullen nicht auf die gleiche Weise. Führen Sie dies aus und sehen Sie, was passiert:select 'Matched' as did_it_match where 1 not in (5, null);
Entfernen Sie dann dienull
und beobachten Sie die Magie.a IN (1,2,3,3,3,3,3)
ist das gleiche wiea IN (1,2,3)
. Es funktioniert auch mit ImNOT IN
Gegensatza NOT IN (1,2,3,null,null,null,null)
(was immer keine Zeilen zurückgibt, daany_value != NULL
es immer falsch ist).Eine unangenehme Umgehung, 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
Hässlich, aber eine praktikable Alternative, wenn Ihre Werteliste sehr groß ist.
Diese Technik hat den zusätzlichen Vorteil, dass potenziell bessere Abfragepläne vom Optimierer (eine Seite auf mehrere Werte prüfen, Tabellen nur einmal statt einmal pro Wert usw. scannen) Overhead sparen können, wenn Ihre Datenbank vorbereitete Anweisungen nicht zwischenspeichert. Ihre "INSERTS" müssten im Batch ausgeführt werden, und die MYVALUES-Tabelle muss möglicherweise optimiert werden, um minimale Sperren oder andere Schutzmaßnahmen mit hohem Overhead zu erzielen.
quelle
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.
Der in () -Ansatz kann in einigen Fällen gut genug sein, ist aber nicht raketenfest :)
Die raketenfeste Lösung besteht darin, die beliebige Anzahl von Parametern in einem separaten Aufruf zu übergeben (z. B. durch Übergeben eines Parameterclobs) 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 finden Sie 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.
Anschließend können Sie eine beliebige Anzahl von durch Kommas getrennten Kunden-IDs im Parameter übergeben und:
Der Trick hier ist:
Die Ansicht sieht aus wie:
Dabei bezieht sich aux_in_list.getpayload auf die ursprüngliche Eingabezeichenfolge.
Ein möglicher Ansatz wäre das Übergeben 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 durchgeführt werden. Daher ist es die effizienteste Lösung, einen Clob mit allen Parametern in der Zeichenfolge zu übergeben und in eine Ansicht zu konvertieren.
quelle
So habe ich es in meiner eigenen Anwendung gelöst. Idealerweise sollten Sie einen StringBuilder verwenden, anstatt + für Strings zu verwenden.
Die Verwendung einer Variablen wie x oben anstelle konkreter Zahlen hilft sehr, wenn Sie die Abfrage zu einem späteren Zeitpunkt ändern möchten.
quelle
Ich habe es noch nie versucht, aber würde .setArray () das tun, wonach Sie suchen?
Update : Offensichtlich nicht. setArray scheint nur mit einem java.sql.Array zu funktionieren, das aus einer ARRAY-Spalte stammt, die Sie aus einer vorherigen Abfrage abgerufen haben, oder mit einer Unterabfrage mit einer ARRAY-Spalte.
quelle
Meine Problemumgehung ist:
Jetzt können Sie eine Variable verwenden, um einige Werte in einer Tabelle zu erhalten:
Die vorbereitete Aussage könnte also sein:
Grüße,
Javier Ibanez
quelle
Ich nehme an, Sie könnten (mithilfe der grundlegenden Zeichenfolgenmanipulation) die Abfragezeichenfolge in generieren
PreparedStatement
, um eine?
Anzahl von Elementen zu erhalten, die mit der Anzahl der Elemente in Ihrer Liste übereinstimmt.Wenn Sie dies tun, sind Sie natürlich nur einen Schritt davon entfernt, einen
OR
in Ihrer Abfrage verketteten Riesen zu generieren , aber ohne die richtige Anzahl von?
in der Abfragezeichenfolge zu haben, sehe ich nicht, wie Sie dies sonst umgehen können.quelle
Sie können die setArray-Methode wie in diesem Javadoc erwähnt verwenden :
quelle
Sie können
Collections.nCopies
eine Sammlung von Platzhaltern generieren und diese verbinden, indem SieString.join
:quelle
Hier ist eine vollständige Lösung in Java, um die vorbereitete Anweisung für Sie zu erstellen:
quelle
Mit Spring können Sie java.util.Lists an NamedParameterJdbcTemplate übergeben , wodurch die Generierung von (?,?,?, ...,?) nach Anzahl der Argumente automatisiert wird.
In diesem Blogbeitrag für Oracle wird die Verwendung von oracle.sql.ARRAY erläutert (Connection.createArrayOf funktioniert nicht mit Oracle). Dazu müssen Sie Ihre SQL-Anweisung ändern:
Die Oracle-Tabellenfunktion wandelt das übergebene Array in einen tabellenähnlichen Wert um, der in der
IN
Anweisung verwendet werden kann.quelle
versuchen Sie es mit der Instr-Funktion?
dann
Zugegeben, dies ist ein schmutziger Hack, aber es verringert die Möglichkeiten für die SQL-Injektion. Funktioniert sowieso im Orakel.
quelle
Sormula unterstützt den SQL IN-Operator, indem Sie ein java.util.Collection-Objekt als Parameter angeben können . Es erstellt eine vorbereitete Aussage mit einem? für jedes der Elemente die Sammlung. Siehe Beispiel 4 (SQL im Beispiel ist ein Kommentar, um zu verdeutlichen, was erstellt, aber nicht von Sormula verwendet wird).
quelle
anstatt zu verwenden
Verwenden Sie die SQL-Anweisung als
und
oder verwenden Sie eine gespeicherte Prozedur. Dies wäre die beste Lösung, da die SQL-Anweisungen kompiliert und auf dem DataBase-Server gespeichert werden
quelle
Ich bin auf eine Reihe von Einschränkungen im Zusammenhang mit vorbereiteten Aussagen gestoßen:
Unter den vorgeschlagenen Lösungen würde ich die auswählen, die die Abfrageleistung nicht verringert und die Anzahl der Abfragen verringert. Dies ist die Nummer 4 (Stapel von wenigen Abfragen) über den @ Don-Link oder die Angabe von NULL-Werten für nicht benötigtes '?' Noten wie von @Vladimir Dyuzhev vorgeschlagen
quelle
Ich habe gerade eine PostgreSQL-spezifische Option dafür ausgearbeitet. Es ist ein bisschen wie 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 bestimmten PG-Treiber beschränkt.
Der Trick besteht natürlich darin, einen Weg zu finden, eine Sammlung von Werten beliebiger Länge als einen einzelnen Parameter zu übergeben und die Datenbank als mehrere Werte erkennen zu lassen. Die Lösung, an der ich arbeite, besteht darin, aus den Werten in der Auflistung eine begrenzte Zeichenfolge zu erstellen, diese Zeichenfolge als einzelnen Parameter zu übergeben und string_to_array () mit dem erforderlichen Casting für PostgreSQL zu verwenden, um sie 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 das reine SQL:
Sie würden natürlich die explizite Umwandlung in das ändern, was Ihr resultierendes Wertearray sein soll - int, text, uuid usw. Und weil die Funktion einen einzelnen Zeichenfolgenwert annimmt (oder zwei, nehme ich an, wenn Sie das Trennzeichen anpassen möchten Sie können es auch als Parameter in einer vorbereiteten Anweisung übergeben:
Dies ist sogar flexibel genug, um Dinge wie LIKE-Vergleiche zu unterstützen:
Keine Frage, es ist ein Hack, aber es funktioniert und ermöglicht es Ihnen, vorkompilierte vorbereitete Anweisungen zu verwenden, die * ahem * diskrete Parameter mit den damit verbundenen Sicherheits- und (möglicherweise) Leistungsvorteilen verwenden. Ist es ratsam und tatsächlich performant? Dies hängt natürlich davon ab, ob Sie das Parsen von Zeichenfolgen und möglicherweise das Casting durchführen, bevor Ihre Abfrage überhaupt ausgeführt wird. Wenn Sie damit rechnen, drei, fünf, ein paar Dutzend Werte zu senden, ist das wahrscheinlich in Ordnung. Ein paar tausend? Ja, vielleicht nicht so sehr. YMMV, Einschränkungen und Ausschlüsse gelten, keine ausdrückliche oder stillschweigende Garantie.
Aber es funktioniert.
quelle
Nur der Vollständigkeit halber: Solange die Menge der Werte nicht zu groß ist, man könnte auch einfach String-Konstrukt eine Anweisung wie
Diese können Sie dann übergeben, um prepare () zu erstellen, und dann setXXX () in einer Schleife verwenden, um alle Werte festzulegen. Das sieht gut aus, aber viele "große" kommerzielle Systeme tun dies routinemäßig, bis sie DB-spezifische Grenzen erreichen, wie z. B. 32 KB (glaube ich) für Anweisungen in Oracle.
Natürlich müssen Sie sicherstellen, dass der Satz niemals unangemessen groß wird, oder in diesem Fall Fehler einfangen.
quelle
Nach Adams Idee. Stellen Sie Ihre vorbereitete Anweisung so ein, dass Sie my_column aus my_table auswählen, wobei search_column in (#) einen String x erstellt und mit einer Zahl von "?,?,?" abhängig von Ihrer Werteliste Ändern Sie dann einfach das # in der Abfrage für Ihren neuen String x und füllen Sie ihn aus
quelle
Generieren Sie die Abfragezeichenfolge im PreparedStatement so, dass eine Anzahl von? Mit der Anzahl der Elemente in Ihrer Liste übereinstimmt. Hier ist ein Beispiel:
quelle
StringBuilder
. Aber nicht so, wie du denkst. Beim Dekompilieren könnengenerateQsForIn
Sie sehen, dass pro Schleifeniteration zwei neueStringBuilder
zugewiesen werden und jeweilstoString
aufgerufen werden. DieStringBuilder
Optimierung fängt nur Dinge wie ein"x" + i+ "y" + j
, geht aber nicht über einen Ausdruck hinaus.ps.setObject(1,items)
anstatt die Liste zu durchlaufen und dann die zu setzenparamteres
?Es gibt verschiedene alternative Ansätze, die wir für die IN-Klausel in PreparedStatement verwenden können.
Verwenden Sie NULL in PreparedStatement-Abfragen - Optimale Leistung, funktioniert hervorragend, wenn Sie die Grenze der IN-Klauselargumente kennen. Wenn es keine Begrenzung gibt, können Sie Abfragen im Stapel ausführen. Beispielcode-Snippet ist;
Weitere Details zu diesen alternativen Ansätzen finden Sie hier .
quelle
In einigen Situationen kann regulärer Ausdruck hilfreich sein. Hier ist ein Beispiel, das ich bei Oracle überprüft habe und das funktioniert.
Es gibt jedoch eine Reihe von Nachteilen:
quelle
Nachdem ich verschiedene Lösungen in verschiedenen Foren untersucht und keine gute Lösung gefunden habe, bin ich der Meinung, dass der folgende Hack, den ich mir ausgedacht habe, am einfachsten zu befolgen und zu codieren ist:
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". Bezeichnen Sie die Liste der Parameter, die anstelle dieses Dummy-Strings verwendet werden.
Sie können alle Parameter in einer einzigen String-Variablen in Ihrem Java-Code sammeln. Dies kann wie folgt erfolgen:
In unserem Fall können Sie alle durch Kommas getrennten Parameter in eine einzige String-Variable 'param1' einfügen.
Nachdem Sie alle Parameter in einem einzigen String zusammengefasst haben, können Sie einfach den Dummy-Text in Ihrer Abfrage, in diesem Fall "PARAM", durch den Parameter String, dh param1, ersetzen. Folgendes müssen Sie tun:
Sie können Ihre Abfrage jetzt mit der Methode executeQuery () ausführen. Stellen Sie einfach sicher, dass Ihre Abfrage nirgendwo das Wort "PARAM" enthält. Sie können anstelle des Wortes "PARAM" eine Kombination aus Sonderzeichen und Alphabeten verwenden, um sicherzustellen, dass kein solches Wort in die Abfrage aufgenommen wird. Hoffe du hast die Lösung.
Hinweis: Obwohl dies keine vorbereitete Abfrage ist, erledigt sie die Arbeit, die mein Code ausführen soll.
quelle
Nur der Vollständigkeit halber und weil ich niemanden gesehen habe, der es vorschlägt:
Bevor Sie einen der oben genannten komplizierten Vorschläge implementieren, prüfen Sie, ob die SQL-Injection in Ihrem Szenario tatsächlich ein Problem darstellt.
In vielen Fällen ist der für IN (...) bereitgestellte Wert eine Liste von IDs, die so generiert wurden, dass Sie sicher sein können, dass keine Injektion möglich ist ... (z. B. die Ergebnisse einer vorherigen Auswahl von some_id aus some_table where some_condition.)
In diesem Fall können Sie diesen Wert einfach verketten und die Dienste oder die vorbereitete Anweisung nicht oder für andere Parameter dieser Abfrage verwenden.
quelle
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 erforderlich, da sich das SQL selbst ändern kann, wenn Der Treiber kann die Anweisung nicht vorkompilieren. Sie hat auch den netten Nebeneffekt, dass SQL-Injection-Angriffe verhindert werden. " Am Ende habe ich folgenden Ansatz gewählt:
quelle
SetArray ist die beste Lösung, aber für viele ältere Treiber nicht verfügbar. Die folgende Problemumgehung kann in Java8 verwendet werden
Diese Lösung ist besser als andere hässliche while-Schleifenlösungen, bei denen die Abfragezeichenfolge durch manuelle Iterationen erstellt wird
quelle
Das hat bei mir funktioniert (Pseudocode):
Bindung angeben:
quelle
Mein Beispiel für SQLite- und Oracle-Datenbanken.
Die erste For-Schleife dient zum Erstellen von PreparedStatement-Objekten.
Die zweite For-Schleife dient zum Bereitstellen von Werten für PreparedStatement-Parameter.
quelle
Meine Problemumgehung (JavaScript)
SearchTerms
ist das Array, das Ihre Eingaben / Schlüssel / Felder usw. enthältquelle