Vai al contenuto

Componenti dell’API JDBC

2025 – prof. Roberto Fuligni

Connection

Rappresenta una sessione di connessione a un database specifico. Attraverso un oggetto Connection è possibile creare statement SQL, gestire le transazioni (commit, rollback) e configurare proprietà di connessione (auto-commit, livelli di isolamento).

Aprire una connessione al database è un’operazione critica che deve sempre essere chiusa correttamente per evitare memory leak e blocchi sul database. La gestione tradizionale richiedeva try-finally, ma con Java 7 è stato introdotto try-with-resources, che chiude automaticamente la connessione al termine del blocco.

Apertura di connessione a un database SQL MariaDB
import java.sql.*;

public static void main(String[] args) {

    String url = "jdbc:mariadb://localhost:3306/dbesempio";
    String user = "root";
    String password = "";

    try (Connection conn = DriverManager.getConnection(url, user, password)) {

        // Routine di accesso al database...

    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Statement

In JDBC, gli statement sono gli oggetti utilizzati per eseguire comandi SQL su un database. Esistono tre principali tipi di statement, ognuno con caratteristiche e casi d’uso specifici:

  • Statement base, per query statiche semplici;
  • PreparedStatement, per query parametrizzate e più efficienti;
  • CallableStatement, per eseguire stored procedure.

Statement base

Lo Statement base di JDBC viene utilizzato per eseguire query SQL senza parametri dinamici. È semplice da usare ma meno efficiente quando si eseguono ripetutamente query simili.

Lo statement base non è sicuro contro SQL injection se i dati provengono da un input dell’utente.

Uso della classe Statement
String query = "SELECT id, nome, saldo FROM conti";

try (Connection conn = DriverManager.getConnection(url, user, password);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query)) {

    while (rs.next()) {
        int id = rs.getInt("id");
        String nome = rs.getString("nome");
        double saldo = rs.getDouble("saldo");

        System.out.format("%-7d %-18s %11.2f%n", id, nome, saldo);
    }

} catch (SQLException e) {
    System.err.println("Errore di connessione al database: " + e.getMessage());
}

PreparedStatement

La classe PreparedStatement permette di utilizzare parametri segnaposto (indicati con il carattere ?) al posto di valori fissi. Questo migliora la sicurezza e le prestazioni, perché il database può compilare ed eseguire la query in modo più efficiente.

Il PreparedStatement protegge dagli attacchi di SQL injection, dato che i parametri vengono trattati come dati e non come parte della query. Il codice risulta più chiaro e pulito, specialmente nel caso di query complesse.

Uso della classe PreparedStatement
double saldoMin = 1000.0;
String query = "SELECT id, nome, saldo FROM conti WHERE saldo >= ?";

try (Connection conn = DriverManager.getConnection(url, user, password);
        PreparedStatement pstmt = conn.prepareStatement(query)) {

    // Parametro per il saldo minimo
    pstmt.setDouble(1, saldoMin);

    try (ResultSet rs = pstmt.executeQuery()) {
        System.out.format("Conti con saldo non inferiore a %.2f €:%n", saldoMin);

        while (rs.next()) {
            int id = rs.getInt("id");
            String nome = rs.getString("nome");
            double saldo = rs.getDouble("saldo");
            System.out.format("%-7d %-18s %11.2f%n", id, nome, saldo);
        }
    }
} catch (SQLException e) {
    System.err.println("Errore di connessione al database: " + e.getMessage());
}

CallableStatement

Se il database supporta le stored procedure, si può usare CallableStatement per eseguirle. Una stored procedure è un insieme di query SQL salvate nel database, spesso usata per migliorare le prestazioni e centralizzare la logica di business.

Se un database contiene una stored procedure dimunisci_saldi che riduce i saldi di tutti i conti di un certo valore, è possibile invocare la procedura con il seguente codice:

Uso della classe CallableStatement con parametri di input
double importo = 150.0,
String callSP = "{CALL diminuisci_saldi(?)}";

try (Connection conn = DriverManager.getConnection(url, user, password);
        CallableStatement cstmt = conn.prepareCall(callSP)) {

    cstmt.setDouble(1, importo);
    cstmt.execute();

    System.out.println("Stored procedure eseguita con successo");

} catch (SQLException e) {
    System.err.println("Errore durante l'esecuzione della stored procedure: " +
            e.getMessage());
}

Se la stored procedure restituisce un valore (ad esempio il numero di conti con saldo inferiore a una certa soglia), si usa un parametro di output:

Uso della classe CallableStatement con parametri di output
double soglia = 150.0;
String callFunction = "{? = CALL conta_conti_sotto_soglia(?)}";

try (Connection conn = DriverManager.getConnection(url, user, password);
        CallableStatement cstmt = conn.prepareCall(callFunction)) {

    cstmt.registerOutParameter(1, java.sql.Types.INTEGER);
    cstmt.setDouble(2, soglia);

    cstmt.execute();

    int conteggio = cstmt.getInt(1);

    System.out.format("Numero di conti con saldo inferiore a %.2f €: %d%n",
            soglia, conteggio);

} catch (SQLException e) {
    System.err.println("Errore durante l'esecuzione della funzione: " + e.getMessage());
}

ResultSet

In JDBC, ResultSet è l’oggetto che rappresenta il risultato di una query SQL eseguita con Statement, PreparedStatement o CallableStatement. Esso permette di leggere i dati restituiti dal database riga per riga e colonna per colonna.

È possibile ottenere un ResultSet eseguendo una query SELECT con il metodo executeQuery(): il risultato conterrà tutte le righe della tabella utenti con le colonne id e `nome

Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, nome FROM conti");

Il ResultSet è una struttura simile a un cursore che parte prima della prima riga e avanza con next():

while (rs.next()) {
    int id = rs.getInt("id");
    String nome = rs.getString("nome");
    System.out.println("ID: " + id + ", Nome: " + nome);
}

Di seguito sono riportati i principali metodi offerti dalla classse ResultSet.

Metodo Descrizione
next() Sposta il cursore alla riga successiva (ritorna false se non ci sono più righe)
previous() Sposta il cursore alla riga precedente (solo se il ResultSet è scorrevole)
first() Si sposta sulla prima riga
last() Si sposta sull’ultima riga
absolute(int row) Si sposta a una riga specifica (solo se il ResultSet è scorrevole)
relative(int rows) Si sposta avanti o indietro di un certo numero di righe

Esistono tre tipi di ResultSet, definiti durante la creazione dello Statement:

Statement stmt = conn.createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE, 
    ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT id, nome FROM conti");

if (rs.last()) { // Sposta il cursore sull'ultima riga
    int id = rs.getInt("id");
    System.out.println("Ultimo id conto: " + id);
}
Tipo Descrizione
TYPE_FORWARD_ONLY (Default) Permette solo di avanzare con next(), senza tornare indietro
TYPE_SCROLL_INSENSITIVE Permette di scorrere avanti e indietro, ma non riflette le modifiche fatte nel database dopo la query
TYPE_SCROLL_SENSITIVE Permette di scorrere avanti e indietro e rileva modifiche nel database

La classe ResultSet offre inoltre diversi metodi per leggere i campi di un record. Questi metodi si differenziano in base al tipo di dato che si desidera recuperare. I principali metodi sono riportati nella seguente tabella.

Metodo Tipo di dato restituito
getInt(String colonna) int
getString(String colonna) String
getBoolean(String colonna) boolean
getDouble(String colonna) double
getDate(String colonna) java.sql.Date
getTimestamp(String colonna) java.sql.Timestamp
getBlob(String colonna) java.sql.Blob

I campi possono essere selezionati in due modi:

  • Per indice: utilizzando l’indice della colonna, ad esempio rs.getString(1) per recuperare il primo campo (gli indici delle colonne partono da 1, non da 0);

  • Per nome: indicando nome della colonna, ad esempio rs.getString("nomeColonna").

Dopo aver creato il ResultSet con CONCUR_UPDATABLE, è possibile modificarne il contenuto:

Statement stmt = conn.createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE, 
    ResultSet.CONCUR_UPDATABLE
);
ResultSet rs = stmt.executeQuery("SELECT id, nome FROM conti");

if (rs.next()) {
    rs.updateString("nome", "Nuovo Nome");
    rs.updateRow();
}
Metodo di modifica Descrizione
updateString("colonna", valore) Modifica il valore di una colonna
updateInt("colonna", valore) Modifica un valore intero
updateRow() Salva le modifiche
deleteRow() Cancella la riga corrente
insertRow() Inserisce una nuova riga