Panduan untuk Antaramuka ResultSet JDBC

1. Gambaran keseluruhan

API Java Database Connectivity (JDBC) menyediakan akses ke pangkalan data dari aplikasi Java. Kita boleh menggunakan JDBC untuk menyambung ke pangkalan data mana-mana selagi pemacu JDBC yang disokong tersedia.

The ResultSet merupakan jadual data yang dihasilkan dengan melaksanakan pertanyaan pangkalan data. Dalam tutorial ini, kita akan melihat API ResultSet dengan lebih mendalam .

2. Menjana ResultSet

Pertama, kami mengambil ResultSet dengan memanggil executeQuery () pada objek yang melaksanakan antara muka Pernyataan . Kedua-dua PreparedStatement dan CallableStatement adalah subinterfaces daripada Penyata :

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

The ResultSet objek mengekalkan kursor titik dengan baris semasa set keputusan. Kami akan menggunakan seterusnya () pada ResultSet kami untuk mengulangi rekod.

Seterusnya, kami akan menggunakan kaedah getX () sambil mengulangi hasil untuk mengambil nilai dari lajur pangkalan data , di mana X adalah jenis data lajur. Sebenarnya, kami akan memberikan nama lajur pangkalan data untuk kaedah 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"); } 

Begitu juga, nombor indeks tiang boleh digunakan dengan getX () kaedah dan bukannya nama lajur. Nombor indeks adalah urutan lajur dalam pernyataan pilih SQL.

Sekiranya pernyataan pilih tidak menyenaraikan nama lajur, nombor indeks adalah urutan lajur dalam jadual. Penomboran indeks lajur bermula 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 bahagian ini, kita akan melihat cara mendapatkan maklumat mengenai sifat dan jenis lajur dalam ResultSet .

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

ResultSetMetaData metaData = rs.getMetaData();

Seterusnya, mari dapatkan jumlah lajur yang terdapat di ResultSet kami :

Integer columnCount = metaData.getColumnCount();

Selanjutnya, kita dapat menggunakan salah satu kaedah di bawah pada objek metadata kita untuk mendapatkan sifat setiap lajur:

  • getColumnName (int columnNumber) - untuk mendapatkan nama lajur
  • getColumnLabel (int columnNumber) - untuk mengakses label lajur, yang ditentukan selepas AS dalam pertanyaan SQL
  • getTableName (int columnNumber) - untuk mendapatkan nama jadual yang dimiliki lajur ini
  • getColumnClassName (int columnNumber) - untuk memperoleh jenis data lajur Java
  • getColumnTypeName (int columnNumber) - untuk mendapatkan jenis data lajur dalam pangkalan data
  • getColumnType (int columnNumber) - untuk mendapatkan jenis data SQL lajur
  • isAutoIncrement (int columnNumber) - menunjukkan sama ada lajur adalah kenaikan automatik
  • isCaseSensitive (int columnNumber) - menentukan sama ada kes lajur itu penting
  • isSearchable (int columnNumber) - mencadangkan jika kita dapat menggunakan lajur di mana klausa pertanyaan SQL
  • isCurrency (int columnNumber) - memberi isyarat sekiranya lajur tersebut mengandungi nilai tunai
  • isNullable (int columnNumber) - mengembalikan sifar jika lajur tidak boleh kosong, satu jika lajur boleh mengandungi nilai nol, dan dua jika nullability lajur tidak diketahui
  • isSigned (int columnNumber) - mengembalikan true jika nilai dalam lajur ditandatangani, jika tidak mengembalikan false

Mari ulangi lajur untuk mendapatkan sifatnya:

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

Apabila kita memperoleh ResultSet , kedudukan kursor adalah sebelum baris pertama. Lebih-lebih lagi, secara lalai, ResultSet hanya bergerak ke arah hadapan. Tetapi, kita boleh menggunakan ResultSet yang boleh digulir untuk pilihan navigasi lain.

Di bahagian ini, kita akan membincangkan pelbagai pilihan navigasi.

4.1. Jenis Set Hasil

Jenis ResultSet menunjukkan bagaimana kita akan melihat set data:

  • TYPE_FORWARD_ONLY - pilihan lalai, di mana kursor bergerak dari awal hingga akhir
  • TYPE_SCROLL_INSENSITIVE - kursor kami dapat bergerak melalui set data dalam arah ke hadapan dan ke belakang; jika terdapat perubahan pada data yang mendasari ketika bergerak melalui set data, data tersebut akan diabaikan; set data mengandungi data dari saat pertanyaan pangkalan data mengembalikan hasilnya
  • TYPE_SCROLL_SENSITIVE - mirip dengan jenis tatal tidak sensitif, namun untuk jenis ini, set data segera mencerminkan sebarang perubahan pada data yang mendasari

Tidak semua pangkalan data menyokong semua jenis ResultSet . Oleh itu, mari kita periksa sama ada jenis tersebut disokong dengan menggunakan supportResultSetType pada objek DatabaseMetaData kami :

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

4.2. ResultSet yang boleh ditatal

Untuk mendapatkan ResultSet yang boleh ditatal , kita perlu melewati beberapa parameter tambahan semasa menyiapkan Penyataan .

Sebagai contoh, kami akan memperoleh ResultSet yang boleh ditatal 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. Pilihan Navigasi

Kita boleh menggunakan salah satu pilihan di bawah pada ResultSet yang boleh ditatal :

  • next () - terus ke baris seterusnya dari kedudukan semasa
  • sebelumnya () - melintasi ke baris sebelumnya
  • pertama () - menavigasi ke baris pertama ResultSet
  • terakhir () - melompat ke baris terakhir
  • beforeFirst () - bergerak ke permulaan; memanggil seterusnya () pada ResultSet kami setelah memanggil kaedah ini mengembalikan baris pertama dari ResultSet kami
  • afterLast () - melonjak hingga akhir; memanggil sebelumnya () pada ResultSet kami setelah melaksanakan kaedah ini mengembalikan baris terakhir dari ResultSet kami
  • relatif (int numOfRows) - maju atau mundur dari kedudukan semasa oleh numOfRows
  • mutlak (int barisNumber) - melompat ke barisNombor yang ditentukan

Mari 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 nombor baris ResultSet kami sekarang .

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

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

5. Mengemas kini Data dalam ResultSet

Secara lalai, ResultSet hanya boleh dibaca. Namun, kita dapat menggunakan ResultSet yang dapat dikemas kini untuk memasukkan, mengemas kini, dan menghapus baris.

5.1. ResultSet Serentak

Mod serentak menunjukkan jika ResultSet kami dapat mengemas kini data.

Pilihan CONCUR_READ_ONLY adalah lalai dan harus digunakan jika kita tidak perlu mengemas kini data menggunakan ResultSet kami .

Namun, jika kita perlu mengemas kini data dalam ResultSet kami , maka pilihan CONCUR_UPDATABLE harus digunakan.

Tidak semua pangkalan data menyokong semua mod serentak untuk semua jenis ResultSet . Oleh itu, kita perlu memeriksa apakah jenis dan mod serentak yang kita inginkan disokong menggunakan kaedah supportResultSetConcurrency () :

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

5.2. Mendapatkan ResultSet yang Boleh Dikemas kini

Untuk mendapatkan ResultSet yang dapat dikemas kini , kita perlu melewati parameter tambahan ketika kita menyiapkan Pernyataan . Untuk itu, mari kita gunakan CONCUR_UPDATABLE sebagai parameter ketiga semasa membuat pernyataan:

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

5.3. Mengemas kini 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 mengubah ukuran pengambilan pada ResultSet kami menjadi 20 sambil membaca rekod ke-30. Jadi, 4 perjalanan seterusnya akan memuatkan 20 rekod setiap perjalanan.

Oleh itu, kami memerlukan 7 perjalanan pangkalan data untuk memuatkan semua 100 rekod:

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

Dalam artikel ini, kami melihat bagaimana menggunakan ResultSet API untuk mengambil dan mengemas kini data dari pangkalan data. Beberapa ciri lanjutan yang kami bincangkan bergantung pada pangkalan data yang kami gunakan. Oleh itu, kita perlu memeriksa sokongan untuk ciri tersebut sebelum menggunakannya.

Seperti biasa, kodnya tersedia di GitHub.