0Pricing
Java Academy · Lezione

PreparedStatement

Query parametrizzate sicure

PreparedStatement è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.

Perché usare PreparedStatement?

Costruire SQL concatenando l'input dell'utente è pericoloso e lento.

PreparedStatement risolve entrambi i problemi:

  • Sicurezza — i valori vengono inviati separatamente dal testo SQL, impedendo la SQL injection.
  • Prestazioni — il database può analizzare e pianificare la query una volta, quindi riutilizzarla.

Il problema della SQL injection

Consideri la costruzione di una query mediante concatenazione di stringhe. Se un utente inserisce '; DROP TABLE users; -- come nome, l'SQL concatenato esegue il contenuto come comando.

Si tratta di SQL injection, una delle vulnerabilità web più vecchie e dannose. PreparedStatement la rende impossibile per progettazione.

public class Main {
    public static void main(String[] args) {
        String userInput = "'; DROP TABLE users; --";
        // Dangerous: never do this
        String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
        System.out.println("Injected SQL becomes:");
        System.out.println(unsafe);
    }
}

Segnaposto

In un PreparedStatement, scriva un ? per ogni valore che fornirà in seguito.

Quindi associ i valori usando setter tipizzati:

  • ps.setString(1, name)
  • ps.setInt(2, age)

Gli indici dei parametri partono da 1, in accordo con l'ordine dei segnaposto ?.

Una query sicura

Crei lo statement con prepareStatement, associ i parametri, quindi chiami executeQuery.

Il valore non entra mai nel testo SQL, quindi la SQL injection non può verificarsi, indipendentemente da ciò che l'utente inserisce.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "SELECT id FROM users WHERE name = ?";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Ada");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) System.out.println(rs.getInt("id"));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Associare più parametri

A ogni ? corrisponde una chiamata al relativo setter. L'ordine è importante: il parametro 1 corrisponde al primo ?, il parametro 2 al secondo e così via.

Usi il setter corrispondente al tipo della colonna — setInt, setString, setDouble, setBoolean e così via.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Grace");
            ps.setInt(2, 42);
            ps.setBoolean(3, true);
            int rows = ps.executeUpdate();
            System.out.println("Inserted " + rows + " row");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Gestire NULL

Per associare un SQL NULL, usi setNull(index, type) con una costante di java.sql.Types.

Non passi un null Java a setString sperando che funzioni: sia esplicito:

  • ps.setNull(2, java.sql.Types.INTEGER)

Recuperare le chiavi generate

Dopo un INSERT, spesso serve l'id generato automaticamente.

Passi Statement.RETURN_GENERATED_KEYS quando crea lo statement, quindi legga ps.getGeneratedKeys().

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name) VALUES (?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, "Linus");
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) System.out.println("New id: " + keys.getLong(1));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Inserimenti in batch

Inserire migliaia di righe una per volta è lento. Utilizzi l'elaborazione in batch:

  • Associ i parametri, quindi chiami ps.addBatch().
  • Ripeta l'operazione per ogni riga.
  • Chiami ps.executeBatch() una sola volta.

In questo modo le righe vengono inviate insieme, riducendo drasticamente i round trip.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO tags(label) VALUES (?)";
        String[] labels = {"java", "jdbc", "sql"};
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (String label : labels) {
                ps.setString(1, label);
                ps.addBatch();
            }
            int[] counts = ps.executeBatch();
            System.out.println("Batched " + counts.length + " inserts");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

Riutilizzare uno statement

Un singolo PreparedStatement può essere eseguito molte volte con parametri diversi.

Riassegni i segnaposto e chiami nuovamente execute. Il database riutilizza il piano di esecuzione memorizzato nella cache, ed è per questo che gli statement preparati sono più veloci per le query ripetute.

Mai concatenare

La regola d'oro: qualsiasi valore variabile deve essere il parametro ?.

Solo gli identificatori fissi e controllati dal codice, come il nome di una colonna inserito in una allowlist, possono essere concatenati; anche in quel caso, li convalidi rispetto a una allowlist. I dati forniti dall'utente vengono sempre associati, mai concatenati.

Chiusura e ambito di riutilizzo

Un PreparedStatement è legato alla sua Connection e deve essere chiuso al termine.

All'interno di try-with-resources, dichiari lo statement in modo che venga chiuso prima della connessione. Se riutilizza uno statement molte volte in un ciclo, lo prepari una sola volta fuori dal ciclo e associ nuovamente solo i parametri al suo interno: prepararlo a ogni iterazione scarta il piano memorizzato nella cache.

Verifica rapida

Perché un PreparedStatement impedisce l'iniezione SQL?

Riepilogo

Ha imparato a scrivere query sicure e veloci:

  • Utilizzi segnaposto ? e setter tipizzati (indice a partire da 1).
  • Assocî i valori con setString, setInt, setNull e così via.
  • Recuperi le chiavi generate automaticamente con RETURN_GENERATED_KEYS e getGeneratedKeys.
  • Raggruppi molte scritture in batch con addBatch + executeBatch.
  • Non concateni mai l'input dell'utente nelle istruzioni SQL.

Domande Frequenti

La lezione «PreparedStatement» è gratuita?

Sì — il testo completo di «PreparedStatement» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.

Cosa imparerò in «PreparedStatement»?

Query parametrizzate sicure Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Java Academy?

Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «PreparedStatement»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Java Academy?

Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Fondamenti di JDBC
  2. PreparedStatement
  3. Transazioni
  4. Connection pooling con HikariCP
← Torna a Java Academy