天天看點

JDBC API 4.2(八):ResultSet 接口源碼分析

文章目錄

    • 1、簡介
    • 2、ResultSet 類圖
    • 3、ResultSet 重要方法
    • 4、ResultSet 類型
      • 4.1、TYPE_FORWARD_ONLY
      • 4.2、TYPE_SCROLL_INSENSITIVE
      • 4.3、TYPE_SCROLL_SENSITIVE
    • 5、ResultSet 并發性
    • 6、Cursor Holdability(遊标可保持性)
    • 7、示例
      • 7.1、從行中檢索列值
      • 7.2、更新ResultSet對象中的行
      • 7.3、使用 Statement 對象進行批處理更新
      • 7.4、執行參數化的批量更新
      • 7.5、在ResultSet對象中插入行

1、簡介

ResultSet 接口提供了用于檢索和處理已獲得結果集的方法,并且ResultSet對象具有不同的功能和特性。 這些特性是type(類型), concurrency(并發性), cursor holdability(遊标可保持性)。

ResultSet 對象維護一個遊标,該遊标指向其目前資料行。 next 方法将光标移動到下一行,當ResultSet對象中沒有更多行時它傳回false,是以可以在while循環中使用它來疊代結果集。

預設的 ResultSet 對象是不可更新的,并且隻有僅向前移動的光标。 是以,您隻能從第一行到最後一行疊代一次。

可以生成可滾動或可更新的ResultSet對象。 下面的代碼片段(其中con是有效的Connection對象)說明了如何建立一個可滾動且對其他更新不敏感并且可更新的結果集。

Statement stmt = con.createStatement(
                                      ResultSet.TYPE_SCROLL_INSENSITIVE,
                                      ResultSet.CONCUR_UPDATABLE);
       ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");
           

ResultSet 接口提供了用于從目前行檢索列值的getter方法(getBoolean,getLong等)。 可以使用列的索引号或列的名稱來檢索值。 通常,使用列索,列索引編号從1開始編号。為了實作最大的可移植性,應按從左到右的順序讀取每一行中的結果集列,并且每一列隻能讀取一次。

對于getter方法,JDBC驅動程式嘗試将基礎資料轉換為getter方法中指定的Java類型,并傳回合适的Java值。 JDBC規範具有一個表,該表顯示了ResultSet getter方法可以使用的從SQL類型到Java類型的映射。

用作getter方法的列名不區分大小寫。 當使用列名調用getter方法并且多個列具有相同的名稱時,将傳回第一個比對列的值。 對于在查詢中未明确命名的列,最好使用列号。 如果使用了列名,則應注意確定它們的唯一,這可以通過SQL AS子句來確定。

在JDBC 2.0 API(Java™2 SDK,标準版,版本1.2)中,向該接口添加了一組更新程式方法。 有關getter方法的參數的注釋也适用于updater方法的參數。

更新方法有兩種使用方式:

1、更新目前行中的列值:在可滾動的ResultSet對象中,光标可以前後移動,移動到絕對位置或相對于目前行的位置。 下面的代碼片段更新ResultSet對象rs的第五行中的NAME列,然後使用方法 updateRow 來更新 rs 資料源。

rs.absolute(5); // 移動光标到 rs 的第五行
       rs.updateString("NAME", "AINSWORTH"); // 更新第五行 NAME 列的值為 AINSWORTH
       rs.updateRow(); // 更新目前資料源 rs 中的第五行操作
           

2、将列值插入到插入行中:可更新的ResultSet對象具有與其關聯的特殊行,該行用作建構要插入的行的暫存區。 下面的代碼片段将光标移動到插入行,建構一個三列的行,然後使用insertRow方法将其插入rs和資料源表中。

rs.moveToInsertRow(); // 移動遊标到插入行
       rs.updateString(1, "AINSWORTH"); // 更新插入行第一列的值為 AINSWORTH
       rs.updateInt(2,35); // 更新第二列的值為 35
       rs.updateBoolean(3, true); // 更新第三列的值為 true
       rs.insertRow();
       rs.moveToCurrentRow();
           

ResultSet 對象的列的數量,類型和屬性由ResultSet.getMetaData方法傳回的ResultSetMetaData對象提供。

2、ResultSet 類圖

JDBC API 4.2(八):ResultSet 接口源碼分析

3、ResultSet 重要方法

方法 描述
boolean absolute(int row) 将遊标移動到此ResultSet對象中指定的行号。
afterLast() 将遊标移動到該ResultSet對象的末尾,在最後一行之後。
beforeFirst() 将遊标移動到該ResultSet對象的開頭,在第一行之前。
cancelRowUpdates() 取消對此ResultSet對象中的目前行進行的更新。
clearWarnings() 清除此ResultSet對象上報告的所有警告。
close() 立即釋放此ResultSet對象的資料庫和JDBC資源。
deleteRow() 從此ResultSet對象和基礎資料庫中删除目前行。
findColumn(String columnLabel) 将給定的ResultSet列标簽映射到其ResultSet列索引。
first() 将光标移動到此ResultSet對象的第一行。
int getRow() 檢索目前行号。
RowId getRowId(int columnIndex) 以Java程式設計語言中java.sql.RowId對象的形式檢索此ResultSet對象的目前行中指定列的值。
getStatement() 檢索産生此ResultSet對象的Statement對象。
SQLWarning getWarnings() 檢索此ResultSet對象上的調用報告的第一個警告。
void insertRow() 将插入行的内容插入到此ResultSet對象和資料庫中。
boolean last() 将光标移動到此ResultSet對象的最後一行。
void moveToCurrentRow() 将光标移動到記住的光标位置,通常是目前行。
void moveToInsertRow() 将光标移動到插入行。
boolean next() 将光标從目前位置向前移動一行。
boolean previous() 将光标移動到此ResultSet對象的上一行。
void refreshRow() 用資料庫中的最新值重新整理目前行。
boolean rowDeleted() 檢索是否已删除行。
boolean rowInserted() 檢索目前行是否有插入。
boolean rowUpdated() 檢索目前行是否已更新。
updateArray(int columnIndex, Array x) 使用java.sql.Array值更新指定的列。
updateBinaryStream(int columnIndex, InputStream x) 用二進制流值更新指定的列。
updateBlob(int columnIndex, Blob x) 使用java.sql.Blob值更新指定的列。
updateBoolean(String columnLabel, boolean x) 用布爾值更新指定的列。
updateByte(String columnLabel, byte x) 用byte值更新指定的列。
updateDate(int columnIndex, Date x) 使用java.sql.Date值更新指定的列。
updateDouble(int columnIndex, double x) 用double值更新指定的列。
updateFloat(int columnIndex, float x) 用float值更新指定的列。
updateInt(int columnIndex, int x) 用int值更新指定的列。
updateLong(int columnIndex, long x) 用long值更新指定的列。
updateNull(String columnLabel) 用空值更新指定的列。
updateRow() 使用此ResultSet對象的目前行的新内容更新基礎資料庫。
updateShort(int columnIndex, short x) 用short值更新指定的列。
updateString(int columnIndex, String x) 用字元串值更新指定的列。

4、ResultSet 類型

ResultSet 對象的類型在兩個方面确定其功能級别:遊标的操作方式以及ResultSet對象如何反映對基礎資料源進行的并發更改。

4.1、TYPE_FORWARD_ONLY

結果集無法滾動,它的光标隻能從第一行之前移到最後一行之後。 結果集中包含的行取決于基礎資料庫如何生成的結果。 即,它包含在執行查詢時或在檢索行時滿足查詢條件的行。

4.2、TYPE_SCROLL_INSENSITIVE

結果集可以滾動, 它的光标可以相對于目前位置向前和向後移動,并且可以移動到絕對位置。 結果集在打開時對基礎資料源所做的更改不敏感。 它包含在執行查詢時或在檢索行時滿足查詢條件的行。

4.3、TYPE_SCROLL_SENSITIVE

結果可以滾動,它的光标可以相對于目前位置向前和向後移動,并且可以移動到絕對位置。 結果集反映在結果集保持打開狀态時對基礎資料源所做的更改。

5、ResultSet 并發性

ResultSet 對象的并發性确定了支援什麼級别的更新功能。

有兩個并發級别:

  • CONCUR_READ_ONLY:無法使用ResultSet接口更新ResultSet對象。
  • CONCUR_UPDATABLE:可以使用ResultSet接口更新ResultSet對象。

預設的ResultSet并發性是CONCUR_READ_ONLY。

下面的示例示範如何使用并發級别為CONCUR_UPDATABLE的ResultSet對象。

private static void modifyUserName() {
        String QUERY = "select id,name,email,country,password from Users where id = 1";
        // Step 1:建立 connection 對象
        try (Connection connection = DriverManager
                .getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");
             // Step 2:使用connection 對象建立一個statement 對象
             Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                     ResultSet.CONCUR_UPDATABLE);
             // Step 3: 執行查詢或更新操作
             ResultSet rs = stmt.executeQuery(QUERY)) {
            // Step 4: 處理結果集對象ResultSet
            while (rs.next()) {
                String name = rs.getString("name");
                System.out.println("更新之前的使用者名 : " + name);
                rs.updateString("name", name+"_update");
                rs.updateRow();
                System.out.println("更新之後的使用者名 : " + rs.getString("name"));
            }
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

結果:

更新之前的使用者名 : 張三
更新之後的使用者名 : 張三_update
           

6、Cursor Holdability(遊标可保持性)

調用方法Connection.commit可以關閉在目前事務期間建立的ResultSet對象。 但是,在某些情況下,這可能不是所需的行為。 ResultSet屬性的可保留性使應用程式可以控制在調用送出時是否關閉ResultSet對象(光标)。

可以将以下ResultSet常量提供給Connection方法的createStatement,prepareStatement和prepareCall:

  • HOLD_CURSORS_OVER_COMMIT: ResultSet遊标未關閉,它是可保持的,調用方法commit時,它們保持打開狀态。 如果您的應用程式主要使用隻讀的ResultSet對象,則可保持遊标可能是理想的選擇。
  • CLOSE_CURSORS_AT_COMMIT: 調用commit方法時,将關閉ResultSet對象(光标)。 調用此方法時關閉遊标可以提高某些應用程式的性能。

預設的遊标可保留性取決于您的DBMS。

以目前本機安裝的MySQL為例:

public static void main(String[] args) throws SQLException {
        try(Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");){
            cursorHoldabilitySupport(connection);
        }
    }

    public static void cursorHoldabilitySupport(Connection conn)
            throws SQLException {

        DatabaseMetaData dbMetaData = conn.getMetaData();
        System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " +
                ResultSet.HOLD_CURSORS_OVER_COMMIT);

        System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " +
                ResultSet.CLOSE_CURSORS_AT_COMMIT);

        System.out.println("Default cursor holdability: " +
                dbMetaData.getResultSetHoldability());

        System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " +
                dbMetaData.supportsResultSetHoldability(
                        ResultSet.HOLD_CURSORS_OVER_COMMIT));

        System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " +
                dbMetaData.supportsResultSetHoldability(
                        ResultSet.CLOSE_CURSORS_AT_COMMIT));
    }
           

輸出結果:

ResultSet.HOLD_CURSORS_OVER_COMMIT = 1
ResultSet.CLOSE_CURSORS_AT_COMMIT = 2
Default cursor holdability: 1
Supports HOLD_CURSORS_OVER_COMMIT? true
Supports CLOSE_CURSORS_AT_COMMIT? false
           

7、示例

7.1、從行中檢索列值

ResultSet 接口聲明了用于從目前行中檢索列值的getter方法(例如,getBoolean和getLong)。 您可以使用列的索引号或列的别名或名稱來檢索值。 列索引通常更有效。 列從1開始編号。為了實作最大的可移植性,應按從左到右的順序讀取每一行中的結果集列,并且每一列隻能讀取一次。

例如,以下方法将按數字檢索列值:

public static void main(String[] args) throws SQLException {
        retrievingValueByDigital();
    }


    public static void retrievingValueByDigital() {
        // Step 1: 建立 Connection 對象
        try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");

             // Step 2:使用 connection 建立 statement 對象
             Statement stmt = connection.createStatement();

             // Step 3: 執行查詢或更新
             ResultSet rs = stmt.executeQuery(QUERY)) {

            // Step 4: 處理 ResultSet 對象
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                String country = rs.getString("country");
                String password = rs.getString("password");
                System.out.println(id + "," + name + "," + email + "," + country + "," + password);
            }
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

輸出結果:

1,張三_update,[email protected],china,123456
2,李四,[email protected],china,123456
           

7.2、更新ResultSet對象中的行

您不能更新預設的ResultSet對象,而隻能将其光标向前移動。 但是,您可以建立可以滾動(光标可以向後移動或移至絕對位置)和更新的ResultSet對象。

以下方法 modifyUserName() 使用新名稱更新現有名稱:

private static void modifyUserName() {
        String QUERY = "select id,name,email,country,password from Users where id = 1";
        // Step 1:建立 connection 對象
        try (Connection connection = DriverManager
                .getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");
             // Step 2:使用connection 對象建立一個statement 對象
             Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                     ResultSet.CONCUR_UPDATABLE);
             // Step 3: 執行查詢或更新操作
             ResultSet rs = stmt.executeQuery(QUERY)) {
            // Step 4: 處理結果集對象ResultSet
            while (rs.next()) {
                String name = rs.getString("name");
                System.out.println("更新之前的使用者名 : " + name);
                rs.updateString("name", name+"_update");
                rs.updateRow();
                System.out.println("更新之後的使用者名 : " + rs.getString("name"));
            }
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

7.3、使用 Statement 對象進行批處理更新

Statement,PreparedStatement 和 CallableStatement 對象具有與其關聯的指令清單。 該清單在建立時與 Statement 對象相關聯,初始是空的。 您可以使用addBatch方法将SQL指令添加到此清單,并使用clearBatch方法将其清空。 完成将語句添加到清單後,請調用executeBatch方法将其全部發送到資料庫以作為一個單元或批處理執行。

public static void main(String[] args) throws SQLException {
        batchUpdate();
    }

    /**
     * 使用 Statement 對象進行批處理更新
     */
    private static void batchUpdate() {
        //Step 1: 建立 Connection 對象
        try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");
             // Step 2:使用 connection 建立 statement 對象
             Statement statement = connection.createStatement()) {
            connection.setAutoCommit(false);
            statement.addBatch("INSERT INTO Users VALUES (3, '李梅', '[email protected]', 'China', '123789');");
            statement.addBatch("INSERT INTO Users VALUES (4, '韓磊磊', '[email protected]', 'China', '123789');");
            statement.addBatch("INSERT INTO Users VALUES (5, '喬治', '[email protected]', 'China', '123789');");
            statement.addBatch("INSERT INTO Users VALUES (6, '佩奇', '[email protected]', 'China', '123789');");
            statement.addBatch("INSERT INTO Users VALUES (7, '光頭強', '[email protected]', 'China', '123789');");
            int[] updateCounts = statement.executeBatch();
            System.out.println(Arrays.toString(updateCounts));
            connection.commit();
        } catch (BatchUpdateException batchUpdateException) {
            printBatchUpdateException(batchUpdateException);
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

結果:

JDBC API 4.2(八):ResultSet 接口源碼分析

7.4、執行參數化的批量更新

也可以進行參數化的批量更新,如以下代碼片段所示:

private static void parameterizedBatchUpdate() {
        String INSERT_USERS_SQL = "INSERT INTO users" + "  (id, name, email, country, password) VALUES " +
                " (?, ?, ?, ?, ?);";

        try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");
             PreparedStatement preparedStatement = connection.prepareStatement(INSERT_USERS_SQL)) {
            connection.setAutoCommit(false);

            preparedStatement.setInt(1, 8);
            preparedStatement.setString(2, "悟空");
            preparedStatement.setString(3, "[email protected]");
            preparedStatement.setString(4, "China");
            preparedStatement.setString(5, "189398");
            preparedStatement.addBatch();

            preparedStatement.setInt(1, 9);
            preparedStatement.setString(2, "八戒");
            preparedStatement.setString(3, "[email protected]");
            preparedStatement.setString(4, "China");
            preparedStatement.setString(5, "189398");
            preparedStatement.addBatch();

            preparedStatement.setInt(1, 10);
            preparedStatement.setString(2, "唐生");
            preparedStatement.setString(3, "[email protected]");
            preparedStatement.setString(4, "China");
            preparedStatement.setString(5, "189398");
            preparedStatement.addBatch();

            preparedStatement.setInt(1, 11);
            preparedStatement.setString(2, "沙和尚");
            preparedStatement.setString(3, "shaheshang.com");
            preparedStatement.setString(4, "China");
            preparedStatement.setString(5, "189398");
            preparedStatement.addBatch();

            int[] updateCounts = preparedStatement.executeBatch();
            System.out.println(Arrays.toString(updateCounts));
            connection.commit();
            connection.setAutoCommit(true);
        } catch (BatchUpdateException batchUpdateException) {
            printBatchUpdateException(batchUpdateException);
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

執行結果:

JDBC API 4.2(八):ResultSet 接口源碼分析

7.5、在ResultSet對象中插入行

private static void insertRowInResultSetObject() {
        String QUERY = "select id,name,email,country,password from Users where id = 1";
        try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT", "root", "root");
             Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                     ResultSet.CONCUR_UPDATABLE);
             ResultSet uprs = stmt.executeQuery(QUERY)) {
            uprs.moveToInsertRow();
            uprs.updateInt(1, 100);
            uprs.updateString(2, "博士生");
            uprs.updateString(3, "[email protected]");
            uprs.updateString(4, "China");
            uprs.updateString(5, "325468");
            uprs.insertRow();
            uprs.beforeFirst();
        } catch (SQLException e) {
            printSQLException(e);
        }
    }
           

執行結果:

JDBC API 4.2(八):ResultSet 接口源碼分析

參考資料:

https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html?is-external=true