核心概念
ResultSet 对象代表了一个数据结果集,它通常是一行一行的数据,并有一个指向当前行的“光标”,遍历 ResultSet 的过程本质上就是移动这个光标,并读取每一列的数据。

关键点:
- 初始位置:在创建后,光标位于第一行 之前。
- 移动光标:使用
next()方法将光标移动到下一行,如果存在下一行,next()返回true;如果已经到达结果集的末尾,则返回false。 - 读取数据:在光标定位到某一行后,可以使用
getXxx(int columnIndex)或getXxx(String columnName)方法来获取该列的值。
基础的 try-catch-finally 遍历(传统方式)
这是最经典、最基础的遍历方式,适用于所有 JDBC 版本,它的核心是手动管理资源(Connection, Statement, ResultSet)。
代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class BasicResultSetTraversal {
// 假设这是你的数据库连接信息
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. 执行查询,获取 ResultSet
String sql = "SELECT id, name, email FROM users";
rs = stmt.executeQuery(sql);
System.out.println("ID\tName\tEmail");
System.out.println("--------------------");
// 5. 遍历 ResultSet
// while 循环是关键,只要 rs.next() 返回 true,就说明还有下一行数据
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 (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源 (非常重要!)
// 必须按从后到前的顺序关闭,否则可能会产生资源泄漏
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
优点:

- 简单直观,易于理解。
- 兼容所有 JDBC 版本。
缺点:
- 资源管理繁琐:必须手动在
finally块中关闭资源,容易忘记或写错顺序,导致资源泄漏。 - 代码冗长:大量的
try-catch-finally代码块使得业务逻辑不够清晰。
使用 try-with-resources(现代推荐方式)
从 Java 7 开始,引入了 try-with-resources 语句,它能自动实现资源的关闭,极大地简化了代码,任何实现了 AutoCloseable 接口的资源都可以使用这种方式。
Connection, Statement, 和 ResultSet 都实现了 AutoCloseable 接口。
代码示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TryWithResourcesTraversal {
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 会自动在 try 块结束时关闭资源
// 注意:声明的资源会按声明的相反顺序自动关闭
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 都已经被自动关闭了,无需手动操作
}
}
优点:
- 代码简洁:消除了繁琐的
finally块。 - 安全可靠:自动关闭资源,从根本上避免了资源泄漏问题。
- 推荐做法:这是目前 Java 开发中处理 JDBC 资源的标准和最佳实践。
使用 RowSet(可选方案)
RowSet 是 ResultSet 的一个子接口,它更灵活,因为它通常是可滚动、可更新且可序列化的。RowSet 不需要一直保持与数据库的连接,它可以将数据加载到内存中。
最常用的实现是 CachedRowSet。
特点:
- 断开连接:可以从
ResultSet或直接从数据库创建,然后关闭连接,在内存中操作数据。 - 可滚动:可以向前或向后遍历数据。
代码示例:
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetFactory;
import javax.sql.rowset.RowSetProvider;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class RowSetTraversal {
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;
CachedRowSet cachedRs = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT id, name, email FROM users");
// 创建 RowSetFactory
RowSetFactory factory = RowSetProvider.newFactory();
// 从 ResultSet 创建 CachedRowSet
cachedRs = factory.createCachedRowSet();
cachedRs.populate(rs); // 将数据从 ResultSet 填充到 CachedRowSet
// 现在可以关闭原始的数据库连接了
rs.close();
stmt.close();
conn.close();
System.out.println("ID\tName\tEmail");
System.out.println("--------------------");
// 遍历 CachedRowSet
while (cachedRs.next()) {
int id = cachedRs.getInt("id");
String name = cachedRs.getString("name");
String email = cachedRs.getString("email");
System.out.println(id + "\t" + name + "\t" + email);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 只需要关闭 CachedRowSet 即可
try {
if (cachedRs != null) cachedRs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
优点:
- 减少数据库连接压力:适合在需要离线操作或数据量不大的场景。
- API 更丰富:提供了比
ResultSet更多的便捷方法。
缺点:
- 性能开销:需要将数据从数据库加载到内存,如果结果集非常大,会消耗大量内存。
- 需要额外依赖:
RowSetAPI 通常需要 JDBC 驱动或应用服务器提供额外的 JAR 包(如javax.sql.rowset包)。
- 优先使用
try-with-resources:这是现代 Java 编程处理 JDBC 资源的首选方法,安全且简洁。 - 始终关闭资源:无论使用哪种方法,确保所有数据库资源(
Connection,Statement,ResultSet)在使用完毕后被正确关闭。try-with-resources是实现这一点的最佳方式。 - 使用列名而非列索引:
rs.getString("name")比rs.getString(2)更具可读性,并且在 SQL 查询中列的顺序发生变化时,代码不容易出错。 - 处理
SQLException:数据库操作可能会抛出SQLException,必须妥善处理,至少要记录日志。 - 考虑使用 JPA 或 MyBatis:对于复杂的项目,直接使用 JDBC 会编写大量重复代码,现代项目通常使用 ORM 框架(如 Hibernate/JPA)或 SQL 映射框架(如 MyBatis),它们能将结果集自动映射为 Java 对象(POJO),大大简化了数据访问层的代码。
示例:使用 JPA 的结果映射
// 假设你有一个 User 实体类
@Entity
public class User {
@Id
private int id;
private String name;
private String email;
// getters and setters...
}
// 在 Repository 或 DAO 层,代码会非常简洁
public List<User> findAllUsers() {
String jpql = "SELECT u FROM User u";
return entityManager.createQuery(jpql, User.class).getResultList();
}
// 框架会自动处理连接、执行查询、遍历ResultSet并映射为User对象列表 