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.
Was man aus der Ausgabe mitnehmen kann:
- Der Cursor startet bei Index
-1— vor der ersten Zeile — und der++cursor-Schritt spiegeltrs.next()wider: erst vorrücken, dann lesen. Genau deshalb lautet eine echte Schleifewhile (rs.next())und liest niemals eine Spalte vor dem erstennext(). - Zeilen werden einzeln gelesen, nicht als Liste geladen. Das Modell verwendet eine Liste der Einfachheit halber, aber ein echter
ResultSetstreamt Zeilen vom Server, was ihm ermöglicht, Ergebnismengen zu verarbeiten, die weit größer als der Arbeitsspeicher sind. - Linus'
null-Score wurde als0ausgegeben — die gleiche Falle, diegetIntstellt. 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 manrs.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.