Differenza tra Statement e PreparedStatement

Java Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

In questo tutorial, esploreremo le differenze tra le interfacce Statement e PreparedStatement di JDBC . Non tratteremo CallableStatement , un'interfaccia API JDBC utilizzata per eseguire procedure memorizzate.

2. Interfaccia API JDBC

Sia Statement che PreparedStatement possono essere utilizzati per eseguire query SQL. Queste interfacce sono molto simili. Tuttavia, differiscono in modo significativo l'uno dall'altro per caratteristiche e prestazioni:

  • Istruzione : utilizzata per eseguire query SQL basate su stringhe
  • PreparedStatement : utilizzato per eseguire query SQL con parametri

Per poter utilizzare Statement e PreparedStatement nei nostri esempi, dichiareremo il connettore JDBC h2 come dipendenza nel nostro file pom.xml :

 com.h2database h2 1.4.200 

Definiamo un'entità che useremo in questo articolo:

public class PersonEntity { private int id; private String name; // standard setters and getters }

3. Dichiarazione

In primo luogo, l' interfaccia Statement accetta stringhe come query SQL. Pertanto, il codice diventa meno leggibile quando concateniamo stringhe SQL:

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES(" + personEntity.getId() + ", '" + personEntity.getName() + "')"; Statement statement = connection.createStatement(); statement.executeUpdate(query); }

In secondo luogo, è vulnerabile all'iniezione SQL . I prossimi esempi illustrano questa debolezza.

Nella prima riga, l'aggiornamento imposterà la colonna " nome " su tutte le righe su " hacker ", poiché qualsiasi cosa dopo "-" viene interpretata come un commento in SQL e le condizioni dell'istruzione di aggiornamento verranno ignorate. Nella seconda riga, l'inserimento fallirà perché la citazione nella colonna " nome " non è stata sottoposta a escape:

dao.update(new PersonEntity(1, "hacker' --")); dao.insert(new PersonEntity(1, "O'Brien"))

In terzo luogo, JDBC passa la query con valori inline al database . Pertanto, non esiste un'ottimizzazione delle query e, soprattutto, il motore di database deve garantire tutti i controlli . Inoltre, la query non apparirà come la stessa nel database e impedirà l'utilizzo della cache . Allo stesso modo, gli aggiornamenti batch devono essere eseguiti separatamente:

public void insert(List personEntities) { for (PersonEntity personEntity: personEntities) { insert(personEntity); } }

In quarto luogo, l' interfaccia Statement è adatta per query DDL come CREATE, ALTER e DROP :

public void createTables() { String query = "create table if not exists PERSONS (ID INT, NAME VARCHAR(45))"; connection.createStatement().executeUpdate(query); }

Infine, l' interfaccia Statement non può essere utilizzata per archiviare e recuperare file e array .

4. PreparedStatement

In primo luogo, PreparedStatement estende l' interfaccia Statement . Ha metodi per associare vari tipi di oggetti , inclusi file e array. Quindi, il codice diventa facile da capire :

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.executeUpdate(); }

In secondo luogo, protegge dall'iniezione SQL , eseguendo l'escape del testo per tutti i valori dei parametri forniti:

@Test void whenInsertAPersonWithQuoteInText_thenItNeverThrowsAnException() { assertDoesNotThrow(() -> dao.insert(new PersonEntity(1, "O'Brien"))); } @Test void whenAHackerUpdateAPerson_thenItUpdatesTheTargetedPerson() throws SQLException { dao.insert(Arrays.asList(new PersonEntity(1, "john"), new PersonEntity(2, "skeet"))); dao.update(new PersonEntity(1, "hacker' --")); List result = dao.getAll(); assertEquals(Arrays.asList( new PersonEntity(1, "hacker' --"), new PersonEntity(2, "skeet")), result); }

In terzo luogo, PreparedStatement utilizza la pre-compilazione . Non appena il database riceve una query, controllerà la cache prima di precompilare la query. Di conseguenza, se non è memorizzato nella cache, il motore di database lo salverà per l'utilizzo successivo.

Inoltre, questa funzione accelera la comunicazione tra il database e la JVM tramite un protocollo binario non SQL. Vale a dire, ci sono meno dati nei pacchetti, quindi la comunicazione tra i server è più veloce.

In quarto luogo, PreparedStatement fornisce un'esecuzione batch durante una singola connessione al database . Vediamolo in azione:

public void insert(List personEntities) throws SQLException { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); for (PersonEntity personEntity: personEntities) { preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); }

Successivamente, PreparedStatement fornisce un modo semplice per archiviare e recuperare i file utilizzando i tipi di dati BLOB e CLOB . Allo stesso modo, aiuta a memorizzare gli elenchi convertendo java.sql.Array in un array SQL.

Infine, PreparedStatement implementa metodi come getMetadata () che contengono informazioni sul risultato restituito.

5. conclusione

In questo tutorial, abbiamo presentato le principali differenze tra PreparedStatement e Statement . Entrambe le interfacce offrono metodi per eseguire query SQL, ma è più adatto utilizzare Statement per le query DDL e PreparedStatement per le query DML.

Come al solito, tutti gli esempi di codice sono disponibili su GitHub.

Fondo Java

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO