21.7Elemente einer Datenbank ändern
Bisher haben wir executeQuery(…) benutzt, um Abfragen zu verfassen. Es lassen sich jedoch mit dieser Methode keine Veränderungen an Datensätzen und Einfüge-/Löschoperationen vornehmen, denn executeQuery(…) ist nur für Anfragen gedacht. Für Datenbankänderungen mit den SQL-Kommandos INSERT (fügt neue Zeilen ein), UPDATE (aktualisiert existierende Zeilen), DELETE (löscht Zeilen) gibt es andere Wege.
21.7.1Einzelne INSERT-, UPDATE- oder DELETE-Anweisungen senden
Damit Zeilen verändert werden können, müssen wir in zwei Schritten vorgehen:
eine SQL-Anweisung mit einem UPDATE/INSERT/DELETE aufbauen und
anschließend die Statement-Methode executeUpdate(…) aufrufen. Damit wird die Änderung wirksam.
Neben den Methodennamen gibt es aber noch einen anderen Unterschied zu executeQuery(…): executeUpdate(…) liefert als Rückgabewert ein int, das angibt, wie viele Zeilen von der Änderung betroffen sind, der Rückgabetyp ist kein ResultSet.
[zB]Beispiel
Füge einen Eintrag ein, und aktualisiere einen Eintrag:
int updates1 = stmt.executeUpdate( sqlInsert );
String sqlUpdate = "UPDATE Tabelle SET …;
int updates2 = stmt.executeUpdate( sqlUpdate );
Die Methode executeUpdate(…) gibt zurück, wie viele Zeilen von den Änderungen betroffen sind. Sie ist 0, falls das SQL-Statement nichts bewirkt.
extends Wrapper, AutoCloseable
int executeUpdate(String sql) throws SQLException
Führt eine SQL-Anweisung aus, die Manipulationen an der Datenbank vornimmt. Die SQL-Anweisungen sind in der Regel INSERT-, UPDATE- oder DELETE-Anweisungen. Zurückgegeben wird die Anzahl der veränderten Zeilen oder null, falls eine SQL-Anweisung nichts verändert hat.
21.7.2Aktualisierbares ResultSet
Bisher haben wir das ResultSet nur als rein lesbare Sammlung angesehen, bei der ein Cursor einmal von oben nach unten durchläuft. Allerdings unterstützt JDBC auch flexiblere ResultSets, die auch veränderbar sind und bei denen sich der Cursor frei bewegen lässt – Voraussetzung ist nur, dass der JDBC-Treiber und die Datenbank das auch realisieren, dieses Feature ist optional.
Um sich in einem ResultSet frei bewegen und Updates an Zeilen realisieren zu können, muss im ersten Schritt statt des einfachen createStatement() eine überladene Variante aufgerufen werden:
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE );
Wurde das ResultSet vorher auf eine Zeile bewegt, können die Belegungen einer Spalte mit den updateXXX(column, XXX)-Methoden auf den neuesten Stand gebracht und mit updateRow() dann persistiert werden; cancelRowUpdates() verwirft die angedachten Änderungen. Eine Zeile neu aus der Datenbank zu lesen macht refreshRow(), aus der Datenbank zu löschen übernimmt deleteRow().
Anstatt nur existierende Zeilen zu aktualisieren, kann mit moveToInsertRow() eine neue Zeile eingefügt werden (erst einmal als eine Art Puffer), dann mit den updateXXX(…)-Methoden gefüllt und final mit insertRow() in die Datenbank geschrieben werden.
Bei der Navigation durch ein ResultSet sind weitere Methoden nützlich, bisher haben wir nur next() benutzt: first(), last(), previous(), beforeFirst(), afterLast() und absolute(int) bzw. relative(int).
[zB]Beispiel
Lösche das letzte Ergebnis eines ResultSet rs aus der Datenbank:
rs.deleteRow();
21.7.3Batch-Updates
Das Einfügen und Ändern großer Mengen von Daten kostet viel Zeit, da für jede Modifikation ein INSERT oder UPDATE über ein Statement-Objekt abgewickelt werden muss. Eine Verbesserung stellen Batch-Updates dar, die in einem Rutsch gleich eine ganze Reihe von Daten zur Datenbank transferieren. Anstatt mit execute(…) und deren Varianten zu arbeiten, nutzen wir die Methode executeBatch(). Damit zuvor die einzelnen Aktionen dem Statement-Objekt mitgeteilt werden können, bietet die Klasse die Methoden addBatch(String sql) und clearBatch() an. Die Datenbank führt die Anweisungen in der Reihenfolge aus, wie sie im Batch-Prozess eingefügt wurden. Ein Fehler wird über eine BatchUpdateException angezeigt.
[zB]Beispiel
Wir fügen einige Einträge der Datenbank als Batch hinzu. con sei unser Connection-Objekt:
Statement stmt = con.createStatement();
stmt.addBatch( "INSERT INTO Lieferanten VALUES (x,y,z)" );
stmt.addBatch( "INSERT INTO Lieferanten VALUES (a,b,c)" );
stmt.addBatch( "INSERT INTO Lieferanten VALUES (d,e,f)" );
int[] updateCounts = updateCounts = s.executeBatch();
}
catch ( BatchUpdateException e ) { /* Behandeln! */ }
catch ( SQLException e ) { /* Behandeln! */ }
Nach dem Abarbeiten von executeBatch() erhalten wir als Rückgabewert ein int-Feld mit den Ergebnissen der Ausführung. Dies liegt daran, dass in der Batch-Verarbeitung ganz unterschiedliche Anweisungen vorgenommen werden können und jede davon einen unterschiedlichen Rückgabewert verwendet.
Soll der gesamte Ablauf als Transaktion gewürdigt werden, so setzen wir im try-Block den AutoCommit-Modus auf false, damit nicht jede SQL-Anweisung als einzelne Transaktion gewertet wird. Im Fall eines Fehlers müssen wir im catch-Block ein Rollback ausführen. Übertragen wir dies auf das obere Beispiel, dann müssen nur die beiden Anweisungen für die Transaktion eingesetzt werden:
con.setAutoCommit( false );
Statement s = …
…
}
catch ( BatchUpdateException e ) {
con.rollback();
}