Sebbene sia vero che, in molti casi, un'istruzione SQL di base farà il lavoro per molte modifiche o query del database, spesso è una best practice per sfruttare la flessibilità e i vantaggi offerti dall'utilizzo di PreparedStatements
.
Le principali differenze tra un'istruzione JDBC standard e un PreparedStatement
sono meglio definiti dai vantaggi che un PreparedStatement
offre te e la tua applicazione. Di seguito esamineremo i tre vantaggi principali di PreparedStatements
su normali istruzioni JDBC/SQL.
Prevenzione iniezione SQL
Il primo vantaggio dell'utilizzo di un PreparedStatement
puoi sfruttare la moltitudine di .setXYZ()
metodi, come .setString()
, che consente al codice di eseguire automaticamente l'escape di caratteri speciali come le virgolette all'interno dell'istruzione SQL passata, prevenendo la sempre pericolosa SQL injection
attacco.
Ad esempio, in un'istruzione SQL standard, può essere tipico inserire valori direttamente in linea con l'istruzione, in questo modo:
statement = "INSERT INTO books (title, primary_author, published_date) VALUES ('" + book.getTitle() + "', '" + book.getPrimaryAuthor() + "', '" + new Timestamp(book.getPublishedDate().getTime()) + "'";
Ciò ti costringerebbe a eseguire il tuo codice per impedire le iniezioni SQL eseguendo l'escape di virgolette e altri caratteri speciali dai valori inseriti.
Al contrario, un PreparedStatement
potrebbe essere invocato come segue, usando .setXYZ()
metodi per inserire valori con caratteri di escape automatico durante l'esecuzione del metodo:
ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.executeUpdate();
Pre-compilazione
Un altro vantaggio di un PreparedStatement
è che l'SQL stesso è pre-compiled
una sola volta e quindi mantenuto in memoria dal sistema, anziché essere compilato ogni volta che viene chiamata l'istruzione. Ciò consente un'esecuzione più rapida, in particolare quando un PreparedStatement
viene utilizzato insieme a batches
, che ti consentono di eseguire una serie (o batch
) di istruzioni SQL contemporaneamente durante una singola connessione al database.
Ad esempio, qui abbiamo una funzione che accetta un List
di libri. Per ogni book
nell'elenco, vogliamo eseguire un INSERT
istruzione, ma li aggiungeremo tutti a un batch di PreparedStatements
ed eseguili tutti in un colpo solo:
public void createBooks(List<Entity> books) throws SQLException {
try (
Connection connection = dataSource.getConnection();
PreparedStatement ps = connection.prepareStatement("INSERT INTO books (title, primary_author, published_date) VALUES (?, ?, ?)");
) {
for (Entity book : books) {
ps.setString(1, book.getTitle());
ps.setString(2, book.getPrimaryAuthor());
ps.setTimestamp(3, new Timestamp(book.getPublishedDate().getTime()));
ps.addBatch();
}
ps.executeBatch();
}
}
Inserimento di tipi di dati anomali nell'istruzione SQL
Il vantaggio finale di PreparedStatements
che tratteremo è la possibilità di inserire tipi di dati anomali nell'istruzione SQL stessa, come Timestamp
, InputStream
e molti altri.
Ad esempio, possiamo utilizzare un PreparedStatement
per aggiungere una foto di copertina al record del nostro libro utilizzando .setBinaryStream()
metodo:
ps = connection.prepareStatement("INSERT INTO books (cover_photo) VALUES (?)");
ps.setBinaryStream(1, book.getPhoto());
ps.executeUpdate();