W3docs

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.

java— editable, runs on the server

Cosa osservare dall'esecuzione:

  • Il template ha tre segnaposto ?, e il programma li conta — quel conteggio è esattamente il numero di chiamate setXxx che 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 stampa bind 1, bind 2, bind 3 per chiarirlo — l'errore più comune per i principianti è partire da 0.
  • Il primo valore è la stessa stringa x'; DROP TABLE users;-- che ha compromesso lo Statement nel 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 null al parametro 3 è il motivo per cui esiste setNull(index, Types.VARCHAR). JDBC ha bisogno del tipo SQL per comunicare al database che tipo di NULL è — lo si specifica con una costante java.sql.Types.
  • Ogni valore porta un tipo implicito — String, int, NULL-di-VARCHAR — ecco perché esiste un setXxx per 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.

Esercitazione

Pratica
Perché un PreparedStatement impedisce l'SQL injection anche quando un valore associato contiene un apice e un punto e virgola?
Perché un PreparedStatement impedisce l'SQL injection anche quando un valore associato contiene un apice e un punto e virgola?
Was this page helpful?