W3docs

Java JDBC ResultSet

Zeilen einer SQL-Abfrage in Java mit dem ResultSet-Interface iterieren und lesen.

Ein ResultSet ist ein Cursor über die Zeilen, die eine Abfrage zurückgegeben hat. Er hält nicht alle Zeilen gleichzeitig im Speicher; er zeigt jeweils auf eine Zeile, und man bewegt sich vorwärts. Entscheidend ist: Ein frischer ResultSet ist vor der ersten Zeile positioniert — man muss einmal next() aufrufen, um zur ersten Zeile zu gelangen. Deshalb lautet jede Leseschleife while (rs.next()).

Ein ResultSet erhält man als Rückgabe einer Abfrage, die über ein Statement oder ein PreparedStatement ausgeführt wird. Diese Seite erklärt, wie man den Cursor durchläuft und Spalten sicher liest.

Die Leseschleife

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);
  }
}

Spalten lesen: nach Name oder Index

Beide Varianten funktionieren. Spaltennamen sind übersichtlicher und überleben ein umsortiertes SELECT; Spaltenindizes (1-basiert, wie alles in JDBC) sind geringfügig schneller. Namen gewinnen für die Wartbarkeit in fast jedem Code. Wenn man Spaltennamen, -typen oder -anzahl zur Laufzeit benötigt — etwa um eine generische Tabelle darzustellen — liest man sie aus dem ResultSetMetaData, das man mit rs.getMetaData() erhält.

Das NULL-Problem und wasNull()

Die primitiven Getter können kein null zurückgeben. getInt liefert 0 für ein SQL-NULL, getDouble liefert 0.0 usw. — nicht zu unterscheiden von einer echten Null. Wenn eine Spalte nullable ist und der Unterschied eine Rolle spielt, ruft man direkt nach dem Getter rs.wasNull() auf:

int score = rs.getInt("score");
if (rs.wasNull()) { /* it was SQL NULL, not a zero */ }

(Bei Objekttypen gibt getObject direkt null zurück, was oft übersichtlicher ist.)

Cursor-Typ und Nebenläufigkeit

Standardmäßig ist ein ResultSet vom Typ TYPE_FORWARD_ONLY und CONCUR_READ_ONLY — man bewegt sich vorwärts und liest nur. Mit TYPE_SCROLL_INSENSITIVE kann man previous(), absolute(n) oder first() aufrufen, mit CONCUR_UPDATABLE lassen sich Zeilen direkt bearbeiten. Diese Modi sind aufwändiger, also sollte man sie nur bei Bedarf anfordern.

Schließen (try-with-resources erledigt das)

Ein ResultSet hält einen serverseitigen Cursor offen; ein offener Cursor verschwendet Datenbankressourcen. Das Schließen des zugehörigen Statement schließt ihn, und try-with-resources erledigt beides. Einen offenen ResultSet sollte man niemals aus einer Methode zurückgeben, deren Connection bereits geschlossen wurde.

Ein vollständiges Beispiel: der Forward-only-Cursor und wasNull

Dieses Programm modelliert den ResultSet-Cursor mit einer kleinen In-Memory-Liste — beginnend vor der ersten Zeile, mit einem next()-ähnlichen Schritt vorwärtsbewegend — und reproduziert das Verhalten von getInt-gibt-0-für-NULL zusammen mit einem wasNull-Check sowie den Cursor-Konstanten.

java— editable, runs on the server

Was man aus der Ausgabe mitnehmen kann:

  • Der Cursor startet bei Index -1vor der ersten Zeile — und der ++cursor-Schritt spiegelt rs.next() wider: erst vorrücken, dann lesen. Genau deshalb lautet eine echte Schleife while (rs.next()) und liest niemals eine Spalte vor dem ersten next().
  • Zeilen werden einzeln gelesen, nicht als Liste geladen. Das Modell verwendet eine Liste der Einfachheit halber, aber ein echter ResultSet streamt Zeilen vom Server, was ihm ermöglicht, Ergebnismengen zu verarbeiten, die weit größer als der Arbeitsspeicher sind.
  • Linus' null-Score wurde als 0 ausgegeben — die gleiche Falle, die getInt stellt. Ohne das Flag lässt sich ein fehlender Score nicht von einer echten Null unterscheiden.
  • Der (wasNull)-Marker ist das Unterscheidungsmerkmal. In echtem JDBC ruft man rs.wasNull() direkt nach dem Getter auf, weil er über die zuletzt gelesene Spalte berichtet — liest man zuerst eine andere Spalte, ändert sich die Antwort.
  • Die Konstanten (TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, FETCH_FORWARD) beschreiben den standardmäßigen, günstigsten Cursor: vorwärtsbewegen, nur lesen. Scrollbare oder aktualisierbare Cursor wählt man bewusst, weil sie den Server mehr beanspruchen.

Übungen

Übung
Eine Abfrage liest eine nullable Spalte 'score' mit rs.getInt('score') und erhält den Wert 0 zurück. Wie stellt man fest, ob es sich um eine echte 0 oder ein SQL NULL handelt?
Eine Abfrage liest eine nullable Spalte 'score' mit rs.getInt('score') und erhält den Wert 0 zurück. Wie stellt man fest, ob es sich um eine echte 0 oder ein SQL NULL handelt?
Was this page helpful?