天天看点

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