Anleitung zur JDBC ResultSet-Schnittstelle

1. Übersicht

Die JDBC-API (Java Database Connectivity) bietet Zugriff auf die Datenbank von einer Java-Anwendung aus. Wir können JDBC verwenden, um eine Verbindung zu einer beliebigen Datenbank herzustellen, solange der unterstützte JDBC-Treiber verfügbar ist.

Das ResultSet ist eine Datentabelle, die durch Ausführen von Datenbankabfragen generiert wird. In diesem Tutorial werden wir uns die ResultSet- API genauer ansehen .

2. Generieren eines ResultSet

Zuerst rufen wir ein ResultSet ab, indem wir executeQuery () für jedes Objekt aufrufen, das die Anweisungsschnittstelle implementiert. Sowohl das PreparedStatement als auch das CallableStatement sind Subschnittstellen von Statement :

PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees"); ResultSet rs = pstmt.executeQuery();

Das ResultSet- Objekt verwaltet einen Cursor, der auf die aktuelle Zeile der Ergebnismenge zeigt. Wir werden next () in unserem ResultSet verwenden , um die Datensätze zu durchlaufen.

Als Nächstes verwenden wir die getX () -Methoden, während wir die Ergebnisse durchlaufen, um die Werte aus den Datenbankspalten abzurufen , wobei X der Datentyp der Spalte ist. Tatsächlich werden wir den getX () -Methoden Datenbankspaltennamen bereitstellen :

while(rs.next()) { String name = rs.getString("name"); Integer empId = rs.getInt("emp_id"); Double salary = rs.getDouble("salary"); String position = rs.getString("position"); } 

Ebenso kann die Indexnummer der Spalte mit den Methoden getX () anstelle des Spaltennamens verwendet werden. Die Indexnummer ist die Reihenfolge der Spalten in der SQL-Select-Anweisung.

Wenn in der select-Anweisung keine Spaltennamen aufgeführt sind, ist die Indexnummer die Reihenfolge der Spalten in der Tabelle. Die Spaltenindexnummerierung beginnt bei eins:

Integer empId = rs.getInt(1); String name = rs.getString(2); String position = rs.getString(3); Double salary = rs.getDouble(4); 

3. Abrufen von MetaData aus dem ResultSet

In diesem Abschnitt erfahren Sie, wie Sie Informationen zu den Spalteneigenschaften und -typen in einem ResultSet abrufen .

Verwenden wir zunächst die Methode getMetaData () in unserem ResultSet , um die ResultSetMetaData zu erhalten :

ResultSetMetaData metaData = rs.getMetaData();

Als nächstes ermitteln wir die Anzahl der Spalten in unserem ResultSet :

Integer columnCount = metaData.getColumnCount();

Darüber hinaus können wir eine der folgenden Methoden für unser Metadatenobjekt verwenden, um die Eigenschaften jeder Spalte abzurufen:

  • getColumnName (int columnNumber) - um den Namen der Spalte abzurufen
  • getColumnLabel (int columnNumber) - um auf die Bezeichnung der Spalte zuzugreifen, die in der SQL-Abfrage nach AS angegeben wird
  • getTableName (int columnNumber) - um den Tabellennamen abzurufen , zu dem diese Spalte gehört
  • getColumnClassName (int columnNumber) - um den Java-Datentyp der Spalte abzurufen
  • getColumnTypeName (int columnNumber) - um den Datentyp der Spalte in der Datenbank abzurufen
  • getColumnType (int columnNumber) - um den SQL-Datentyp der Spalte abzurufen
  • isAutoIncrement (int columnNumber) - Gibt an, ob die Spalte automatisch inkrementiert wird
  • isCaseSensitive (int columnNumber) - Gibt an, ob der Spaltenfall von Bedeutung ist
  • isSearchable (int columnNumber) - schlägt vor, ob die Spalte in der where- Klausel der SQL-Abfrage verwendet werden kann
  • isCurrency (int columnNumber) - signalisiert, ob die Spalte einen Barwert enthält
  • isNullable (int column) - kehrt auf Null , wenn die Spalte nicht null sein kann, ein , wenn die Spalte einen Nullwert enthalten kann, und zwei , wenn NULL - Zulässigkeit der Säule ist unbekannt
  • isSigned (int columnNumber) - gibt true zurück , wenn die Werte in der Spalte signiert sind, andernfalls false

Lassen Sie uns die Spalten durchlaufen, um ihre Eigenschaften zu erhalten:

for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) { String catalogName = metaData.getCatalogName(columnNumber); String className = metaData.getColumnClassName(columnNumber); String label = metaData.getColumnLabel(columnNumber); String name = metaData.getColumnName(columnNumber); String typeName = metaData.getColumnTypeName(columnNumber); int type = metaData.getColumnType(columnNumber); String tableName = metaData.getTableName(columnNumber); String schemaName = metaData.getSchemaName(columnNumber); boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber); boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber); boolean isCurrency = metaData.isCurrency(columnNumber); boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber); boolean isReadOnly = metaData.isReadOnly(columnNumber); boolean isSearchable = metaData.isSearchable(columnNumber); boolean isReadable = metaData.isReadOnly(columnNumber); boolean isSigned = metaData.isSigned(columnNumber); boolean isWritable = metaData.isWritable(columnNumber); int nullable = metaData.isNullable(columnNumber); }

4. Navigieren Sie im ResultSet

Wenn wir ein ResultSet erhalten , befindet sich die Position des Cursors vor der ersten Zeile. Darüber hinaus bewegt sich das ResultSet standardmäßig nur in Vorwärtsrichtung. Wir können jedoch ein scrollbares ResultSet für andere Navigationsoptionen verwenden.

In diesem Abschnitt werden die verschiedenen Navigationsoptionen erläutert.

4.1. ResultSet- Typen

Der ResultSet- Typ gibt an, wie wir durch den Datensatz steuern:

  • TYPE_FORWARD_ONLY - Die Standardoption, bei der sich der Cursor von Anfang nach Ende bewegt
  • TYPE_SCROLL_INSENSITIVE - Unser Cursor kann sich sowohl vorwärts als auch rückwärts durch den Datensatz bewegen. Wenn sich die zugrunde liegenden Daten beim Durchlaufen des Datasets ändern, werden sie ignoriert. Das Dataset enthält die Daten ab dem Zeitpunkt, an dem die Datenbankabfrage das Ergebnis zurückgibt
  • TYPE_SCROLL_SENSITIVE - Ähnlich wie beim scrollunempfindlichen Typ, jedoch spiegelt der Datensatz für diesen Typ Änderungen an den zugrunde liegenden Daten sofort wider

Nicht alle Datenbanken unterstützen alle ResultSet- Typen. Überprüfen wir also, ob der Typ unterstützt wird, indem wir den supportResultSetType für unser DatabaseMetaData- Objekt verwenden:

DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);

4.2. Bildlauffähiges Ergebnisset

Um ein scrollbares ResultSet zu erhalten , müssen wir bei der Vorbereitung der Anweisung einige zusätzliche Parameter übergeben .

Zum Beispiel würden wir ein scrollbares ResultSet erhalten, indem wir entweder TYPE_SCROLL_INSENSITIVE oder TYPE_SCROLL_SENSITIVE als ResultSet- Typ verwenden:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery(); 

4.3. Navigationsoptionen

Wir können eine der folgenden Optionen für ein scrollbares ResultSet verwenden :

  • next () - geht von der aktuellen Position zur nächsten Zeile über
  • previous () - wechselt zur vorherigen Zeile
  • first () - navigiert zur ersten Zeile des ResultSet
  • last () - springt zur letzten Zeile
  • beforeFirst () - bewegt sich zum Start; Aufruf next () auf unserer ResultSet nach dem Aufruf dieser Methode gibt die erste Zeile aus unserem ResultSet
  • afterLast () - springt zum Ende; Das Aufrufen von previous () in unserem ResultSet nach Ausführung dieser Methode gibt die letzte Zeile aus unserem ResultSet zurück
  • relative (int numOfRows) - Gehen Sie von der aktuellen Position durch die numOfRows vorwärts oder rückwärts
  • absolute (int rowNumber) - springt zur angegebenen rowNumber

Sehen wir uns einige Beispiele an:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // iterate through the results from first to last } rs.beforeFirst(); // jumps back to the starting point, before the first row rs.afterLast(); // jumps to the end of resultset rs.first(); // navigates to the first row rs.last(); // goes to the last row rs.absolute(2); //jumps to 2nd row rs.relative(-1); // jumps to the previous row rs.relative(2); // jumps forward two rows while (rs.previous()) { // iterates from current row to the first row in backward direction } 

4.4. ResultSet Row Count

Verwenden wir getRow () , um die aktuelle Zeilennummer unseres ResultSet abzurufen .

Zuerst navigieren wir zur letzten Zeile des ResultSet und verwenden dann getRow () , um die Anzahl der Datensätze abzurufen :

rs.last(); int rowCount = rs.getRow();

5. Aktualisieren von Daten in einem ResultSet

Standardmäßig ist das ResultSet schreibgeschützt. Wir können jedoch ein aktualisierbares ResultSet verwenden , um die Zeilen einzufügen, zu aktualisieren und zu löschen.

5.1. ResultSet Parallelität

Der Parallelitätsmodus zeigt an, ob unser ResultSet die Daten aktualisieren kann.

Die Option CONCUR_READ_ONLY ist die Standardeinstellung und sollte verwendet werden, wenn die Daten nicht mit unserem ResultSet aktualisiert werden müssen .

Wenn wir jedoch die Daten in unserem ResultSet aktualisieren müssen , sollte die Option CONCUR_UPDATABLE verwendet werden.

Nicht alle Datenbanken unterstützen alle Parallelitätsmodi für alle ResultSet- Typen . Daher müssen wir mithilfe der Methode supportResultSetConcurrency () überprüfen, ob unser gewünschter Typ und Parallelitätsmodus unterstützt werden :

DatabaseMetaData dbmd = dbConnection.getMetaData(); boolean isSupported = dbmd.supportsResultSetConcurrency( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); 

5.2. Erhalten eines aktualisierbaren ResultSet

Um ein aktualisierbares ResultSet zu erhalten , müssen wir bei der Vorbereitung der Anweisung einen zusätzlichen Parameter übergeben . Verwenden Sie dazu CONCUR_UPDATABLE als dritten Parameter, während Sie eine Anweisung erstellen:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt.executeQuery();

5.3. Aktualisieren einer Zeile

In diesem Abschnitt aktualisieren wir eine Zeile mit dem aktualisierbaren ResultSet, das im vorherigen Abschnitt erstellt wurde.

Wir können Daten in einer Zeile aktualisieren, indem wir updateX () -Methoden aufrufen und die zu aktualisierenden Spaltennamen und Werte übergeben. Wir können jeden unterstützten Datentyp anstelle von X in der updateX () -Methode verwenden.

Lassen Sie uns die Spalte "Gehalt" aktualisieren , die vom Typ double ist :

rs.updateDouble("salary", 1100.0);

Beachten Sie, dass dadurch nur die Daten im ResultSet aktualisiert werden , die Änderungen jedoch noch nicht in der Datenbank gespeichert werden.

Rufen Sie abschließend updateRow () auf, um die Updates in der Datenbank zu speichern :

rs.updateRow(); 

Anstelle der Spaltennamen können wir den Spaltenindex an die updateX () -Methoden übergeben. Dies ähnelt der Verwendung des Spaltenindex zum Abrufen der Werte mit den Methoden getX () . Wenn Sie entweder den Spaltennamen oder den Index an die updateX () -Methoden übergeben, erhalten Sie dasselbe Ergebnis:

rs.updateDouble(4, 1100.0); rs.updateRow(); 

5.4. Einfügen einer Zeile

Fügen wir nun mit unserem aktualisierbaren ResultSet eine neue Zeile ein .

Zuerst bewegen wir den Cursor mit moveToInsertRow () , um eine neue Zeile einzufügen:

rs.moveToInsertRow();

Als nächstes müssen wir updateX () -Methoden aufrufen , um die Informationen zur Zeile hinzuzufügen. Wir müssen Daten für alle Spalten in der Datenbanktabelle bereitstellen. Wenn wir nicht für jede Spalte Daten bereitstellen, wird der Standardspaltenwert verwendet:

rs.updateString("name", "Venkat"); rs.updateString("position", "DBA"); rs.updateDouble("salary", 925.0);

Rufen wir dann insertRow () auf, um eine neue Zeile in die Datenbank einzufügen:

rs.insertRow();

Verwenden wir abschließend moveToCurrentRow (). Dadurch wird die Cursorposition auf die Zeile zurückgesetzt, in der wir uns befanden, bevor wir mit der Methode moveToInsertRow () eine neue Zeile einfügten :

rs.moveToCurrentRow();

5.5. Zeile löschen

In diesem Abschnitt löschen wir eine Zeile mit unserem aktualisierbaren ResultSet .

Zuerst navigieren wir zu der Zeile, die wir löschen möchten. Dann rufen wir die Methode deleteRow () auf, um die aktuelle Zeile zu löschen:

rs.absolute(2); rs.deleteRow();

6. Haltbarkeit

Die Haltbarkeit bestimmt, ob unser ResultSet am Ende einer Datenbanktransaktion geöffnet oder geschlossen wird.

6.1. Haltbarkeitstypen

Use CLOSE_CURSORS_AT_COMMIT if the ResultSet is not required after the transaction is committed.

Use HOLD_CURSORS_OVER_COMMIT to create a holdable ResultSet. A holdable ResultSet is not closed even after the database transaction is committed.

Not all databases support all the holdability types.

So, let's check if the holdability type is supported using supportsResultSetHoldability() on our DatabaseMetaData object. Then, we'll get the default holdability of the database using getResultSetHoldability():

boolean isCloseCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT); boolean isOpenCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); boolean defaultHoldability = dbmd.getResultSetHoldability();

6.2. Holdable ResultSet

To create a holdable ResultSet, we need to specify the holdability type as the last parameter while creating a Statement. This parameter is specified after the concurrency mode.

Note that if we're using Microsoft SQL Server (MSSQL), we have to set holdability on the database connection, rather than on the ResultSet:

dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);

Let's see this in action. First, let's create a Statement, setting the holdability to HOLD_CURSORS_OVER_COMMIT:

Statement pstmt = dbConnection.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT)

Now, let's update a row while retrieving the data. This is similar to the update example we discussed earlier, except that we'll continue to iterate through the ResultSet after committing the update transaction to the database. This works fine on both MySQL and MSSQL databases:

dbConnection.setAutoCommit(false); ResultSet rs = pstmt.executeQuery("select * from employees"); while (rs.next()) { if(rs.getString("name").equalsIgnoreCase("john")) { rs.updateString("name", "John Doe"); rs.updateRow(); dbConnection.commit(); } } rs.last(); 

It's worth noting that MySQL supports only HOLD_CURSORS_OVER_COMMIT. So, even if we use CLOSE_CURSORS_AT_COMMIT, it will be ignored.

The MSSQL database supports CLOSE_CURSORS_AT_COMMIT. This means that the ResultSet will be closed when we commit the transaction. As a result, an attempt to access the ResultSet after committing the transaction results in a ‘Cursor is not open error’. Therefore, we can’t retrieve further records from the ResultSet.

7. Fetch Size

Typically, when loading data into a ResultSet, the database drivers decide on the number of rows to fetch from the database. On a MySQL database, for example, the ResultSet normally loads all the records into memory at once.

Sometimes, however, we may need to deal with a large number of records that won't fit into our JVM memory. In this case, we can use the fetch size property either on our Statement or ResultSet objects to limit the number of records initially returned.

Whenever additional results are required, ResultSet fetches another batch of records from the database. Using the fetch size property, we can provide a suggestion to the database driver on the number of rows to fetch per database trip. The fetch size we specify will be applied to the subsequent database trips.

If we don't specify the fetch size for our ResultSet, then the fetch size of the Statement is used. If we don't specify fetch size for either the Statement or the ResultSet, then the database default is used.

7.1. Using Fetch Size on Statement

Now, let's see the fetch size on Statement in action. We'll set the fetch size of the Statement to 10 records. If our query returns 100 records, then there will be 10 database round trips, loading 10 records each time:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // iterate through the resultset }

7.2. Using Fetch Size on ResultSet

Now, let's change the fetch size in our previous example using the ResultSet.

First, we'll use the fetch size on our Statement. This allows our ResultSet to initially load 10 records after executing the query.

Then, we'll modify the fetch size on the ResultSet. This will override the fetch size we earlier specified on our Statement. So, all the subsequent trips will load 20 records until all the records are loaded.

As a result, there will be only 6 database trips to load all the records:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); rs.setFetchSize(20); while (rs.next()) { // iterate through the resultset }

Finally, we'll see how to modify the fetch size of the ResultSet while iterating the results.

Similar to the previous example, we'll first set the fetch size to 10 on our Statement. So, our first 3 database trips will load 10 records per each trip.

Und dann ändern wir die Abrufgröße in unserem ResultSet auf 20, während wir den 30. Datensatz lesen. Die nächsten 4 Fahrten laden also 20 Datensätze pro Fahrt.

Daher benötigen wir 7 Datenbankfahrten, um alle 100 Datensätze zu laden:

PreparedStatement pstmt = dbConnection.prepareStatement( "select * from employees", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); pstmt.setFetchSize(10); ResultSet rs = pstmt.executeQuery(); int rowCount = 0; while (rs.next()) { // iterate through the resultset if (rowCount == 30) { rs.setFetchSize(20); } rowCount++; }

8. Fazit

In diesem Artikel haben wir gesehen, wie die ResultSet- API zum Abrufen und Aktualisieren von Daten aus einer Datenbank verwendet wird. Einige der erweiterten Funktionen, die wir besprochen haben, hängen von der von uns verwendeten Datenbank ab. Daher müssen wir die Unterstützung für diese Funktionen überprüfen, bevor wir sie verwenden.

Wie immer ist der Code auf GitHub verfügbar.