Java JDBC PreparedStatement
Esegui SQL parametrizzato in modo sicuro in Java con PreparedStatement per prevenire l'SQL injection.
Un PreparedStatement è un template SQL con segnaposto ? dove vanno i valori. I valori vengono impostati separatamente per indice e il driver invia il template e i dati su canali diversi — in questo modo un valore non può mai essere interpretato come SQL. Questa è la singola abitudine più importante in JDBC: rende l'injection strutturalmente impossibile e consente al database di riutilizzare il piano della query. Preferiscilo a un semplice Statement praticamente per qualsiasi cosa.
Questo capitolo spiega come creare, associare ed eseguire un PreparedStatement, perché blocca l'SQL injection, come associare valori NULL e tipizzati, e quando riutilizzarne uno risulta vantaggioso.
Creazione, associazione ed esecuzione
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, pw);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name); // bind by 1-based index
ps.setInt(2, age);
int rows = ps.executeUpdate();
}I segnaposto sono numerati a partire da 1, non da 0 — una fonte costante di errori off-by-one. Ogni setXxx corrisponde al tipo della colonna: setString, setInt, setBigDecimal, setTimestamp e così via.
Perché neutralizza l'injection
Con uno Statement, il valore fa parte del testo SQL, quindi un apice nel valore può terminare il letterale e iniettare nuovi comandi. Con un PreparedStatement, l'SQL è fisso e analizzato prima che qualsiasi valore venga associato; il valore viene poi trasmesso come parametro tipizzato. Non esiste alcuna stringa in cui l'apice di un attaccante possa inserirsi — il valore pericoloso del capitolo sullo Statement diventa semplicemente un nome letterale.
Dopo aver eseguito una query, si leggono le righe con un ResultSet, esattamente come si farebbe con uno Statement.
Associare NULL e tipi speciali
Non è possibile passare un null Java a setInt (accetta un tipo primitivo), e setString(i, null) è ambiguo per alcuni tipi. La forma esplicita è setNull(index, sqlType), che specifica il java.sql.Types della colonna:
ps.setNull(3, java.sql.Types.VARCHAR);Riutilizzare un prepared statement
Un prepared statement è progettato per essere eseguito molte volte con valori diversi — imposta, esegui, azzera, ripeti. Il database analizza e pianifica l'SQL una volta sola e lo riutilizza, ecco perché i prepared statement sono anche più veloci in un ciclo rispetto alla ricostruzione di una stringa Statement ogni volta. Per inserimenti massivi, combinalo con la gestione dei batch.
Un esempio pratico: anatomia di un template e delle sue associazioni
Questo programma tratta il template SQL come dato: conta i segnaposto, percorre i valori che verrebbero associati ad essi (inclusa la stringa malevola che ha compromesso lo Statement), e mostra il codice di tipo setNull — tutti i componenti dell'associazione dei parametri, senza un database reale.
Cosa osservare dall'esecuzione:
- Il template ha tre segnaposto
?, e il programma li conta — quel conteggio è esattamente il numero di chiamatesetXxxche si devono effettuare. Una discrepanza (associare il parametro 4 di una query con 3 segnaposto) genera un'eccezione all'esecuzione. - Le associazioni partono da 1: il parametro 1 corrisponde al primo
?. Il ciclo stampabind 1,bind 2,bind 3per chiarirlo — l'errore più comune per i principianti è partire da 0. - Il primo valore è la stessa stringa
x'; DROP TABLE users;--che ha compromesso loStatementnel capitolo precedente. Qui è solo un dato associato al parametro 1; il driver lo memorizza letteralmente come nome. L'injection è neutralizzata per costruzione, non tramite escaping. - Il
nullal parametro 3 è il motivo per cui esistesetNull(index, Types.VARCHAR). JDBC ha bisogno del tipo SQL per comunicare al database che tipo di NULL è — lo si specifica con una costantejava.sql.Types. - Ogni valore porta un tipo implicito —
String,int, NULL-di-VARCHAR — ecco perché esiste unsetXxxper tipo anziché un unico setter debolmente tipizzato. Abbinare il setter al tipo della colonna è la disciplina che mantiene i prepared statement sia sicuri che corretti.