杰瑞科技汇

Java中ResultSet如何正确使用与关闭?

ResultSet 是 Java 数据库连接中一个至关重要的接口,它代表了一个数据库查询的结果集,你可以把它想象成一个指向查询结果数据的指针游标

Java中ResultSet如何正确使用与关闭?-图1
(图片来源网络,侵删)

什么是 ResultSet

当你在 Java 中执行一个 SQL SELECT 语句后,数据库并不会立即将所有结果数据都传送到你的 Java 应用程序中(尤其是当结果集很大时,这会非常低效和耗费资源),相反,JDBC(Java Database Connectivity)会返回一个 ResultSet 对象。

这个 ResultSet 对象就像一个表格的游标,它最初位于第一行之前,你需要通过移动这个游标来逐行访问查询结果。


ResultSet 的核心概念

1 游标

ResultSet 的核心就是它的游标,默认情况下,这个游标只能向前移动,这意味着你只能从第一行开始,依次访问到最后一行,不能后退。

2 可滚动性

你可以通过创建 StatementPreparedStatement 时指定参数来控制 ResultSet 的滚动行为,这通过 ResultSet 的类型常量来定义:

Java中ResultSet如何正确使用与关闭?-图2
(图片来源网络,侵删)
  • TYPE_FORWARD_ONLY默认值,游标只能向前移动。
  • TYPE_SCROLL_INSENSITIVE:游标可以前后移动,但对数据库中的其他修改不敏感,也就是说,在你创建 ResultSet 后,其他事务对数据的修改不会反映在你的 ResultSet 中。
  • TYPE_SCROLL_SENSITIVE:游标可以前后移动,并且对数据库中的其他修改敏感,其他事务的提交会反映在你的 ResultSet 中。(注意:JDBC 驱动程序可能不完全支持此特性)。

3 可更新性

默认情况下,ResultSet 是只读的,但你可以创建一个可更新的 ResultSet,这样你就可以直接通过 ResultSet 对象来修改、插入或删除数据库中的数据,而无需再编写新的 UPDATEINSERTDELETE 语句。

  • CONCUR_READ_ONLY默认值ResultSet 是只读的。
  • CONCUR_UPDATABLEResultSet 是可更新的。

4 创建可滚动和可更新的 Statement

要创建具有特定特性的 ResultSet,你需要在创建 Statement 时传入参数:

// 创建一个可滚动、敏感、可更新的 Statement
Statement stmt = connection.createStatement(
    ResultSet.TYPE_SCROLL_SENSITIVE, 
    ResultSet.CONCUR_UPDATABLE
);
// 使用 PreparedStatement 也可以
PreparedStatement pstmt = connection.prepareStatement(
    "SELECT id, name, email FROM users", 
    ResultSet.TYPE_SCROLL_INSENSITIVE, 
    ResultSet.CONCUR_READ_ONLY
);

如何使用 ResultSet(标准流程)

使用 ResultSet 的标准步骤如下:

  1. 执行查询:通过 StatementPreparedStatementexecuteQuery() 方法执行 SELECT 语句,获取 ResultSet 对象。
  2. 遍历结果:使用 while (resultSet.next()) 循环来移动游标并检查是否有下一行数据。next() 方法会将游标移动到下一行,并返回 true(如果成功);当游标移过最后一行时,返回 false
  3. 获取数据:在循环内部,使用 getXxx() 方法(如 getString(), getInt(), getDouble() 等)来获取当前行中列的值,你需要通过列名或列索引来指定要获取的列。
  4. 关闭资源非常重要! 使用完毕后,必须关闭 ResultSetStatementConnection,推荐使用 try-with-resources 语句来自动管理这些资源。

示例代码(传统方式)

import java.sql.*;
public class ResultSetExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String user = "your_username";
        String password = "your_password";
        // Connection, Statement, ResultSet 都需要关闭
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1. 加载驱动 (对于新版本JDBC,可以省略)
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2. 建立连接
            conn = DriverManager.getConnection(url, user, password);
            // 3. 创建 Statement
            stmt = conn.createStatement();
            // 4. 执行查询
            String sql = "SELECT id, name, email FROM users";
            rs = stmt.executeQuery(sql);
            // 5. 遍历结果集
            System.out.println("ID\tName\tEmail");
            System.out.println("----------------------");
            while (rs.next()) {
                // 通过列名获取数据,更推荐,可读性更好,且与列顺序无关
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                // 也可以通过列索引获取数据 (从1开始)
                // int id = rs.getInt(1);
                // String name = rs.getString(2);
                // String email = rs.getString(3);
                System.out.printf("%d\t%s\t%s\n", id, name, email);
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源 (顺序很重要:先关闭ResultSet, 再Statement, 最后Connection)
            try {
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

示例代码(推荐:try-with-resources 方式)

Java 7 引入了 try-with-resources,它可以自动实现 AutoCloseable 接口的资源关闭,使代码更简洁、安全。

import java.sql.*;
public class ResultSetTryWithResources {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String user = "your_username";
        String password = "your_password";
        String sql = "SELECT id, name, email FROM users";
        // try-with-resources 会自动关闭 Connection, Statement, 和 ResultSet
        try (Connection conn = DriverManager.getConnection(url, user, password);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            // 遍历结果集
            System.out.println("ID\tName\tEmail");
            System.out.println("----------------------");
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                System.out.printf("%d\t%s\t%s\n", id, name, email);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

ResultSet 的常用方法

方法 描述
next() 将游标移动到下一行,如果存在下一行,则返回 true,否则返回 false,这是遍历结果集的标准方法。
previous() 将游标移动到上一行,如果存在上一行,则返回 true,否则返回 false
absolute(int row) 将游标移动到指定的行号。row 为 1 表示第一行,为 -1 表示最后一行。
beforeFirst() 将游标移动到第一行之前。
afterLast() 将游标移动到最后一行之后。
first() 将游标移动到第一行。
last() 将游标移动到最后一行。
isFirst() 判断游标是否在第一行。
isLast() 判断游标是否在最后一行。
getRow() 返回当前行的行号。
getXxx(String columnName) 获取当前行指定列名的值。Xxx 是数据类型,如 getString(), getInt(), getDate() 等。推荐使用列名
getXxx(int columnIndex) 获取当前行指定列索引的值,列索引从 1 开始。
wasNull() 如果上一个读取的列值为 NULL,则返回 true,在使用 getXxx() 读取可能为 NULL 的列后,应调用此方法进行检查。
close() 关闭 ResultSet,释放其占用的数据库资源。

ResultSet 的类型

从 JDBC 4.0 (Java 6) 开始,ResultSet 分为两种类型,这取决于其底层数据的存储方式:

1 ResultSet.TYPE_FORWARD_ONLY (默认)

  • 特点:游标只能向前移动。
  • 实现:通常基于流式处理,数据库驱动不会一次性将所有数据加载到内存中,而是按需从数据库服务器获取数据。
  • 优点:内存占用非常低,即使处理包含数百万行的结果集也不会导致内存溢出。
  • 缺点:只能遍历一次,不能随机访问,在遍历过程中,对数据库的锁定时间可能更长。
  • 适用场景:处理非常大的结果集,只需要遍历一次的场景(如生成报表、导出数据)。

2 TYPE_SCROLL_INSENSITIVE / TYPE_SCROLL_SENSITIVE

  • 特点:游标可以前后自由移动。
  • 实现:通常基于缓存处理,数据库驱动会将整个结果集(或一个较大的数据块)加载到内存中的某个缓存区(如 ArrayList)中。
  • 优点:可以随机访问任何一行,可以多次遍历结果集。
  • 缺点:如果结果集非常大,会消耗大量内存,可能导致 OutOfMemoryError
  • 适用场景:需要对结果集进行多次处理、随机访问或分页显示的场景。

最佳实践和注意事项

  1. 总是关闭资源ResultSetStatementConnection 都是有限的数据库资源,如果不关闭,会导致连接泄漏,最终耗尽数据库连接池,使应用程序崩溃。强烈推荐使用 try-with-resources
  2. 优先使用 PreparedStatement:对于参数化查询,应始终使用 PreparedStatement 而不是 Statement,它可以防止 SQL 注入攻击,并且通常能被数据库预编译,提高性能。
  3. 优先使用列名而非列索引:使用列名(如 rs.getString("name"))的代码可读性更好,并且当 SQL 查询中表的列顺序发生变化时,你的代码不会出错,使用列索引(rs.getString(2))则非常脆弱。
  4. 处理 NULL:如果数据库中的列可能为 NULL,在获取其值后,应使用 wasNull() 方法进行检查,以避免 NullPointerException 或将 NULL 错误地转换为数据类型的默认值(如 0false)。
  5. 选择合适的 ResultSet 类型:根据你的业务需求选择合适的 ResultSet 类型,如果只需要遍历一次大结果集,使用默认的 TYPE_FORWARD_ONLY 可以节省内存,如果需要随机访问,则使用可滚动的类型。
  6. 避免在 ResultSet 游标打开时进行其他操作:在某些数据库和驱动中,当 ResultSet 的游标打开时,其关联的 Statement 对象可能被锁定,此时执行其他查询可能会阻塞或出错,通常的做法是处理完一个结果集后再执行下一个查询。
分享:
扫描分享到社交APP
上一篇
下一篇