Connessione JDBC in Java
Apri e gestisci le connessioni al database in Java con l'interfaccia Connection — apertura, chiusura e configurazione.
Una Connection è una sessione attiva con il database. È l'oggetto restituito da DriverManager (o da un DataSource) ed è la factory per tutto il resto — statement, transazioni, savepoint, metadati. Una connessione è anche una risorsa scarsa e costosa: ogni connessione aperta occupa un socket e una sessione lato server, quindi la regola fondamentale è aprire tardi, chiudere subito.
Questo capitolo spiega come costruire un URL di connessione, i tre modi per aprire una Connection, perché il try-with-resources è imprescindibile, le impostazioni di sessione configurabili e gli errori che si incontrano in caso di configurazione errata. Si presuppone che il driver sia già stato caricato — consulta JDBC Drivers e JDBC Introduction per il quadro generale.
L'URL di connessione
Tutto ciò di cui DriverManager ha bisogno per trovare e raggiungere un database è codificato nell'URL:
jdbc:<subprotocol>://<host>:<port>/<database>?<key=value&...>Ad esempio jdbc:postgresql://db.internal:5432/shop?ssl=true. Il prefisso jdbc: è obbligatorio; il sottoprotocollo seleziona il driver; il resto è specifico del vendor ma segue la convenzione host, porta, database e una query string di opzioni di configurazione. Alcuni URL reali da riconoscere:
| Database | URL di esempio |
|---|---|
| PostgreSQL | jdbc:postgresql://localhost:5432/shop |
| MySQL | jdbc:mysql://localhost:3306/shop?useSSL=true |
| H2 (in-memory) | jdbc:h2:mem:testdb |
| SQLite (file) | jdbc:sqlite:/data/shop.db |
Il sottoprotocollo (postgresql, mysql, h2, sqlite) è il meccanismo con cui DriverManager decide quale driver registrato deve gestire l'URL, quindi specificarlo correttamente indica a JDBC quale database si intende usare.
Tre modi per aprirla
// 1. URL with credentials as arguments
Connection a = DriverManager.getConnection(url, "app", "secret");
// 2. URL with a Properties bag (user, password, plus driver-specific keys)
Properties props = new Properties();
props.setProperty("user", "app");
props.setProperty("password", "secret");
props.setProperty("connectTimeout", "10");
Connection b = DriverManager.getConnection(url, props);
// 3. From a pooled DataSource (preferred for applications)
Connection c = dataSource.getConnection();DriverManager vs. DataSource
DriverManager.getConnection(...) apre una connessione fisica nuova ogni volta, poi la smonta quando viene chiusa. Quella procedura di handshake — lookup DNS, TCP, TLS, autenticazione — costa decine di millisecondi, il che va bene per uno script ma è disastroso per un server che gestisce molte richieste.
Un DataSource supportato da un pool di connessioni (HikariCP, Apache DBCP o quello fornito dal server applicativo) mantiene aperto un insieme di connessioni fisiche e le distribuisce su richiesta. Chiamare getConnection() ne prende in prestito una; chiamare close() la restituisce al pool invece di chiuderla davvero. Per qualsiasi applicazione con esecuzione prolungata, preferisci un DataSource con pool; usa DriverManager solo in piccoli strumenti, test ed esempi.
Chiudi sempre — usa il try-with-resources
Connection, Statement e ResultSet implementano tutti AutoCloseable. Dichiararli nell'intestazione di un try-with-resources garantisce che vengano chiusi in ordine inverso anche se viene lanciata un'eccezione — è la singola abitudine più importante in JDBC:
try (Connection conn = DriverManager.getConnection(url, "app", "secret")) {
// use conn...
} // conn.close() runs here automatically, even on exceptionFar trapelare connessioni (dimenticare di chiuderle) esaurisce il pool e alla fine blocca l'intera applicazione — un classico blackout in produzione. Si noti che aprire una Connection lancia una SQLException checked, quindi la chiamata risiede sempre all'interno di un try (o di un metodo che dichiara throws SQLException).
Una volta ottenuta una connessione, la si usa per creare Statements e PreparedStatements — e questi devono stare all'interno della stessa intestazione try-with-resources in modo da chiudersi prima della connessione:
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
ps.setInt(1, 42);
// ...read the ResultSet...
} // ps closes first, then conn — reverse declaration orderConfigurare una connessione
Una volta aperta, una connessione porta con sé impostazioni a livello di sessione:
| Metodo | Cosa fa |
|---|---|
setAutoCommit(false) | Avvia una transazione manuale; poi chiami commit() o rollback() tu stesso. Vedi JDBC Transactions. |
setTransactionIsolation(...) | Sceglie un livello di isolamento (es. TRANSACTION_READ_COMMITTED). |
setReadOnly(true) | Un suggerimento che la connessione non esegue scritture; alcuni driver la ottimizzano di conseguenza. |
setSchema(...) / setCatalog(...) | Seleziona il namespace contro cui vengono eseguite le query. |
isValid(timeout) | Restituisce true se la connessione è ancora attiva; i pool la usano per scartare quelle non valide. |
Queste impostazioni sono per sessione, quindi si azzerano quando una connessione reale viene chiusa — ma con un pool, una connessione presa in prestito potrebbe portare ancora le impostazioni lasciate da un utente precedente. È per questo che i pool ripristinano autoCommit e l'isolamento al momento della restituzione, e perché dovresti impostare ciò di cui hai bisogno invece di assumere i valori predefiniti.
Errori di connessione comuni
Quando una connessione fallisce si ottiene quasi sempre una SQLException; il messaggio indica quale livello si è rotto:
No suitable driver found for ...— la classe del driver non è mai stata caricata, oppure il sottoprotocollo dell'URL è scritto male e nessun driver registrato lo riconosce. Correggi la dipendenza o l'URL (vedi JDBC Drivers).Connection refused— niente è in ascolto su quell'host/porta: il database è inattivo, oppure host/porta nell'URL sono errati.- Authentication /
password authentication failed—useropassworderrati, oppure l'utente non ha i diritti su quel database. - Connection timeout — l'host non è raggiungibile (firewall, rete errata). Imposta
connectTimeoutaffinché la chiamata fallisca rapidamente invece di bloccarsi.
Un esempio pratico: l'anatomia di un URL di connessione
Questo programma scompone un URL JDBC realistico nei suoi componenti e costruisce il bag Properties da passare insieme ad esso — i due input di cui getConnection ha bisogno — senza richiedere un database attivo.
Cosa trarre dall'esecuzione:
- L'URL non è opaco — è un dato strutturato.
getConnectionanalizza esattamente questi elementi: il sottoprotocollo sceglie il driver, e host/porta/database dicono a quel driver dove connettersi. Leggere un URL ad alta voce è il modo più rapido per individuare errori di "database sbagliato". - La query string (
?ssl=true&applicationName=reports) trasporta opzioni specifiche del driver. Le stesse impostazioni possono viaggiare nell'URL oppure nel bagProperties— entrambi raggiungono il driver, e li si mescola a piacere. - Le credenziali appartengono a
Properties(o alla configurazione delDataSource), non hardcodate nella stringa URL che finisce nei log. L'esempio maschera la password nell'output proprio per questo motivo — non registrare mai le credenziali nei log. connectTimeoutè una vera proprietà del driver PostgreSQL. La configurazione risiede in queste coppie chiave/valore, motivo per cui raramente si fa subclassing: è la configurazione, non il codice, a modellare una connessione.- Questo è stato eseguito senza database perché costruire gli input per
getConnectionè puro lavoro su stringhe. La parte costosa — il socket e la sessione server — avviene solo alla chiamata digetConnection, motivo per cui la si rinvia e la si chiude rapidamente.