W3docs

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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il cursore inizia all'indice -1prima della prima riga — e il passo ++cursor rispecchia rs.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 primo next().
  • Le righe vengono lette una alla volta, non caricate come una lista. Il modello usa una lista per semplicità, ma un vero ResultSet trasmette in streaming le righe dal server, il che gli consente di gestire result set molto più grandi della memoria disponibile.
  • Il punteggio null di Linus viene stampato come 0 — la stessa trappola che tende getInt. Senza il flag non si potrebbe distinguere un punteggio mancante da uno zero reale.
  • Il marcatore (wasNull) è il disambiguatore. In JDBC reale si chiama rs.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.

Esercizio

Pratica
Una query legge una colonna 'score' nullable con rs.getInt('score') e ottiene 0. Come si distingue se il valore era uno 0 reale o un NULL SQL?
Una query legge una colonna 'score' nullable con rs.getInt('score') e ottiene 0. Come si distingue se il valore era uno 0 reale o un NULL SQL?
Was this page helpful?