Panduan untuk Antarmuka ResultSet JDBC

1. Ikhtisar

Java Database Connectivity (JDBC) API menyediakan akses ke database dari aplikasi Java. Kami dapat menggunakan JDBC untuk terhubung ke database apa pun selama driver JDBC yang didukung tersedia.

The ResultSet adalah tabel data yang dihasilkan oleh mengeksekusi query database. Dalam tutorial ini, kita akan melihat lebih dalam pada API ResultSet .

2. Menghasilkan ResultSet

Pertama, kita mengambil ResultSet dengan memanggil executeQuery () pada objek apa pun yang mengimplementasikan antarmuka Pernyataan . Baik PreparedStatement dan CallableStatement adalah subinterfaces dari Pernyataan :

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

The ResultSet objek mempertahankan kursor yang menunjuk ke baris saat ini dari hasil set. Kami akan menggunakan next () pada ResultSet kami untuk melakukan iterasi melalui catatan.

Selanjutnya, kita akan menggunakan metode getX () saat melakukan iterasi melalui hasil untuk mengambil nilai dari kolom database , di mana X adalah tipe data kolom. Faktanya, kami akan memberikan nama kolom database ke metode getX () :

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

Demikian juga, jumlah indeks kolom dapat digunakan dengan getX () metode bukan nama kolom. Nomor indeks adalah urutan kolom dalam pernyataan pilih SQL.

Jika pernyataan pilih tidak mencantumkan nama kolom, nomor indeks adalah urutan kolom dalam tabel. Penomoran indeks kolom dimulai dari satu:

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

3. Mengambil MetaData dari ResultSet

Di bagian ini, kita akan melihat cara mengambil informasi tentang properti dan tipe kolom di ResultSet .

Pertama, mari gunakan metode getMetaData () pada ResultSet kita untuk mendapatkan ResultSetMetaData :

ResultSetMetaData metaData = rs.getMetaData();

Selanjutnya, mari kita dapatkan jumlah kolom yang ada di ResultSet kita :

Integer columnCount = metaData.getColumnCount();

Selanjutnya, kita dapat menggunakan salah satu metode di bawah ini pada objek metadata kita untuk mengambil properti dari setiap kolom:

  • getColumnName (int columnNumber) - untuk mendapatkan nama kolom
  • getColumnLabel (int columnNumber) - untuk mengakses label kolom, yang ditentukan setelah AS dalam kueri SQL
  • getTableName (int columnNumber) - untuk mendapatkan nama tabel dari kolom ini
  • getColumnClassName (int columnNumber) - untuk mendapatkan tipe data kolom Java
  • getColumnTypeName (int columnNumber) - untuk mendapatkan tipe data kolom dalam database
  • getColumnType (int columnNumber) - untuk mendapatkan tipe data SQL dari kolom
  • isAutoIncrement (int columnNumber) - menunjukkan apakah kolom adalah penambahan otomatis
  • isCaseSensitive (int columnNumber) - menentukan apakah kasus kolom penting
  • isSearchable (int columnNumber) - menyarankan jika kita dapat menggunakan kolom di klausa where dari kueri SQL
  • isCurrency (int columnNumber) - memberi sinyal jika kolom berisi nilai tunai
  • isNullable (int columnNumber) - mengembalikan nol jika kolom tidak boleh null, satu jika kolom dapat berisi nilai null, dan dua jika nullability kolom tidak diketahui
  • isSigned (int columnNumber) - mengembalikan nilai benar jika nilai dalam kolom ditandatangani, jika tidak mengembalikan salah

Mari lakukan iterasi melalui kolom untuk mendapatkan propertinya:

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. Menavigasi ResultSet

Saat kita mendapatkan ResultSet , posisi kursor berada sebelum baris pertama. Selain itu, secara default, ResultSet hanya bergerak ke arah depan. Tapi, kita bisa menggunakan ResultSet yang bisa digulir untuk opsi navigasi lainnya.

Di bagian ini, kita akan membahas berbagai opsi navigasi.

4.1. Jenis ResultSet

Jenis ResultSet menunjukkan bagaimana kita akan mengarahkan melalui dataset:

  • TYPE_FORWARD_ONLY - opsi default, di mana kursor bergerak dari awal hingga akhir
  • TYPE_SCROLL_INSENSITIVE - kursor kami dapat bergerak melalui kumpulan data baik dalam arah maju maupun mundur; jika ada perubahan pada data pokok saat berpindah melalui kumpulan data, perubahan tersebut akan diabaikan; set data berisi data dari saat kueri database mengembalikan hasilnya
  • TYPE_SCROLL_SENSITIVE - mirip dengan jenis scroll tidak sensitif, namun untuk jenis ini, set data langsung mencerminkan perubahan apa pun pada data pokok

Tidak semua database mendukung semua jenis ResultSet . Jadi, mari kita periksa apakah jenisnya didukung dengan menggunakan supportResultSetType pada objek DatabaseMetaData kita :

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

4.2. ResultSet yang Dapat Digulir

Untuk mendapatkan ResultSet yang dapat digulir , kita perlu melewatkan beberapa parameter tambahan saat menyiapkan Pernyataan .

Misalnya, kita akan memperoleh ResultSet yang dapat digulir dengan menggunakan TYPE_SCROLL_INSENSITIVE atau TYPE_SCROLL_SENSITIVE sebagai jenis ResultSet :

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

4.3. Opsi Navigasi

Kita dapat menggunakan salah satu opsi di bawah ini pada ResultSet yang dapat digulir :

  • next () - melanjutkan ke baris berikutnya dari posisi saat ini
  • sebelumnya () - melintasi ke baris sebelumnya
  • first () - menavigasi ke baris pertama dari ResultSet
  • last () - lompat ke baris terakhir
  • beforeFirst () - pindah ke awal; memanggil next () pada ResultSet kami setelah memanggil metode ini mengembalikan baris pertama dari ResultSet kami
  • afterLast () - melompat ke akhir; memanggil sebelumnya () pada ResultSet kami setelah menjalankan metode ini mengembalikan baris terakhir dari ResultSet kami
  • relative (int numOfRows) - maju atau mundur dari posisi saat ini dengan numOfRows
  • absolute (int rowNumber) - melompat ke rowNumber yang ditentukan

Mari kita lihat beberapa contoh:

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 Jumlah Baris

Mari gunakan getRow () untuk mendapatkan nomor baris saat ini dari ResultSet kita .

Pertama, kita akan menavigasi ke baris terakhir dari ResultSet dan kemudian menggunakan getRow () untuk mendapatkan jumlah record:

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

5. Memperbarui Data di ResultSet

Secara default, ResultSet hanya-baca. Namun, kita dapat menggunakan ResultSet yang dapat diperbarui untuk menyisipkan, memperbarui, dan menghapus baris.

5.1. Concurrency ResultSet

Mode konkurensi menunjukkan apakah ResultSet kami dapat memperbarui data.

The CONCUR_READ_ONLY pilihan adalah default dan harus digunakan jika kita tidak perlu memperbarui data menggunakan kami ResultSet .

Namun, jika kita perlu memperbarui data di ResultSet kita , maka opsi CONCUR_UPDATABLE harus digunakan.

Tidak semua database mendukung semua mode konkurensi untuk semua jenis ResultSet . Oleh karena itu, kita perlu memeriksa apakah jenis dan mode konkurensi yang kita inginkan didukung menggunakan metode supportingResultSetConcurrency () :

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

5.2. Mendapatkan ResultSet yang Dapat Diperbarui

Untuk mendapatkan ResultSet yang dapat diupdate , kita perlu meneruskan parameter tambahan saat kita menyiapkan Pernyataan . Untuk itu, mari gunakan CONCUR_UPDATABLE sebagai parameter ketiga saat membuat pernyataan:

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

5.3. Memperbarui Baris

In this section, we'll update a row using the updatable ResultSet created in the previous section.

We can update data in a row by calling updateX() methods, passing the column names and values to update. We can use any supported data type in place of X in the updateX() method.

Let's update the “salary” column, which is of type double:

rs.updateDouble("salary", 1100.0);

Note that this just updates the data in the ResultSet, but the modifications are not yet saved back to the database.

Finally, let’s call updateRow() to save the updates to the database:

rs.updateRow(); 

Instead of the column names, we can pass the column index to the updateX() methods. This is similar to using the column index for getting the values using getX() methods. Passing either the column name or index to the updateX() methods yields the same result:

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

5.4. Inserting a Row

Now, let's insert a new row using our updatable ResultSet.

First, we'll use moveToInsertRow() to move the cursor to insert a new row:

rs.moveToInsertRow();

Next, we must call updateX() methods to add the information to the row. We need to provide data to all the columns in the database table. If we don't provide data to every column, then the default column value is used:

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

Then, let's call insertRow() to insert a new row into the database:

rs.insertRow();

Finally, let's use moveToCurrentRow(). This will take the cursor position back to the row we were at before we started inserting a new row using the moveToInsertRow() method:

rs.moveToCurrentRow();

5.5. Deleting a Row

In this section, we'll delete a row using our updatable ResultSet.

First, we'll navigate to the row we want to delete. Then, we'll call the deleteRow() method to delete the current row:

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

6. Holdability

The holdability determines if our ResultSet will be open or closed at the end of a database transaction.

6.1. Holdability Types

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.

Dan kemudian, kami akan memodifikasi ukuran pengambilan pada ResultSet kami menjadi 20 saat membaca catatan ke-30. Jadi, 4 perjalanan berikutnya akan memuat 20 rekaman per setiap perjalanan.

Oleh karena itu, kami memerlukan 7 perjalanan database untuk memuat semua 100 catatan:

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. Kesimpulan

Di artikel ini, kami melihat cara menggunakan API ResultSet untuk mengambil dan memperbarui data dari database. Beberapa fitur lanjutan yang kita diskusikan bergantung pada database yang kita gunakan. Oleh karena itu, kami perlu memeriksa dukungan untuk fitur tersebut sebelum menggunakannya.

Seperti biasa, kode tersedia di GitHub.