Java JDBC ResultSet
Scorri e leggi le righe di una query SQL in Java con l'interfaccia ResultSet.
Un ResultSet è un cursore sulle righe restituite da una query. Non mantiene tutte le righe in memoria contemporaneamente: punta a una riga alla volta e la si scorre in avanti. Un aspetto fondamentale è che un ResultSet appena creato è posizionato prima della prima riga — occorre chiamare next() una volta per arrivare alla prima. Per questo motivo ogni ciclo di lettura è while (rs.next()).
Si ottiene un ResultSet da una query eseguita tramite uno Statement o un PreparedStatement. Questa pagina spiega come scorrere il cursore e leggere le colonne in modo sicuro.
Il ciclo di lettura
String sql = "SELECT id, name, score FROM player ORDER BY score DESC";
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) { // advance; false when no more rows
int id = rs.getInt("id"); // read by column name...
String name = rs.getString(2); // ...or by 1-based index
int score = rs.getInt("score");
System.out.println(id + " " + name + " " + score);
}
}Lettura delle colonne: per nome o per indice
Entrambi i metodi funzionano. I nomi delle colonne sono più chiari e sopravvivono a un riordinamento del SELECT; gli indici delle colonne (a base 1, come tutto in JDBC) sono marginalmente più veloci. I nomi risultano vincenti per la manutenibilità nella quasi totalità del codice. Se in fase di esecuzione si necessita dei nomi, dei tipi o del numero delle colonne — ad esempio per visualizzare una tabella generica — è possibile leggerli dal ResultSetMetaData ottenuto con rs.getMetaData().
Il problema di NULL e wasNull()
I getter primitivi non possono restituire null. getInt restituisce 0 per un NULL SQL, getDouble restituisce 0.0, e così via — indistinguibili da uno zero reale. Quando una colonna è nullable e la differenza è rilevante, occorre chiamare rs.wasNull() immediatamente dopo il getter:
int score = rs.getInt("score");
if (rs.wasNull()) { /* it was SQL NULL, not a zero */ }(Per i tipi oggetto, getObject restituisce direttamente null, il che è spesso più pulito.)
Tipo di cursore e concorrenza
Per impostazione predefinita un ResultSet è TYPE_FORWARD_ONLY e CONCUR_READ_ONLY — ci si sposta in avanti e si legge soltanto. È possibile richiedere TYPE_SCROLL_INSENSITIVE per chiamare previous(), absolute(n) o first(), oppure CONCUR_UPDATABLE per modificare le righe sul posto. Questi costano di più, quindi vanno richiesti solo quando necessario.
Chiuderlo (try-with-resources lo fa)
Un ResultSet mantiene un cursore lato server; lasciarlo aperto spreca risorse del database. La chiusura del relativo Statement lo chiude, e try-with-resources gestisce entrambi. Non restituire mai un ResultSet aperto da un metodo la cui Connection è già stata chiusa.
Un esempio pratico: il cursore forward-only e wasNull
Questo programma modella il cursore ResultSet con un piccolo elenco in memoria — partendo prima della prima riga e avanzando con un passaggio simile a next() — e riproduce il comportamento di getInt che restituisce 0 per NULL con un controllo wasNull, insieme alle costanti del cursore.
Cosa ricavare dall'esecuzione:
- Il cursore inizia all'indice
-1— prima della prima riga — e il passo++cursorrispecchiars.next(): si avanza prima, poi si legge. Questo spiega esattamente perché un ciclo reale èwhile (rs.next())e non legge mai una colonna prima del primonext(). - Le righe vengono lette una alla volta, non caricate come una lista. Il modello usa una lista per semplicità, ma un vero
ResultSettrasmette in streaming le righe dal server, il che gli consente di gestire result set molto più grandi della memoria disponibile. - Il punteggio
nulldi Linus viene stampato come0— la stessa trappola che tendegetInt. Senza il flag non si potrebbe distinguere un punteggio mancante da uno zero reale. - Il marcatore
(wasNull)è il disambiguatore. In JDBC reale si chiamars.wasNull()subito dopo il getter, perché riporta l'ultima colonna letta — leggere prima un'altra colonna cambia la risposta. - Le costanti (
TYPE_FORWARD_ONLY,CONCUR_READ_ONLY,FETCH_FORWARD) descrivono il cursore predefinito e meno costoso: muoversi in avanti, sola lettura. Si opta deliberatamente per cursori scorrevoli o aggiornabili, perché richiedono al server un lavoro maggiore.