1. 概述
Java Database Connectivity (JDBC) API 提供了从 Java 应用访问数据库的能力。只要对应的 JDBC 驱动可用,我们就可以通过 JDBC 连接任何数据库。
ResultSet
是执行数据库查询后返回的数据表形式的结果集。在本教程中,我们将深入探讨 ResultSet
API 的使用方式。
2. 如何获取 ResultSet
我们通常通过调用实现了 Statement
接口的对象的 executeQuery()
方法来获取 ResultSet
。PreparedStatement
和 CallableStatement
都是 Statement
的子接口:
PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();
ResultSet
对象内部维护一个游标(cursor),指向当前结果集中的某一行数据。我们可以通过 next()
方法遍历结果集中的记录。
在遍历过程中,我们使用 getX()
方法来获取各列的值,其中 X
表示列的数据类型。我们也可以将列名作为参数传入:
while(rs.next()) {
String name = rs.getString("name");
Integer empId = rs.getInt("emp_id");
Double salary = rs.getDouble("salary");
String position = rs.getString("position");
}
此外,我们也可以使用列的索引号来代替列名。索引号从 1 开始,顺序与 SQL 查询中的列顺序一致:
Integer empId = rs.getInt(1);
String name = rs.getString(2);
String position = rs.getString(3);
Double salary = rs.getDouble(4);
3. 获取 ResultSet
元数据
本节我们将介绍如何从 ResultSet
中提取列的元数据信息。
首先,通过 getMetaData()
方法获取 ResultSetMetaData
对象:
ResultSetMetaData metaData = rs.getMetaData();
接着,我们可以获取列的数量:
Integer columnCount = metaData.getColumnCount();
还可以使用以下方法获取各列的详细信息:
✅ getColumnName(int columnNumber)
– 获取列名
✅ getColumnLabel(int columnNumber)
– 获取查询中定义的列别名(如 AS
后面的内容)
✅ getTableName(int columnNumber)
– 获取该列所属的表名
✅ getColumnClassName(int columnNumber)
– 获取该列对应的 Java 类型
✅ getColumnTypeName(int columnNumber)
– 获取该列在数据库中的类型
✅ getColumnType(int columnNumber)
– 获取该列的 SQL 类型代码
✅ isAutoIncrement(int columnNumber)
– 判断是否为自增列
✅ isCaseSensitive(int columnNumber)
– 判断是否区分大小写
✅ isSearchable(int columnNumber)
– 判断是否可用于 WHERE 子句
✅ isCurrency(int columnNumber)
– 判断是否为货币类型
✅ isNullable(int columnNumber)
– 判断是否可为空(0=不可为空,1=可为空,2=未知)
✅ isSigned(int columnNumber)
– 判断是否为有符号类型
示例:遍历所有列并获取其元数据:
for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
String catalogName = metaData.getCatalogName(columnNumber);
String className = metaData.getColumnClassName(columnNumber);
String label = metaData.getColumnLabel(columnNumber);
String name = metaData.getColumnName(columnNumber);
String typeName = metaData.getColumnTypeName(columnNumber);
int type = metaData.getColumnType(columnNumber);
String tableName = metaData.getTableName(columnNumber);
String schemaName = metaData.getSchemaName(columnNumber);
boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber);
boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber);
boolean isCurrency = metaData.isCurrency(columnNumber);
boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber);
boolean isReadOnly = metaData.isReadOnly(columnNumber);
boolean isSearchable = metaData.isSearchable(columnNumber);
boolean isReadable = metaData.isReadOnly(columnNumber);
boolean isSigned = metaData.isSigned(columnNumber);
boolean isWritable = metaData.isWritable(columnNumber);
int nullable = metaData.isNullable(columnNumber);
}
4. 控制 ResultSet
的游标位置
获取 ResultSet
后,游标默认位于第一条记录之前。默认情况下,只能向前遍历,但我们可以使用可滚动的 ResultSet
实现更多导航方式。
4.1. ResultSet
类型
ResultSet
类型决定了游标的移动方式:
✅ TYPE_FORWARD_ONLY
– 默认类型,只能从前往后遍历
✅ TYPE_SCROLL_INSENSITIVE
– 可前后滚动,不反映底层数据变更
✅ TYPE_SCROLL_SENSITIVE
– 可前后滚动,并反映数据变更(数据库支持情况视情况而定)
并不是所有数据库都支持上述所有类型。我们可以通过 DatabaseMetaData
检查是否支持:
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
4.2. 创建可滚动的 ResultSet
创建时需指定类型:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
4.3. 导航方法
✅ next()
– 向下移动一行
✅ previous()
– 向上移动一行
✅ first()
– 移动到第一行
✅ last()
– 移动到最后一行
✅ beforeFirst()
– 移动到起始位置
✅ afterLast()
– 移动到末尾位置
✅ relative(int rows)
– 相对当前位置移动指定行数
✅ absolute(int rowNumber)
– 移动到指定行号
示例:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// 正向遍历
}
rs.beforeFirst();
rs.afterLast();
rs.first();
rs.last();
rs.absolute(2);
rs.relative(-1);
rs.relative(2);
while (rs.previous()) {
// 反向遍历
}
4.4. 获取行数
通过 getRow()
获取当前行号:
rs.last();
int rowCount = rs.getRow();
5. 更新 ResultSet
中的数据
默认情况下,ResultSet
是只读的。但我们可以创建可更新的 ResultSet
,实现插入、更新、删除操作。
5.1. 并发模式
✅ CONCUR_READ_ONLY
– 只读(默认)
✅ CONCUR_UPDATABLE
– 可更新
同样地,不是所有数据库都支持所有并发模式。我们可以通过 supportsResultSetConcurrency()
检查支持情况:
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetConcurrency(
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
5.2. 获取可更新的 ResultSet
创建时指定并发模式:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
5.3. 更新行数据
使用 updateX()
方法更新指定列的值,然后调用 updateRow()
提交更改:
rs.updateDouble("salary", 1100.0);
rs.updateRow();
也可以使用列索引:
rs.updateDouble(4, 1100.0);
rs.updateRow();
5.4. 插入新行
使用 moveToInsertRow()
进入插入模式,设置各列值后调用 insertRow()
插入:
rs.moveToInsertRow();
rs.updateString("name", "Venkat");
rs.updateString("position", "DBA");
rs.updateDouble("salary", 925.0);
rs.insertRow();
rs.moveToCurrentRow();
5.5. 删除行
先定位到目标行,再调用 deleteRow()
:
rs.absolute(2);
rs.deleteRow();
6. 游标的可保持性(Holdability)
控制事务提交后 ResultSet
是否仍然保持打开状态。
6.1. 可保持性类型
✅ CLOSE_CURSORS_AT_COMMIT
– 提交后关闭游标
✅ HOLD_CURSORS_OVER_COMMIT
– 提交后保持游标打开
不是所有数据库都支持两种类型。可以通过以下方式检查:
boolean isCloseCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
boolean isOpenCursorSupported = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
boolean defaultHoldability = dbmd.getResultSetHoldability();
6.2. 创建可保持的 ResultSet
示例:
Statement pstmt = dbConnection.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResultSet.HOLD_CURSORS_OVER_COMMIT)
在事务提交后继续使用 ResultSet
:
dbConnection.setAutoCommit(false);
ResultSet rs = pstmt.executeQuery("select * from employees");
while (rs.next()) {
if(rs.getString("name").equalsIgnoreCase("john")) {
rs.updateString("name", "John Doe");
rs.updateRow();
dbConnection.commit();
}
}
rs.last();
⚠️ 注意:MySQL 默认支持 HOLD_CURSORS_OVER_COMMIT
,MSSQL 支持 CLOSE_CURSORS_AT_COMMIT
。
7. 控制获取数据的批次大小(Fetch Size)
用于控制每次从数据库获取多少行数据,避免内存溢出。
7.1. 在 Statement
上设置
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
7.2. 在 ResultSet
上设置
ResultSet rs = pstmt.executeQuery();
rs.setFetchSize(20);
动态调整示例:
int rowCount = 0;
while (rs.next()) {
if (rowCount == 30) {
rs.setFetchSize(20);
}
rowCount++;
}
8. 总结
本文详细介绍了 ResultSet
的各种使用方式,包括遍历、更新、元数据获取、游标导航、事务保持性及批量加载控制。需要注意的是,很多高级特性依赖于数据库的支持,使用前务必确认兼容性。