核心概念
ResultSet 对象代表一个数据表,它通常位于一个指向数据库当前行的光标,这个光标最初位于第一行之前,遍历 ResultSet 的核心就是使用 next() 方法将光标移动到下一行,并判断是否还有数据。

传统的 while 循环(最基础、最常用)
这是最经典、最直接的遍历方式,它适用于所有 JDBC 版本和驱动。
步骤:
- 执行查询:通过
Statement或PreparedStatement的executeQuery()方法获取ResultSet。 - 循环遍历:使用
while (resultSet.next())循环。next()方法会将光标移动到下一行,如果成功移动(即存在下一行数据),则返回true;如果已经到达结果集末尾,则返回false。 - 获取数据:在循环体内,使用
getXxx(columnIndex)或getXxx(columnLabel)方法获取当前行中某一列的值。columnIndex:列的索引(从 1 开始,不是从 0 开始)。columnLabel:列的名称(推荐使用,更具可读性,且不受SELECT子句中列顺序改变的影响)。
- 关闭资源:非常重要! 最后必须关闭
ResultSet、Statement和Connection,以释放数据库资源。
代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TraditionalJdbcExample {
// 假设的数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 注册 JDBC 驱动 (对于较新版本的JDBC驱动,通常可以省略此步)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection(DB_URL, USER, PASS);
// 3. 创建 Statement
stmt = conn.createStatement();
// 4. 执行查询
String sql = "SELECT id, name, email FROM users";
rs = stmt.executeQuery(sql);
// 5. 遍历 ResultSet
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");
// 也可以通过列索引获取数据 (不推荐,因为顺序易变)
// int id = rs.getInt(1);
// String name = rs.getString(2);
// String email = rs.getString(3);
System.out.println(id + "\t" + name + "\t" + 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+ 最佳实践)
手动关闭资源 (finally 块) 代码冗长,且容易出错,从 Java 7 开始,引入了 try-with-resources 语句,它可以自动实现资源的关闭。
任何实现了 AutoCloseable 接口的资源都可以在 try-with-resources 中使用。Connection, Statement, 和 ResultSet 都实现了这个接口。
优点:
- 代码简洁:无需
finally块来手动关闭资源。 - 安全:JVM 会确保在
try块执行完毕后,自动调用资源的close()方法,即使发生了异常。
代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TryWithResourcesExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
// try-with-resources 会自动关闭 Connection, Statement, 和 ResultSet
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name, email FROM users")) {
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.println(id + "\t" + name + "\t" + email);
}
} catch (SQLException e) {
e.printStackTrace();
}
// conn, stmt, rs 都已经被自动关闭了
}
}
使用 RowSet (更灵活的 ResultSet)
RowSet 是 ResultSet 的一个子接口,它有两个主要特点:

- 可滚动:默认情况下,
RowSet对象是可滚动的。 - 可断开连接:
RowSet可以在关闭数据库连接后仍然存在和使用,因为它通常会将数据缓存在内存中。
这使得 RowSet 特别适合于需要将数据传递给表现层(如 UI 组件)或者进行复杂离线处理的场景。
常用的 RowSet 实现类:
CachedRowSet:最常用的RowSet,它是一个离线的、可滚动的RowSet。JdbcRowSet:一个连接的、可滚动的RowSet,行为更像一个传统的ResultSet。
代码示例 (CachedRowSet):
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;
import java.sql.SQLException;
public class CachedRowSetExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
private static final String USER = "your_username";
private static final String PASS = "your_password";
public static void main(String[] args) {
CachedRowSet crs = null;
try {
// 1. 创建 RowSetFactory
RowSetFactory rsf = RowSetProvider.newFactory();
// 2. 创建 CachedRowSet
crs = rsf.createCachedRowSet();
// 3. 设置连接属性
crs.setUrl(DB_URL);
crs.setUsername(USER);
crs.setPassword(PASS);
// 4. 设置查询 SQL
crs.setCommand("SELECT id, name, email FROM users");
// 5. 执行查询并填充数据 (此时会建立连接,查询完后可以断开)
crs.execute();
// 6. 此时可以关闭原始连接了,crs 已经在内存中有了数据副本
// ... (关闭 Connection 的代码) ...
// 7. 遍历 CachedRowSet (它和 ResultSet 的遍历方式一样)
System.out.println("ID\tName\tEmail");
System.out.println("----------------------");
while (crs.next()) {
int id = crs.getInt("id");
String name = crs.getString("name");
String email = crs.getString("email");
System.out.println(id + "\t" + name + "\t" + email);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 8. 关闭 CachedRowSet (会释放其占用的资源)
if (crs != null) {
try {
crs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
注意:使用 RowSet 需要 JDBC 驱动或额外的库(如 javax.sql.rowset 包,通常在 JDK 中提供)支持。
重要注意事项
- 关闭资源:这是 JDBC 编程的黄金法则,忘记关闭
Connection会导致数据库连接泄漏,最终可能导致数据库服务宕机。try-with-resources是目前最好的解决方案。 - 列名 vs. 列索引:强烈推荐使用列名 (
rs.getString("name")),因为:- 可读性高:代码更容易理解。
- 健壮性强:即使
SELECT语句中列的顺序改变,你的代码依然可以正常工作。
- 处理
NULL值:如果数据库中的列可能为NULL,直接调用getInt()或getString()会抛出SQLException,应该使用wasNull()方法来判断。int age = rs.getInt("age"); if (rs.wasNull()) { // age 列的值为 NULL System.out.println("Age is not available."); } else { System.out.println("Age: " + age); } - 性能考虑:
- *`SELECT
**:尽量避免使用SELECT *`,只查询你需要的列,可以减少网络传输和内存消耗。 - 大数据量:
ResultSet包含海量数据,一次性加载到内存可能会导致OutOfMemoryError,此时应考虑分页查询或使用服务器端游标。
- *`SELECT
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
传统 while 循环 |
简单直观,兼容所有 JDBC 版本 | 需要手动关闭资源,代码冗长 | 遗留系统或对 Java 版本有要求的旧项目。 |
try-with-resources |
代码简洁、安全、自动关闭资源 | 需要 Java 7+ | 现代 Java 项目的首选和最佳实践。 |
RowSet |
可断开连接,可滚动,易于传递数据 | 需要额外内存存储数据,性能可能稍差 | 需要在离线状态下操作数据,或需要将数据传递给非 JDBC 组件(如 UI)。 |
对于新的 Java 项目,强烈推荐使用 try-with-resources 来遍历 ResultSet,这是最现代、最安全、也是最简洁的方式。

