Differenza tra Dichiarazione e PreparedStatement


222

L'istruzione preparata è una versione leggermente più potente di un'istruzione e dovrebbe essere sempre altrettanto rapida e facile da gestire come un'istruzione.
L'istruzione preparata può essere parametrizzata

La maggior parte dei database relazionali gestisce una query JDBC / SQL in quattro passaggi:

  1. Analizzare la query SQL in entrata
  2. Compilare la query SQL
  3. Pianificare / ottimizzare il percorso di acquisizione dei dati
  4. Eseguire la query ottimizzata / acquisire e restituire i dati

Un'istruzione procederà sempre attraverso i quattro passaggi precedenti per ogni query SQL inviata al database. Un'istruzione preparata pre-esegue i passaggi (1) - (3) nel processo di esecuzione sopra. Pertanto, quando si crea un'istruzione preparata, viene eseguita immediatamente una pre-ottimizzazione. L'effetto è ridurre il carico sul motore di database al momento dell'esecuzione.

Ora la mia domanda è: "C'è qualche altro vantaggio nell'usare la dichiarazione preparata?"


12
il più efficiente secondo me è che la tua query può essere parametrizzata in modo dinamico
Hussain Akhtar Wahid 'Ghouri'

Risposte:


198

Vantaggi di un PreparedStatement:

  • La precompilazione e la memorizzazione nella cache sul lato DB dell'istruzione SQL consentono un'esecuzione complessivamente più rapida e la possibilità di riutilizzare la stessa istruzione SQL in batch .

  • Prevenzione automatica degli attacchi di iniezione SQL mediante escape incorporato di virgolette e altri caratteri speciali. Si noti che ciò richiede l'utilizzo di uno dei metodi per impostare i valoriPreparedStatement setXxx()

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

    e quindi non incorporare i valori nella stringa SQL concatenando la stringa.

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • Facilita impostazione di oggetti fuori standard Java in una stringa SQL, ad esempio Date, Time, Timestamp, BigDecimal, InputStream( Blob) e Reader( Clob). Sulla maggior parte di quei tipi non puoi "semplicemente" fare una cosa toString()come faresti in una semplice Statement. Potresti persino rifattorizzare tutto sull'uso PreparedStatement#setObject()all'interno di un ciclo, come dimostrato nel metodo di utilità seguente:

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

    Quale può essere usato come di seguito:

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

4
Un testo descrittivo e esplicativo, unito a riferimenti ed esempi, fornisce una risposta eccellente. +1
XenoRo,

1
@RD Questo può essere vero perché un'istruzione preparata richiede 2 round trip al database: il primo da preparare, il secondo da eseguire. Tuttavia, lo testerei. Presumo che il piano sarebbe ancora memorizzato nella cache del server di database per un Statement, ma potrebbe valere la pena di provarlo.
Brandon,

2
Non posso dire con certezza con Java, ma in generale un'istruzione preparata non preforma "la fuga incorporata di virgolette e altri caratteri speciali"; esegue invece la separazione di SQL eseguibile e dati , inviando i parametri al DBMS come pacchetti separati di informazioni dopo che l'SQL è stato convertito in un piano di query.
IMSoP,

@BalusC - Grazie per la spiegazione dettagliata.
CodeBee ..

49
  1. Sono precompilati (una volta), quindi più veloci per l'esecuzione ripetuta di SQL dinamico (in cui i parametri cambiano)

  2. La memorizzazione nella cache delle istruzioni del database migliora le prestazioni di esecuzione del DB

    I database memorizzano le cache dei piani di esecuzione per le istruzioni eseguite in precedenza. Ciò consente al motore di database di riutilizzare i piani per le istruzioni eseguite in precedenza. Poiché PreparedStatement utilizza parametri, ogni volta che viene eseguito appare come lo stesso SQL, il database può riutilizzare il piano di accesso precedente, riducendo l'elaborazione. Le istruzioni "incorporano" i parametri nella stringa SQL e quindi non appaiono come lo stesso SQL nel DB, impedendo l'utilizzo della cache.

  3. Il protocollo di comunicazione binaria significa meno larghezza di banda e chiamate di comunicazione più veloci al server DB

    Le istruzioni preparate vengono normalmente eseguite tramite un protocollo binario non SQL. Ciò significa che ci sono meno dati nei pacchetti, quindi le comunicazioni con il server sono più veloci. Come regola generale, le operazioni di rete sono un ordine di grandezza più lento delle operazioni del disco che sono un ordine di grandezza più lento delle operazioni della CPU in memoria. Pertanto, qualsiasi riduzione della quantità di dati inviati sulla rete avrà un effetto positivo sulle prestazioni complessive.

  4. Proteggono dall'iniezione SQL, eseguendo l'escape del testo per tutti i valori dei parametri forniti.

  5. Forniscono una maggiore separazione tra il codice della query e i valori dei parametri (rispetto alle stringhe SQL concatenate), aumentando la leggibilità e aiutando i manutentori del codice a comprendere rapidamente input e output della query.

  6. In java, è possibile chiamare getMetadata () e getParameterMetadata () per riflettere rispettivamente sui campi del set di risultati e sui campi dei parametri

  7. In java, accetta in modo intelligente oggetti java come tipi di parametri tramite setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - converte in formato di tipo JDBC comprensibile per DB (non solo per String () formato).

  8. In Java, accetta SQL ARRAY, come tipo di parametro tramite il metodo setArray

  9. In java, accetta CLOB, BLOB, OutputStreams e Readers come parametri "feed" tramite setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream, rispettivamente

  10. In java, consente di impostare valori specifici per DB per DATALINK SQL, SQL ROWID, SQL XML e NULL tramite setURL, setRowId, setSQLXML e setNull metodi

  11. In java, eredita tutti i metodi da Statement. Eredita il metodo addBatch e consente inoltre di aggiungere una serie di valori di parametro per abbinare la serie di comandi SQL in batch tramite il metodo addBatch.

  12. In Java, un tipo speciale di PreparedStatement (la sottoclasse CallableStatement) consente di eseguire le procedure memorizzate, supportando prestazioni elevate, incapsulamento, programmazione procedurale e SQL, amministrazione / manutenzione DB / modifica della logica e utilizzo di logiche e caratteristiche DB proprietarie


Come sono possibili tutte queste meraviglie quando entrambe sono solo interfacce?!?!
Rafael,

1
Le "meraviglie" sono rese possibili da metodi standard di fabbrica che restituiscono implementazioni (specifiche del fornitore) delle interfacce: Connection.createStatement e Connection.prepareStatement. Questo design ti costringe a lavorare contro le interfacce, quindi non è necessario conoscere le classi di implementazione specifiche ed evitare inutili accoppiamenti con tali classi di implementazione. Tutto spiegato con esempi nei documenti jdbc Java e nei documenti Java. :)
Glen Best

La tua parte "come regola empirica" ​​non ha senso non è il contrario 🤔
bhathiya-perera,

38

PreparedStatementè un'ottima difesa (ma non infallibile) nel prevenire attacchi di iniezione SQL . I valori dei parametri vincolanti sono un buon modo per proteggersi dai "piccoli tavoli Bobby" che fanno una visita indesiderata.


6
Come si farebbe l'iniezione SQL tramite un'istruzione preparata allora?
Michael Borgwardt,

2
Michael, le variabili passate come argomenti alle istruzioni preparate verranno automaticamente salvate dal driver JDBC.
CodeBee ..

3
Puoi fare un esempio di come un attacco di iniezione SQL funzionerebbe con un'istruzione preparata? Stai assumendo un bug nel codice del database?
Peter Recore,

2
Sì, ma va ben oltre "piuttosto stupido". È una stupidità sbalorditiva. Nessuno con un'oncia di conoscenza lo farebbe.
duffymo,

2
Inoltre, molti fornitori di database non supportano la parametrizzazione di nomi di colonna (think ORDER BY) e / o costanti numeriche in determinati punti (think LIMIT, OFFSETe altre soluzioni di impaginazione), quindi questi possono essere attaccati dall'iniezione SQL, anche quando Dichiarazioni preparate e parametrizzazione vengono utilizzate ovunque possibile.
dnet,

31

Alcuni dei vantaggi di PreparedStatement over Statement sono:

  1. PreparedStatement ci aiuta a prevenire attacchi di iniezione SQL perché sfugge automaticamente ai caratteri speciali.
  2. PreparedStatement ci consente di eseguire query dinamiche con input di parametri.
  3. PreparedStatement fornisce diversi tipi di metodi setter per impostare i parametri di input per la query.
  4. PreparedStatement è più veloce di Statement. Diventa più visibile quando riutilizziamo PreparedStatement o utilizziamo i suoi metodi di elaborazione batch per eseguire più query.
  5. PreparedStatement ci aiuta a scrivere codice orientato agli oggetti con metodi setter mentre con Statement dobbiamo usare la concatenazione di stringhe per creare la query. Se ci sono più parametri da impostare, scrivere Query usando la concatenazione di stringhe sembra molto brutto e soggetto a errori.

Maggiori informazioni sul problema di iniezione SQL su http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example


Ho letto il tuo articolo, davvero buono. La mia domanda ora è perché qualcuno dovrebbe usare Statement ?! anche per una query statica ?!
Pedram Bashiri,

Uso sempre PreparedStatement, non conosco uno scenario specifico in cui Statement potrebbe avere più vantaggi.
Pankaj,

13

non c'è molto da aggiungere,

1 - se si desidera eseguire una query in un ciclo (più di 1 volta), l'istruzione preparata può essere più veloce, a causa dell'ottimizzazione menzionata.

2 - la query con parametri è un buon modo per evitare l'iniezione SQL. Le query con parametri sono disponibili solo in PreparedStatement.


10

L'istruzione è statica e la dichiarazione preparata è dinamica.

L'istruzione è adatta per DDL e istruzioni preparate per DML.

L'istruzione è più lenta mentre l'istruzione preparata è più veloce.

più differenze (archiviate)


7

Non è possibile eseguire CLOB in una dichiarazione.

E: (OraclePreparedStatement) ps


7

Come citato da mattjames

L'uso di un'istruzione in JDBC deve essere localizzato al 100% per essere utilizzato per DDL (ALTER, CREATE, GRANT, ecc.) In quanto questi sono gli unici tipi di istruzione che non possono accettare VARIABILI BIND. PreparedStatements o CallableStatements devono essere utilizzati per OGNI ALTRO tipo di istruzione (DML, Query). Poiché questi sono i tipi di istruzione che accettano variabili di bind.

Questo è un fatto, una regola, una legge - usa dichiarazioni preparate OVUNQUE. Usa DICHIARAZIONI quasi da nessuna parte.


5

l'iniezione sql viene ignorata dall'istruzione preparata, quindi la sicurezza aumenta nell'istruzione preparata


4
  • È più facile da leggere
  • Puoi facilmente rendere costante la stringa di query

4

L'istruzione verrà utilizzata per l'esecuzione di istruzioni SQL statiche e non può accettare parametri di input.

PreparedStatement verrà utilizzato per eseguire più volte istruzioni SQL in modo dinamico. Accetterà i parametri di input.


4

Un'altra caratteristica della query preparata o parametrizzata: riferimento tratto da questo articolo.

Questa affermazione è una delle caratteristiche del sistema di database in cui la stessa istruzione SQL viene eseguita ripetutamente con alta efficienza. Le istruzioni preparate sono un tipo di modello e utilizzate dall'applicazione con parametri diversi.

Il modello di istruzione viene preparato e inviato al sistema di database e il sistema di database esegue l'analisi, la compilazione e l'ottimizzazione su questo modello e lo memorizza senza eseguirlo.

Alcuni parametri come, in cui la clausola non viene passata durante la successiva creazione del modello, inviano questi parametri al sistema del database e il sistema del database utilizza il modello dell'istruzione SQL ed esegue secondo la richiesta.

Le istruzioni preparate sono molto utili contro SQL Injection poiché l'applicazione può preparare parametri utilizzando tecniche e protocolli diversi.

Quando il numero di dati aumenta e gli indici cambiano frequentemente in quel momento, le istruzioni preparate potrebbero non riuscire perché in questa situazione è necessario un nuovo piano di query.


3

Statement l'interfaccia esegue istruzioni SQL statiche senza parametri

PreparedStatement interface (extending Statement) esegue un'istruzione SQL precompilata con / senza parametri

  1. Efficiente per esecuzioni ripetute

  2. È precompilato, quindi è più veloce


2

Non avere confusione: ricorda semplicemente

  1. L'istruzione viene utilizzata per query statiche come DDL, ad esempio create, drop, alter e preparStatement viene utilizzato per query dinamiche, ad esempio query DML.
  2. In Statement, la query non è precompilata mentre nella preparazioneizeStatement la query è precompilata, a causa di ciò preparStatement è efficiente in termini di tempo.
  3. preparStatement prende argomento al momento della creazione mentre Statement non accetta argomenti. Ad esempio, se si desidera creare una tabella e inserire un elemento, allora :: Creare una tabella (statica) utilizzando Istruzione e Inserisci elemento (dinamico) utilizzando preparStatement.

1
preparStatement prende argomento al momento della creazione, mentre Statement non accetta argomenti.

1

Ho seguito tutte le risposte a questa domanda per cambiare un codice legacy funzionante usando - Statement(ma avendo iniezioni SQL) in una soluzione usando PreparedStatementun codice molto più lento a causa della scarsa comprensione della semantica attorno a Statement.addBatch(String sql)& PreparedStatement.addBatch().

Quindi sto elencando il mio scenario qui in modo che gli altri non commettano lo stesso errore.

Il mio scenario era

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

Quindi nel codice sopra ho avuto migliaia di query diverse, tutte aggiunte alla stessa istruzione e questo codice ha funzionato più velocemente perché le istruzioni che non venivano memorizzate nella cache erano buone e questo codice veniva eseguito raramente nell'app.

Ora per correggere le iniezioni SQL, ho cambiato questo codice 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();
}

Quindi, vedete, ho iniziato a creare migliaia di PreparedStatementoggetti e alla fine non sono stato in grado di utilizzare il batch perché il mio scenario lo richiedeva: ci sono migliaia di query UPDATE o INSERT e tutte queste query sono diverse.

La correzione dell'iniezione SQL era obbligatoria senza alcun costo per il degrado delle prestazioni e non credo che sia possibile PreparedStatementin questo scenario.

Inoltre, quando si utilizza la funzione di batch integrata, è necessario preoccuparsi di chiudere una sola istruzione ma con questo approccio Elenco, è necessario chiudere la dichiarazione prima di riutilizzarla, Riutilizzo di una dichiarazione preparata

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.