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

什么是 ResultSet?
当你在 Java 中执行一个 SQL SELECT 语句后,数据库并不会立即将所有结果数据都传送到你的 Java 应用程序中(尤其是当结果集很大时,这会非常低效和耗费资源),相反,JDBC(Java Database Connectivity)会返回一个 ResultSet 对象。
这个 ResultSet 对象就像一个表格的游标,它最初位于第一行之前,你需要通过移动这个游标来逐行访问查询结果。
ResultSet 的核心概念
1 游标
ResultSet 的核心就是它的游标,默认情况下,这个游标只能向前移动,这意味着你只能从第一行开始,依次访问到最后一行,不能后退。
2 可滚动性
你可以通过创建 Statement 或 PreparedStatement 时指定参数来控制 ResultSet 的滚动行为,这通过 ResultSet 的类型常量来定义:

TYPE_FORWARD_ONLY:默认值,游标只能向前移动。TYPE_SCROLL_INSENSITIVE:游标可以前后移动,但对数据库中的其他修改不敏感,也就是说,在你创建ResultSet后,其他事务对数据的修改不会反映在你的ResultSet中。TYPE_SCROLL_SENSITIVE:游标可以前后移动,并且对数据库中的其他修改敏感,其他事务的提交会反映在你的ResultSet中。(注意:JDBC 驱动程序可能不完全支持此特性)。
3 可更新性
默认情况下,ResultSet 是只读的,但你可以创建一个可更新的 ResultSet,这样你就可以直接通过 ResultSet 对象来修改、插入或删除数据库中的数据,而无需再编写新的 UPDATE、INSERT 或 DELETE 语句。
CONCUR_READ_ONLY:默认值。ResultSet是只读的。CONCUR_UPDATABLE:ResultSet是可更新的。
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 的标准步骤如下:
- 执行查询:通过
Statement或PreparedStatement的executeQuery()方法执行SELECT语句,获取ResultSet对象。 - 遍历结果:使用
while (resultSet.next())循环来移动游标并检查是否有下一行数据。next()方法会将游标移动到下一行,并返回true(如果成功);当游标移过最后一行时,返回false。 - 获取数据:在循环内部,使用
getXxx()方法(如getString(),getInt(),getDouble()等)来获取当前行中列的值,你需要通过列名或列索引来指定要获取的列。 - 关闭资源:非常重要! 使用完毕后,必须关闭
ResultSet、Statement和Connection,推荐使用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。 - 适用场景:需要对结果集进行多次处理、随机访问或分页显示的场景。
最佳实践和注意事项
- 总是关闭资源:
ResultSet、Statement和Connection都是有限的数据库资源,如果不关闭,会导致连接泄漏,最终耗尽数据库连接池,使应用程序崩溃。强烈推荐使用try-with-resources。 - 优先使用
PreparedStatement:对于参数化查询,应始终使用PreparedStatement而不是Statement,它可以防止 SQL 注入攻击,并且通常能被数据库预编译,提高性能。 - 优先使用列名而非列索引:使用列名(如
rs.getString("name"))的代码可读性更好,并且当 SQL 查询中表的列顺序发生变化时,你的代码不会出错,使用列索引(rs.getString(2))则非常脆弱。 - 处理
NULL值:如果数据库中的列可能为NULL,在获取其值后,应使用wasNull()方法进行检查,以避免NullPointerException或将NULL错误地转换为数据类型的默认值(如0或false)。 - 选择合适的
ResultSet类型:根据你的业务需求选择合适的ResultSet类型,如果只需要遍历一次大结果集,使用默认的TYPE_FORWARD_ONLY可以节省内存,如果需要随机访问,则使用可滚动的类型。 - 避免在
ResultSet游标打开时进行其他操作:在某些数据库和驱动中,当ResultSet的游标打开时,其关联的Statement对象可能被锁定,此时执行其他查询可能会阻塞或出错,通常的做法是处理完一个结果集后再执行下一个查询。
