跳转至

ResultSet 数据读取

本文你会学到

  • ResultSet 游标机制和 next() 遍历方式
  • 按列名取值与按列索引取值的区别和选择
  • ResultSetMetaData 动态获取列信息
  • 可滚动 ResultSet 的创建和双向导航方法

🚀 游标遍历机制

ResultSet 怎么读数据?——游标机制

ResultSet 内部维护一个游标,初始位置在第一行之前。调用 next() 将游标前移一行,返回 true 表示当前行有数据,false 表示已越过最后一行。

1
2
3
4
5
初始位置
[before first] → [row 1] → [row 2] → [row 3] → [after last]
                  next() 调用后游标所在位置

向前遍历(按列名取值)

next() 遍历 ResultSet,按列名获取各类型数据
    /**
     * 演示 next() 向前遍历 ResultSet,按列名取值
     */
    @Test
    void testIterateForward() throws SQLException {
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM products ORDER BY id")) {

            int count = 0;
            while (rs.next()) {
                // 按列名获取不同类型的值
                String name = rs.getString("name");
                int id = rs.getInt("id");
                double price = rs.getDouble("price");
                boolean inStock = rs.getBoolean("in_stock");
                System.out.printf("产品: id=%d, name=%s, price=%.2f, inStock=%b%n",
                        id, name, price, inStock);
                count++;
            }
            assertEquals(5, count, "应遍历到 5 条产品记录");
        }
    }

按列索引取值

按列索引(1-based)与按列名两种方式对比
    /**
     * 演示按列索引(1-based)获取数据
     */
    @Test
    void testGetByIndex() throws SQLException {
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(
                     "SELECT id, name, price, in_stock FROM products WHERE id = 1")) {

            assertTrue(rs.next(), "应有一条记录");

            // 按列索引获取(从 1 开始)
            int id = rs.getInt(1);           // 第1列: id
            String name = rs.getString(2);   // 第2列: name
            double price = rs.getDouble(3);  // 第3列: price
            boolean inStock = rs.getBoolean(4); // 第4列: in_stock

            // 按列名获取(两种方式结果相同)
            int idByName = rs.getInt("id");
            String nameByName = rs.getString("name");

            assertEquals(id, idByName, "按索引和按列名获取的 id 应一致");
            assertEquals(name, nameByName, "按索引和按列名获取的 name 应一致");
            assertEquals(1, id);
            assertEquals("笔记本电脑", name);

            System.out.println("按索引获取: id=" + id + ", name=" + name
                    + ", price=" + price + ", inStock=" + inStock);
            System.out.println("按列名获取: id=" + idByName + ", name=" + nameByName);
            // 注意:按列名可读性更好,按索引性能略高
        }
    }

按索引 vs 按列名

  • 按列名(rs.getString("name")):可读性好,不受 SELECT 列顺序变化影响,推荐
  • 按列索引(rs.getString(2)):性能略高,但列顺序变化后容易出错

🔍 元数据与高级遍历

ResultSetMetaData:在遍历中动态获取列信息

遍历结果集时同步读取列元数据
    /**
     * 演示在遍历 ResultSet 过程中访问列元数据(ResultSetMetaData)。
     * <p>
     * 与 MetaDataTest.testResultSetMetadata() 的区别:
     * 后者专门演示 DatabaseMetaData / ResultSetMetaData 的完整能力(类型名、精度等);
     * 本方法聚焦于"边遍历数据、边读取列元数据"这一典型使用场景——
     * 即在不知道列数和列名的情况下,动态构建输出。
     */
    @Test
    void testResultSetMetadataInRs() throws SQLException {
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM products")) {

            // 查询执行后即可从 ResultSet 获取元数据,无需先调用 next()
            ResultSetMetaData meta = rs.getMetaData();
            int columnCount = meta.getColumnCount();
            // products 表有 4 列:id, name, price, in_stock
            assertEquals(4, columnCount, "products 表应有 4 列");

            // 打印列名标题行(体现"不硬编码列名,动态读取"的使用场景)
            System.out.println("=== 动态列名标题 ===");
            for (int i = 1; i <= columnCount; i++) {
                System.out.printf("%-15s", meta.getColumnName(i));
            }
            System.out.println();

            // 遍历第一行数据,同时演示在迭代中访问元数据
            if (rs.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    // 通过列索引读取当前行数据,列名来自元数据
                    System.out.printf("%-15s", rs.getString(i));
                }
                System.out.println();
            }

            // 验证 4 列的列名(H2 默认返回大写列名)
            assertEquals("ID", meta.getColumnName(1));
            assertEquals("NAME", meta.getColumnName(2));
            assertEquals("PRICE", meta.getColumnName(3));
            assertEquals("IN_STOCK", meta.getColumnName(4));
        }
    }

只能往前遍历?——可滚动 ResultSet

默认 ResultSet 只能向前遍历(TYPE_FORWARD_ONLY)。通过 createStatement 时指定类型,可获得支持双向滚动的 ResultSet

可滚动 ResultSet:last()、first()、absolute()、relative()
    /**
     * 演示可滚动 ResultSet:TYPE_SCROLL_INSENSITIVE + CONCUR_READ_ONLY
     * 支持 last()、first()、absolute()、relative() 等导航方法
     */
    @Test
    void testScrollableResultSet() throws SQLException {
        try (Statement stmt = conn.createStatement(
                ResultSet.TYPE_SCROLL_INSENSITIVE,  // 可滚动,对数据变更不敏感
                ResultSet.CONCUR_READ_ONLY);        // 只读
             ResultSet rs = stmt.executeQuery(
                     "SELECT * FROM products ORDER BY id")) {

            // 移动到最后一行
            rs.last();
            assertEquals(5, rs.getInt("id"), "最后一行的 id 应为 5");
            System.out.println("last() -> id=" + rs.getInt("id")
                    + ", name=" + rs.getString("name"));

            // 移动到第一行
            rs.first();
            assertEquals(1, rs.getInt("id"), "第一行的 id 应为 1");
            System.out.println("first() -> id=" + rs.getInt("id")
                    + ", name=" + rs.getString("name"));

            // absolute(n): 移动到第 n 行(从 1 开始)
            rs.absolute(3);
            assertEquals(3, rs.getInt("id"), "第 3 行的 id 应为 3");
            System.out.println("absolute(3) -> id=" + rs.getInt("id")
                    + ", name=" + rs.getString("name"));

            // relative(n): 从当前位置相对移动 n 行
            rs.relative(-1); // 当前第3行,向前移1行到第2行
            assertEquals(2, rs.getInt("id"), "relative(-1) 后应在第 2 行");
            System.out.println("relative(-1) -> id=" + rs.getInt("id")
                    + ", name=" + rs.getString("name"));

            rs.relative(2); // 当前第2行,向后移2行到第4行
            assertEquals(4, rs.getInt("id"), "relative(2) 后应在第 4 行");
            System.out.println("relative(2) -> id=" + rs.getInt("id")
                    + ", name=" + rs.getString("name"));
        }
    }
导航方法 说明
first() 移动到第一行
last() 移动到最后一行
absolute(n) 移动到第 n 行(从 1 计数)
relative(n) 从当前行相对移动 n 行(正数向后,负数向前)
beforeFirst() 移动到第一行之前(初始位置)
afterLast() 移动到最后一行之后