18.6.2 | Abfrage von Werten |
In der Datenbank sind die Daten zwar nur in einem Datentyp gespeichert, in JDBC kann man jedoch bei der Abfrage von Werten automatisch Typ-Konvertierungen durchführen, sofern der gewünschte Typ mit dem Datentyp in der Datenbank kompatibel ist. Welche Konvertierungen genau zulässig sind, zeigt Abbildung 18.13. Dort ist angegeben, welche Datentypen aus der Datenbank mit welchen getXXX()-Methoden abgerufen werden können.Die Mehrheit der aufgeführten Methoden führt eine Konvertierung in primitive Java-Datentypen durch. Über die Methode getObject() kann man sich jeden Wert in der Ergebnismenge als Java-Objekt zurückgeben lassen. Die Verwendung von getObject() ist obligatorisch für den Zugriff auf Objekt-Typen.
Beim Aufruf von getBoolean() ist zu beachten, dass der Treiber zunächst versucht, den abgefragten Wert in eine Zahl zu konvertieren (falls es sich um einen alphanumerischen Typ handelt) und anschließend einen numerischen Vergleich durchführt. Ist der Wert gleich 0, wird false, ansonsten true zurückgeliefert.
Versucht man einen Wert über eine Methode abzufragen, die keine Konvertierung in einen adäquaten Typ durchführen kann, wird eine SQLException ausgelöst.
Ruft man die Daten über eine getXXX()-Methode ab, die das Ergebnis in einem primitiven Rückgabewert zurückliefert, wird vom Treiber ein Default-Wert zurückgegeben, falls in der Datenbank ein NULL-Wert enthalten ist. Bei der Abfrage von Zahlenwerten ist dieser Default-Wert 0, bei booleschen Typen false. Das heißt, beim Aufruf dieser Methoden kann man nicht unterscheiden, ob der Wert wirklich in der Datenbank gespeichert ist, oder ob es sich um einen vom Treiber generierten Default-Wert handelt, der als Ersatz für einen NULL-Wert zurückgegeben wird. Man kann jedoch auf folgende Arten herausfinden, ob in der Datenbank tatsächlich ein NULL-Wert gespeichert ist:Folgendes Beispiel zeigt, wie bei einer Abfrage geprüft wird, ob ein gültiger Preis verfügbar ist:
- Aufruf einer kompatiblen getXXX()-Methode, die den gespeicherten Wert als Verweistyp zurückliefert.
Die Methode getObject() liefert für jeden Datentyp null zurück, falls es sich in der Datenbank um einen NULL-Wert handelt.- Aufruf der Methode wasNull().
Diese Methode hat keine Parameter und liefert true, wenn der zuletzt über eine getXXX() zurückgelieferte Wert in der Datenbank einen NULL-Wert hatte.float preis; while(rs.next()) { System.out.println(rs.getString("titel")); preis = rs.getFloat("preis"); // Ist der Preis in der Datenbank NULL? if(rs.wasNull()) System.out.println("Noch kein Preis verfügbar !"); else System.out.println("Preis: "+preis); }
Während in JDBC 1.0 nur das sequenzielle Abfragen der Ergebniszeilen möglich ist, kann man sich ab JDBC 2.0 in einer Ergebnismenge auch wieder zurückbewegen bzw. direkt an eine bestimmte Zeile in der Ergebnismenge springen. Im Folgenden werden die im Interface ResultSet definierten Konstanten aufgelistet, mit denen die Positionierbarkeit eingestellt werden kann:Positionierbare Ergebnismengen werden verwendet, indem man beim Erzeugen des Statement die entsprechende Konstante übergibt (z. B. createStatement()). Ein Beispiel dazu ist im folgenden Abschnitt enthalten, da die Positionierbarkeit immer zusammen mit der Veränderbarkeit angegeben wird.
- FORWARD_ONLY
Erlaubt nur die Positionierung auf den jeweils nächsten Satz (Voreinstellung bei JDBC 1.0).- TYPE_SCROLL_INSENSITIVE
Ermöglicht das Vorwärts- und Rückwärts-Scrollen in der Ergebnismenge sowie die absolute Positionierung auf einzelne Datensätze. Während des Zugriffs sind Änderungen, die in anderen Sitzungen durchgeführt werden, für die Ergebnismenge nicht sichtbar.- TYPE_SCROLL_SENSITIVE
Ermöglicht das Vorwärts- und Rückwärtsscrollen in der Ergebnismenge sowie die absolute Positionierung auf einzelne Datensätze. Während des Zugriffs sind Änderungen, die in anderen Sitzungen durchgeführt werden, für die Ergebnismenge sichtbar.
Wenn man sich für die Verwendung einer positionierbaren Ergebnismenge entschieden hat, stellt das Interface ResultSet folgende Methoden zur Positionierung bereit:Alle diese Methoden haben den Ergebnistyp boolean. Sie liefern true zurück, wenn die neue Positionierung geglückt ist. Andernfalls wird false als Ergebnis zurückgegeben. Das ist z. B. dann der Fall, wenn last() aufgerufen wird, das ResultSet-Exemplar jedoch keine Datensätze enthält oder wenn der an absolute() übergebene Wert größer ist als die Anzahl der Datensätze in der Ergebnismenge.
- boolean previous()
Springt einen Datensatz zurück.- boolean first()
Springt zum ersten Datensatz.- boolean last()
Springt zum letzten Datensatz.- boolean beforeFirst()
Springt vor den ersten Datensatz.- boolean afterLast()
Springt hinter den letzten Datensatz.- boolean absolute(int)
Springt zum angegebenen Datensatz in der Ergebnismenge. Der übergebene int-Wert gibt die Nummer des Datensatzes an, zu dem gesprungen werden soll, ausgehend vom ersten Satz in der Ergebnismenge. Bei Angabe eines negativen Werts wird vom letzten Datensatz in der Ergebnismenge zurückgezählt.- boolean relative(int)
Verändert die aktuelle Position um den übergebenen Wert relativ zum aktuellen Satz. Liegt die neue Position außerhalb der Ergebnismenge, wird vor den ersten bzw. hinter den letzten Datensatz gesprungen. Es ist zu beachten, dass next() und relative(1) nicht unbedingt immer dasselbe Ergebnis liefern. Ein Aufruf von relative() ist nämlich nur dann möglich, wenn es einen aktuellen Datensatz gibt. Das ist z. B. nicht der Fall, wenn sich der Positionszeiger vor dem ersten Datensatz befindet. Bei einem Aufruf von relative() in einer derartigen Situation wird eine SQLException ausgelöst.
Seit JDBC 2.0 gibt es die Möglichkeit, über ein ResultSet den aktuellen Datensatz zu verändern. Hierzu definiert ResultSet updateXXX()-Funktionen, wobei XXX durch einen konkreten Datentyp ersetzt werden muss. Ob die Daten einer Ergebnismenge aktualisiert werden können wird beim Erzeugen des zugrundeliegenden Statement-Exemplars durch folgende Konstanten definiert:
- CONCUR_READ_ONLY
Erlaubt keine Durchführung von Änderungen über die Ergebnismenge (Voreinstellung bei JDBC 1.0).- CONCUR_UPDATABLE
Erlaubt die Durchführung von Änderungen über die Ergebnismenge.
Bei der Durchführung von Änderungen muss man folgendermaßen vorgehen:Der Ablauf bei der Veränderung von Daten über Ergebnismengen ist für INSERT, UPDATE und DELETE in Abbildung 18.14 dargestellt.
- Erzeugen eines Statement-Exemplars vom Typ CONCUR_UPDATABLE.
- Ausführen einer Abfrage und Positionierung des ResultSet auf den zu verändernden Datensatz.
- Durchführung der Änderung über die Methoden des ResultSet. Hierzu werden die Methoden updateRow(), insertRow() und deleteRow() benutzt.
Folgendes Beispiel zeigt an einem Quellcode-Ausschnitt, wie über die Methoden der Klasse ResultSet in der Datenbank eine UPDATE-Operation durchgeführt wird:
Connection con; ... Statement stmt; stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); String sql = "SELECT tit_id, titel, preis FROM titel WHERE tit_id = 2"; ResultSet rs = stmt.executeQuery(sql); // Positionierung auf den ersten Datensatz rs.next(); // Durchführung der Änderung im internen Puffer rs.updateFloat(2, 68); // Übertragen des geä;nderten Datensatzes an das Datenbanksystem rs.updateRow(); ...Nach der Positionierung auf den zu ändernden Datensatz wird durch Aufruf von updateFloat() der Wert zunächst in einem internen Puffer aktualisiert. Das Interface ResultSet definiert verschiedene updateXXX()-Methoden für die Aktualisierung von Daten verschiedener Typen (genau wie bei den getXXX()-Methoden zur Abfrage von Daten). Durch Aufruf von updateRow() wird der intern zwischengespeicherte Datensatz in die Datenbank geschrieben. Durch Aufruf von cancelRowUpdates() können die Änderungen im internen Puffer wieder rückgängig gemacht werden.
Beim Einfügen von neuen Datensätzen muss man den Satzzeiger zunächst auf den Einfüge-Datensatz positionieren. Dies geschieht durch Aufruf von moveToInsertRow(). Danach werden die Daten mit den updateXXX()-Methoden gesetzt, die auch bei der Durchführung der UPDATE-Operation benutzt werden. Hierbei müssen alle Spalten mit Werten belegt werden, die in der Datenbank nicht NULL sein dürfen. Danach wird durch Ausführung von insertRow() die INSERT-Anweisung an das Datenbanksystem geschickt. Durch Aufruf von moveToCurrentRow() wird der Satzzeiger anschließend wieder auf den Datensatz positioniert, auf dem er sich vor dem Aufruf von moveToInsertRow() bereits befand.
Die Ausführung einer DELETE-Anweisung ist die Einfachste von allen drei DML-Operationen. Der Satzzeiger muss hierzu lediglich auf den zu löschenden Datensatz positioniert und anschließend die Methode deleteRow() aufgerufen werden:Connection con; ... Statement stmt; stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); String sql = "SELECT tit_id, titel, preis FROM titel WHERE tit_id = 2"; ResultSet rs = stmt.executeQuery(sql); // Positionierung auf den ersten Datensatz rs.next(); //Löschen der Zeile rs.deleteRow(); ...Nach dem Aufruf von deleteRow() wird bei Scroll-sensitiven Ergebnismengen derjenige Datensatz aktuell, der sich direkt vor dem gelöschten Datensatz befand.
Alle Änderungen werden letztendlich persistent in der Datenbank festgeschrieben, wenn die aktuelle Transaktion abgeschlossen wird (siehe Abschnitt 18.11).
Aktualisierbare und Scroll-sensitive Ergebnismengen können nicht mit allen SQL-Anweisungen erzeugt werden. Die Einschränkungen für die beiden Arten von Ergebnismengen sind in Tabelle 18.6 aufgeführt.
Tabelle 18.6: Einschränkungen beim Erzeugen von Scroll-sensitiven und positionierbaren Ergebnismengen. Einschränkung aktualisierbar S.-sensitiv Joins SELECT * ORDER BY GROUP BY Selektion von NOT NULL Spalten nur bei INSERT abgeleitete Spalten Die Einschränkungen zur Aktualisierung entsprechen den Einschränkungen, die man auch bei aktualisierbaren Views vorfindet.
Wenn ein Statement-Objekt eine Ergebnismenge eines bestimmten Typs erzeugt und die übergebene SQL-Anweisung ein Konstrukt enthält, das dem gewünschten Typ widerspricht, generiert der Treiber eine SQL-Warnung und gibt ein ResultSet-Exemplar zurück, das dem angeforderten Typ möglichst gut entspricht. Von welchem Typ die zurückgelieferte Ergebnismenge tatsächlich ist, kann man über die Methoden getType() bzw. getConcurrency() ermitteln. Die Methoden liefern die entsprechenden Konstanten zurück, die auch beim Erzeugen benutzt werden.
Während Daten in der Ergebnismenge abgerufen werden, können Veränderungen an den zugrunde liegenden Daten durchgeführt werden. Man unterscheidet hierbei zwei Arten von Änderungen:Abhängig vom Typ der Ergebnismenge sind die Veränderungen automatisch sichtbar oder auch nicht. Sichtbar bedeutet in diesem Zusammenhang, dass veränderte Daten während der Verarbeitung der Ergebnismenge über die getXXX()-Methoden abgerufen werden können.
- Änderungen, die außerhalb des ResultSet direkt in der Datenbank durchgeführt werden (externe Änderungen).
- Änderungen, die über die Methoden des ResultSet durchgeführt werden (interne Änderungen).
Prinzipiell gilt, dass für die Sichtbarkeit externer Änderungen die Ergebnismenge scrollsensitiv und für die Sichtbarkeit interner Änderungen aktualisierbar sein muss, da sonst überhaupt keine Aktualisierung der Ergebnismenge durchgeführt werden kann. In Abbildung 18.15 ist die Sichtbarkeit von DML-Operationen in den verschiedenen ResultSet-Varianten dargestellt.Daran kann man sehen:
- Einfüge-Operationen sind nie sichtbar.
- Lösch-Operationen sind nur dann sichtbar, wenn sie über einen positionierbaren Cursor intern durchgeführt werden.
- Aktualisierungs-Operationen sind immer dann sichtbar, wenn sie intern durchgeführt werden. Extern sind sie nur über eine Scroll-sensitive Ergebnismenge sichtbar.
Wenn ein JDBC-Treiber intern Zeilen zwischenpuffert, werden externe Änderungen in der Regel erst dann sichtbar, wenn die Zeilen neu vom Server angefordert werden. Daher wirken sich externe Änderungen unter Umständen nicht sofort auf die Ergebnismenge aus. Durch Änderung der Abfragegröße kann man die Anzahl der zwischengepufferten Zeilen beeinflussen (siehe Abschnitt 18.10.1).
Alternativ kann man eine Aktualisierung der Zeilen explizit durch Aufruf der Methode refreshRow() durchführen.