Unterschied zwischen Statement und PreparedStatement

222

Die vorbereitete Anweisung ist eine etwas leistungsfähigere Version einer Anweisung und sollte immer mindestens so schnell und einfach zu handhaben sein wie eine Anweisung.
Die vorbereitete Anweisung kann parametrisiert werden

Die meisten relationalen Datenbanken verarbeiten eine JDBC / SQL-Abfrage in vier Schritten:

  1. Analysieren Sie die eingehende SQL-Abfrage
  2. Kompilieren Sie die SQL-Abfrage
  3. Planen / optimieren Sie den Datenerfassungspfad
  4. Führen Sie die optimierten Abfrage- / Erfassungs- und Rückgabedaten aus

Eine Anweisung führt für jede an die Datenbank gesendete SQL-Abfrage immer die vier oben genannten Schritte aus. Eine vorbereitete Anweisung führt die Schritte (1) - (3) im obigen Ausführungsprozess vor. Daher wird beim Erstellen einer vorbereiteten Anweisung sofort eine Voroptimierung durchgeführt. Der Effekt besteht darin, die Belastung des Datenbankmoduls zur Ausführungszeit zu verringern.

Meine Frage lautet nun: "Gibt es einen weiteren Vorteil der Verwendung von Prepared Statement?"

CodeBee ..
quelle
12
Das effizienteste ist meiner Meinung nach, dass Ihre Abfrage dynamisch parametrisiert werden kann
Hussain Akhtar Wahid 'Ghouri'

Antworten:

198

Vorteile eines PreparedStatement:

  • Die Vorkompilierung und das DB-seitige Caching der SQL-Anweisung führt zu einer insgesamt schnelleren Ausführung und der Möglichkeit, dieselbe SQL-Anweisung in Stapeln wiederzuverwenden .

  • Automatische Verhinderung von SQL-Injection- Angriffen durch integriertes Entkommen von Anführungszeichen und anderen Sonderzeichen. Beachten Sie, dass Sie hierfür eine der PreparedStatement setXxx()Methoden zum Festlegen der Werte verwenden müssen

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    preparedStatement.setString(1, person.getName());
    preparedStatement.setString(2, person.getEmail());
    preparedStatement.setTimestamp(3, new Timestamp(person.getBirthdate().getTime()));
    preparedStatement.setBinaryStream(4, person.getPhoto());
    preparedStatement.executeUpdate();

    und somit nicht inline die Werte in der SQL - Zeichenfolge von String-Verkettungs.

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • Erleichtert Einstellung von Nicht-Standard - Java - Objekten in einer SQL - Zeichenfolge, zum Beispiel Date, Time, Timestamp, BigDecimal, InputStream( Blob) und Reader( Clob). Bei den meisten dieser Typen kann man nicht "einfach" machen, toString()wie man es in einem einfachen tun würde Statement. Sie können sogar alles so umgestalten, dass es PreparedStatement#setObject()innerhalb einer Schleife verwendet wird, wie in der folgenden Dienstprogrammmethode gezeigt:

    public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            preparedStatement.setObject(i + 1, values[i]);
        }
    }

    Welches kann wie folgt verwendet werden:

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    setValues(preparedStatement, person.getName(), person.getEmail(), new Timestamp(person.getBirthdate().getTime()), person.getPhoto());
    preparedStatement.executeUpdate();
BalusC
quelle
4
Ein beschreibender und erklärender Text, gepaart mit Referenzen und Beispielen, ist eine hervorragende Antwort. +1
XenoRo
1
@RD Dies kann zutreffen, da für eine vorbereitete Anweisung zwei Roundtrips zur Datenbank erforderlich sind: der erste zur Vorbereitung und der zweite zur Ausführung. Ich würde es jedoch testen. Ich gehe davon aus, dass der Plan für a immer noch auf dem Datenbankserver zwischengespeichert wird Statement, aber es könnte einen Test wert sein.
Brandon
2
Ich kann mit Java nicht sicher sagen, aber im Allgemeinen führt eine vorbereitete Anweisung nicht "eingebautes Entkommen von Anführungszeichen und anderen Sonderzeichen" vor; Stattdessen werden ausführbares SQL und Daten getrennt und die Parameter als separate Informationspakete an das DBMS gesendet, nachdem das SQL in einen Abfrageplan konvertiert wurde.
IMSoP
@ BalusC - Vielen Dank für die ausführliche Erklärung.
CodeBee ..
49
  1. Sie sind (einmal) vorkompiliert und somit schneller für die wiederholte Ausführung von dynamischem SQL (wo sich Parameter ändern).

  2. Das Zwischenspeichern von Datenbankanweisungen erhöht die Leistung der DB-Ausführung

    Datenbanken speichern Caches von Ausführungsplänen für zuvor ausgeführte Anweisungen. Auf diese Weise kann das Datenbankmodul die Pläne für zuvor ausgeführte Anweisungen wiederverwenden. Da PreparedStatement Parameter verwendet, kann es bei jeder Ausführung als dieselbe SQL angezeigt werden. Die Datenbank kann den vorherigen Zugriffsplan wiederverwenden, wodurch die Verarbeitung reduziert wird. Anweisungen "inline" die Parameter in die SQL-Zeichenfolge und werden daher für die Datenbank nicht als dieselbe SQL angezeigt, wodurch die Cache-Nutzung verhindert wird.

  3. Das binäre Kommunikationsprotokoll bedeutet weniger Bandbreite und schnellere Kommunikationsanrufe an den DB-Server

    Vorbereitete Anweisungen werden normalerweise über ein Nicht-SQL-Binärprotokoll ausgeführt. Dies bedeutet, dass die Pakete weniger Daten enthalten, sodass die Kommunikation mit dem Server schneller ist. Als Faustregel gilt, dass Netzwerkoperationen um eine Größenordnung langsamer sind als Plattenoperationen, die um eine Größenordnung langsamer sind als CPU-Operationen im Speicher. Daher wirkt sich eine Verringerung der über das Netzwerk gesendeten Datenmenge positiv auf die Gesamtleistung aus.

  4. Sie schützen vor SQL-Injection, indem sie Text für alle angegebenen Parameterwerte maskieren.

  5. Sie bieten eine stärkere Trennung zwischen dem Abfragecode und den Parameterwerten (im Vergleich zu verketteten SQL-Zeichenfolgen), verbessern die Lesbarkeit und helfen Code-Betreuern, die Ein- und Ausgaben der Abfrage schnell zu verstehen.

  6. Kann in Java getMetadata () und getParameterMetadata () aufrufen, um die Ergebnismengenfelder bzw. die Parameterfelder wiederzugeben

  7. In Java werden Java-Objekte auf intelligente Weise als Parametertypen über setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime und setTimestamp akzeptiert () Format).

  8. Akzeptiert in Java SQL ARRAYs als Parametertyp über die setArray-Methode

  9. Akzeptiert in Java CLOBs, BLOBs, OutputStreams und Readers als Parameter "Feeds" über die Methoden setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream

  10. Ermöglicht in Java das Festlegen von DB-spezifischen Werten für SQL DATALINK, SQL ROWID, SQL XML und NULL über die Methoden setURL, setRowId, setSQLXML und setNull

  11. Erbt in Java alle Methoden von Statement. Es erbt die addBatch-Methode und ermöglicht zusätzlich das Hinzufügen einer Reihe von Parameterwerten, die mit der Menge der gestapelten SQL-Befehle über die addBatch-Methode übereinstimmen.

  12. In Java ermöglicht eine spezielle Art von PreparedStatement (die Unterklasse CallableStatement) die Ausführung gespeicherter Prozeduren - Unterstützung für hohe Leistung, Kapselung, prozedurale Programmierung und SQL, DB-Verwaltung / Wartung / Optimierung der Logik und Verwendung proprietärer DB-Logik und -Funktionen

Glen Best
quelle
Wie sind all diese Wunder möglich, wenn beide nur Schnittstellen sind?!?!
Rafael
1
Die 'Wunder' werden durch Standard-Factory-Methoden ermöglicht, die (herstellerspezifische) Implementierungen der Schnittstellen zurückgeben: Connection.createStatement und Connection.prepareStatement. Dieses Design zwingt Sie dazu, gegen Schnittstellen zu arbeiten, damit Sie die spezifischen Implementierungsklassen nicht kennen und unnötige enge Kopplungen mit solchen Implementierungsklassen vermeiden müssen. Alle werden anhand von Beispielen in den Java-JDBC-Dokumenten und Java-Dokumenten erläutert. :)
Glen Best
Ihr "als Faustregel" Teil macht keinen Sinn, ist es nicht anders herum 🤔
Bhathiya-Perera
38

PreparedStatementist eine sehr gute Verteidigung (aber nicht narrensicher) bei der Verhinderung von SQL-Injection-Angriffen . Das Binden von Parameterwerten ist ein guter Weg, um zu verhindern, dass "kleine Bobby-Tische" einen unerwünschten Besuch machen.

Duffymo
quelle
6
Wie würde man dann eine SQL-Injektion durch eine vorbereitete Anweisung durchführen?
Michael Borgwardt
2
Michael, Variablen, die als Argumente an vorbereitete Anweisungen übergeben werden, werden vom JDBC-Treiber automatisch maskiert.
CodeBee ..
3
Können Sie ein Beispiel geben, wie ein SQL-Injection-Angriff gegen eine vorbereitete Anweisung funktionieren würde? Nehmen Sie einen Fehler im Datenbankcode an?
Peter Recore
2
Ja, aber es ist weit über "ziemlich dumm" hinaus. Es ist umwerfende Dummheit. Niemand mit einer Unze Wissen würde das tun.
Duffymo
2
Viele Datenbank - Anbieter auch, unterstützen keine Spaltennamen Parametrisierung (denken ORDER BY) und / oder numerische Konstanten in bestimmten Orten (denken LIMIT, OFFSETund anderer Paginierung Lösungen), so kann diese durch SQL - Injection angegriffen werden, auch wenn Prepared Statements und Parametrierung verwendet werden , wo immer möglich.
dnet
31

Einige der Vorteile von PreparedStatement gegenüber Statement sind:

  1. PreparedStatement hilft uns dabei, SQL-Injection-Angriffe zu verhindern, da die Sonderzeichen automatisch ausgeblendet werden.
  2. Mit PreparedStatement können wir dynamische Abfragen mit Parametereingaben ausführen.
  3. PreparedStatement bietet verschiedene Arten von Setter-Methoden zum Festlegen der Eingabeparameter für die Abfrage.
  4. PreparedStatement ist schneller als Statement. Es wird sichtbarer, wenn wir das PreparedStatement wiederverwenden oder seine Stapelverarbeitungsmethoden zum Ausführen mehrerer Abfragen verwenden.
  5. PreparedStatement hilft uns beim Schreiben von objektorientiertem Code mit Setter-Methoden, während wir bei Statement die String-Verkettung verwenden müssen, um die Abfrage zu erstellen. Wenn mehrere Parameter festgelegt werden müssen, sieht das Schreiben einer Abfrage mithilfe der Zeichenfolgenverkettung sehr hässlich und fehleranfällig aus.

Weitere Informationen zum Problem der SQL-Injektion finden Sie unter http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example

Pankaj
quelle
Ich habe deinen Artikel gelesen, wirklich gut. Meine Frage ist jetzt, warum jemand Statement verwenden würde?! auch für eine statische Abfrage?!
Pedram Bashiri
Ich verwende immer PreparedStatement. Ich kenne kein bestimmtes Szenario, in dem Statement möglicherweise mehr Nutzen hat.
Pankaj
13

nicht viel hinzuzufügen,

1 - Wenn Sie eine Abfrage in einer Schleife ausführen möchten (mehr als einmal), kann die vorbereitete Anweisung aufgrund der von Ihnen erwähnten Optimierung schneller sein.

2 - Parametrisierte Abfrage ist ein guter Weg, um SQL Injection zu vermeiden. Parametrisierte Abfragen sind nur in PreparedStatement verfügbar.

mhshams
quelle
10

Die Anweisung ist statisch und die vorbereitete Anweisung ist dynamisch.

Die Anweisung ist für DDL geeignet und die Anweisung für DML vorbereitet.

Die Anweisung ist langsamer, während die vorbereitete Anweisung schneller ist.

mehr Unterschiede (archiviert)

Sandeep Vanama
quelle
7

CLOBs in einer Anweisung können nicht ausgeführt werden.

Und: (OraclePreparedStatement) ps

Orbfish
quelle
7

Wie von Mattjames zitiert

Die Verwendung einer Anweisung in JDBC sollte zu 100% auf die Verwendung für DDL (ALTER, CREATE, GRANT usw.) beschränkt sein, da dies die einzigen Anweisungstypen sind, die BIND VARIABLES nicht akzeptieren können. PreparedStatements oder CallableStatements sollten für JEDEN ANDEREN Anweisungstyp (DML, Queries) verwendet werden. Da dies die Anweisungstypen sind, die Bindungsvariablen akzeptieren.

Dies ist eine Tatsache, eine Regel, ein Gesetz - verwenden Sie überall vorbereitete Aussagen. Verwenden Sie STATEMENTS fast nirgendwo.

Wurzel
quelle
5

Die SQL-Injection wird von der vorbereiteten Anweisung ignoriert, sodass die Sicherheit der vorbereiteten Anweisung erhöht wird

aschiges Geol
quelle
4
  • Es ist leichter zu lesen
  • Sie können die Abfragezeichenfolge leicht zu einer Konstanten machen
Nanda
quelle
4

Die Anweisung wird zum Ausführen statischer SQL-Anweisungen verwendet und kann keine Eingabeparameter akzeptieren.

PreparedStatement wird verwendet, um SQL-Anweisungen viele Male dynamisch auszuführen. Es werden Eingabeparameter akzeptiert.

MARA MP
quelle
4

Ein weiteres Merkmal der vorbereiteten oder parametrisierten Abfrage: Referenz aus diesem Artikel.

Diese Anweisung ist eine der Funktionen des Datenbanksystems, in dem dieselbe SQL-Anweisung wiederholt mit hoher Effizienz ausgeführt wird. Die vorbereiteten Anweisungen sind eine Art der Vorlage und werden von der Anwendung mit unterschiedlichen Parametern verwendet.

Die Anweisungsvorlage wird vorbereitet und an das Datenbanksystem gesendet, und das Datenbanksystem führt das Parsen, Kompilieren und Optimieren dieser Vorlage durch und speichert sie, ohne sie auszuführen.

Einige Parameter wie, bei denen die Klausel während der späteren Anwendung der Vorlagenerstellung nicht übergeben wird, senden diese Parameter an das Datenbanksystem, und das Datenbanksystem verwendet die Vorlage von SQL Statement und wird gemäß Anforderung ausgeführt.

Vorbereitete Anweisungen sind gegen SQL Injection sehr nützlich, da die Anwendung Parameter mit verschiedenen Techniken und Protokollen vorbereiten kann.

Wenn die Anzahl der Daten zunimmt und sich die Indizes zu diesem Zeitpunkt häufig ändern, schlagen vorbereitete Anweisungen möglicherweise fehl, da in dieser Situation ein neuer Abfrageplan erforderlich ist.

Anvesh
quelle
3

Statement Schnittstelle führt statische SQL-Anweisungen ohne Parameter aus

PreparedStatement interface (erweiterte Anweisung) führt eine vorkompilierte SQL-Anweisung mit / ohne Parameter aus

  1. Effizient für wiederholte Ausführungen

  2. Es ist vorkompiliert, damit es schneller ist

Bernard
quelle
2

Keine Verwirrung: Denken Sie einfach daran

  1. Die Anweisung wird für statische Abfragen wie DDLs verwendet, dh erstellen, löschen, ändern und vorbereiten. Die Anweisung wird für dynamische Abfragen verwendet, z. B. DML-Abfragen.
  2. In Statement wird die Abfrage nicht vorkompiliert, während in prepareStatement die Abfrage vorkompiliert wird, da dieses prepareStatement zeiteffizient ist.
  3. prepareStatement akzeptiert zum Zeitpunkt der Erstellung Argumente, während Statement keine Argumente akzeptiert. Wenn Sie beispielsweise eine Tabelle erstellen und ein Element einfügen möchten, dann :: Erstellen Sie eine Tabelle (statisch) mithilfe von Statement und ein Element einfügen (dynamisch) mithilfe von prepareStatement.
Roopam
quelle
1
prepareStatement akzeptiert zum Zeitpunkt der Erstellung Argumente, während Statement keine Argumente akzeptiert.
1

Ich habe alle Antworten auf diese Frage befolgt, um einen funktionierenden Legacy-Code mit - Statement(aber mit SQL Injections) in eine Lösung PreparedStatementmit einem viel langsameren Code zu ändern, da die Semantik um Statement.addBatch(String sql)& schlecht verstanden wurdePreparedStatement.addBatch() .

Deshalb liste ich hier mein Szenario auf, damit andere nicht den gleichen Fehler machen.

Mein Szenario war

Statement statement = connection.createStatement();

for (Object object : objectList) {
    //Create a query which would be different for each object 
    // Add this query to statement for batch using - statement.addBatch(query);
}
statement.executeBatch();

Im obigen Code hatte ich Tausende verschiedener Abfragen, die alle zur selben Anweisung hinzugefügt wurden, und dieser Code funktionierte schneller, da nicht zwischengespeicherte Anweisungen gut waren und dieser Code in der App selten ausgeführt wurde.

Um SQL Injections zu reparieren, habe ich diesen Code geändert in:

List<PreparedStatement> pStatements = new ArrayList<>();    
for (Object object : objectList) {
    //Create a query which would be different for each object 
    PreparedStatement pStatement =connection.prepareStatement(query);
    // This query can't be added to batch because its a different query so I used list. 
    //Set parameter to pStatement using object 
    pStatements.add(pStatement);
}// Object loop
// In place of statement.executeBatch(); , I had to loop around the list & execute each update separately          
for (PreparedStatement ps : pStatements) {
    ps.executeUpdate();
}

Sie sehen, ich habe angefangen, Tausende von PreparedStatementObjekten zu erstellen und konnte dann möglicherweise keine Stapelverarbeitung verwenden, weil mein Szenario dies erforderte - es gibt Tausende von UPDATE- oder INSERT-Abfragen und all diese Abfragen sind zufällig unterschiedlich.

Das Reparieren der SQL-Injection war ohne Kosten für Leistungseinbußen obligatorisch, und ich glaube nicht, dass dies PreparedStatementin diesem Szenario möglich ist.

Wenn Sie die integrierte Stapelverarbeitungsfunktion verwenden, müssen Sie sich nur darum kümmern, nur eine Anweisung zu schließen. Bei diesem List-Ansatz müssen Sie die Anweisung jedoch vor der Wiederverwendung schließen und eine vorbereitete Anweisung wiederverwenden

Sabir Khan
quelle