JDBC ResultSet指南
1. 概述
Java数据库连接(JDBC) API提供了从Java应用程序访问数据库的功能。只要支持的JDBC驱动程序可用,我们就可以使用JDBC连接到任何数据库。 ResultSet是执行数据库查询生成的数据表。在本教程中,我们将深入研究ResultSet API
2.生成一个ResultSet
首先,我们通过对实现语句接口的任何对象调用executeQuery()来检索ResultSet。PreparedStatement和CallableStatement都是语句的子接口:
PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();
ResultSet对象维护一个指向结果集中当前行的游标。
while(rs.next()) {
String name = rs.getString("name");
Integer empId = rs.getInt("emp_id");
Double salary = rs.getDouble("salary");
String position = rs.getString("position");
}
同样,可以将列的索引号与getX()方法一起使用,而不是与列名一起使用。索引号是SQL select语句中的列索引编号 如果select语句没有列出列名,则索引遍号是表中列的序列。列索引编号从1开始:
3.3.从ResultSet检索元数据
在本节中,我们将了解如何检索关于ResultSet中的列属性和类型的信息。 首先,让我们在ResultSet上使用getMetaData()方法来获取ResultSetMetaData:
ResultSetMetaData metaData = rs.getMetaData();
接下来,让我们得到ResultSet中的列数:
Integer columnCount = metaData.getColumnCount();
此外,我们可以在元数据对象上使用以下任何方法来检索每个列的属性:
- *getColumnName(int columnNumber)* – 获取列名
- *getColumnLabel(int columnNumber)* – 获取列的标签,该标签在SQL查询中AS后面指定
- *getTableName(int columnNumber)* – 获取此列所属的表名
- *getColumnClassName(int columnNumber)* – 获取列的Java数据类型
- *getColumnTypeName(int columnNumber)* – 获取列在数据库中的数据类型
- *getColumnType(int columnNumber)* – 获取列的SQL数据类型
- *isAutoIncrement(int columnNumber)* – 指示列是否自动递增
- *isCaseSensitive(int columnNumber)* – 列是否区分大小写
- *isSearchable(int columnNumber)* – 指定列大小写是否重要
- *isCurrency(int columnNumber)* – 建议是否可以使用SQL查询的where子句中的列
- *isNullable(int columnNumber)* – 如果列不能为空,则返回0;如果列可以包含空值,则返回1;如果列的可空性未知,则返回2
- isSigned(int columnNumber) – 如果对列中的值进行了签名,则返回true,否则返回false
让我们遍历列来获得它们的属性:
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 Types
ResultSet类型表示我们将如何通过数据集:
- *TYPEFORWARDONLY* – 默认选项,其中游标从开始移动到结束。
- *TYPE_SCROLL_INSENSITIVE* – 我们的光标可以在数据集中向前和向后移动;如果在数据集中移动时底层数据发生了更改,则忽略这些更改;数据集包含数据库查询返回结果时的数据。
- TYPE_SCROLL_SENSITIVE – 与滚动不敏感类型类似,但是对于这种类型,数据集立即反映对底层数据的任何更改。
并不是所有数据库都支持所有的结果集类型。因此,让我们检查一下,是否通过在我们的DatabaseMetaData对象上使用supportsResultSetType来支持该类型:
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
4.2 可滚动结果集
要获得可滚动的ResultSet,我们需要在准备语句时传递一些额外的参数。
例如,我们可以通过使用 TYPESCROLLINSENSITIVE
或 TYPESCROLLSENSITIVE
作为结果集类型来获得可滚动的 ResultSet
:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
4.3 导航选项
我们可以在可滚动的ResultSet上使用以下选项:
- next() -从当前位置继续到下一行
- previous() -遍历到前一行
- first() -导航到ResultSet的第一行
- last() -导航到ResultSet的最后一行
- beforeFirst() -移动到开始;在调用此方法之后调用ResultSet上的next()将返回ResultSet中的第一行
- afterLast() -跳到最后;在执行此方法后调用ResultSet上的previous()将返回ResultSet中的最后一行
- relative(int numOfRows) -由numOfRows从当前位置前进或后退
- absolute(int rowNumber) -跳转到指定的行号
咱们来看个例子
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// iterate through the results from first to last
}
rs.beforeFirst(); // jumps back to the starting point, before the first row
rs.afterLast(); // jumps to the end of resultset
rs.first(); // navigates to the first row
rs.last(); // goes to the last row
rs.absolute(2); //jumps to 2nd row
rs.relative(-1); // jumps to the previous row
rs.relative(2); // jumps forward two rows
while (rs.previous()) {
// iterates from current row to the first row in backward direction
}
4.4 结果集行数
让我们使用getRow()获取ResultSet的当前行号。 首先,我们将导航到ResultSet的最后一行,然后使用getRow()获取记录的数量:
rs.last();
int rowCount = rs.getRow();
5 更新结果集中的数据
默认情况下,ResultSet是只读的。但是,我们可以使用可更新的ResultSet插入、更新和删除行。
5.1 ResultSet并发
并发模式指示我们的ResultSet是否可以更新数据。 CONCURREADONLY选项是默认选项,如果不需要使用ResultSet更新数据,应该使用它。 但是,如果我们需要更新ResultSet中的数据,那么应该使用CONCUR_UPDATABLE选项。并不是所有数据库都支持所有ResultSet类型的所有并发模式。因此,我们需要检查是否使用supportsResultSetConcurrency()方法支持我们想要的类型和并发模式:
DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetConcurrency(
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
5.2 获取可更新的结果集
为了获得一个可更新的ResultSet,我们需要在准备语句时传递一个额外的参数。为此,我们在创建语句时使用CONCURUPDATABLE作为第三个参数:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();
5.3 更新一行
在本节中,我们将使用上一节中创建可更新的ResultSet。 我们可以通过调用updateX()方法,传递要更新的列名和值来更新一行中的数据。我们可以在updateX()方法中使用任何受支持的数据类型来替代X。 让我们更新“salary”列,它的类型是double:
rs.updateDouble("salary", 1100.0);
注意,这只是更新了ResultSet中的数据,但是还没有将修改保存回数据库。 最后,我们调用updateRow()将更新保存到数据库:
rs.updateRow();
我们可以将列索引传递给updateX()方法,而不是列名。这类似于使用列索引使用getX()方法获取值。将列名或索引传递给updateX()方法会得到相同的结果:
rs.updateDouble(4, 1100.0);
rs.updateRow();
5.4 插入一行
现在,让我们使用可更新的ResultSet插入一个新行。 首先,我们将使用moveToInsertRow()移动游标来插入新行:
rs.moveToInsertRow();
接下来,我们必须调用updateX()方法来将信息添加到行中。我们需要为数据库表中的所有列提供数据。如果我们没有为每一列提供数据,则使用默认的列值:
rs.updateString("name", "Venkat");
rs.updateString("position", "DBA");
rs.updateDouble("salary", 925.0);
然后调用insertRow()将新行插入到数据库中:
rs.insertRow();
最后,让我们使用moveToCurrentRow()。这将使光标位置回到使用moveToInsertRow()方法插入新行之前的行:
rs.moveToCurrentRow();
5.5 删除一行
在本节中,我们将使用可更新的ResultSet删除一行。 首先,我们将导航到要删除的行。然后,我们调用deleteRow()方法来删除当前行:
rs.absolute(2);
rs.deleteRow();
6 保持能力
可持有性决定了我们的ResultSet在数据库事务结束时是打开还是关闭。
6.1 可保持性类型
如果事务提交后不需要ResultSet,则使用CLOSECURSORSATCOMMIT。 使用HOLDCURSORSOVERCOMMIT创建一个可保持的结果集。即使提交了数据库事务,也不会关闭可保持的ResultSet。 并非所有数据库都支持所有可持有性类型。 因此,让我们检查在我们的DatabaseMetaData对象上使用supportsResultSetHoldability()是否支持可持有性类型。然后,我们将使用getResultSetHoldability()获得数据库的默认可持有性:
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时将可保持性类型指定为最后一个参数。此参数在并发模式之后指定。
dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
让我们看看它是如何运作的。首先,我们创建一个语句,将HOLDCURSORSOVERCOMMIT的holdability设置为:
Statement pstmt = dbConnection.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResultSet.HOLD_CURSORS_OVER_COMMIT)
现在,让我们在检索数据时更新一行。这与我们前面讨论的update示例类似,只是在将update事务提交到数据库之后,我们将继续遍历ResultSet。这在MySQL和MSSQL数据库上都可以很好地工作:
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只支持HOLDCURSORSOVERCOMMIT。因此,即使我们使用CLOSECURSORSATCOMMIT,它也会被忽略。 MYSQL数据库支持CLOSECURSORSATCOMMIT。这意味着当我们提交事务时,ResultSet将被关闭。因此,在“Cursor is not open error”中提交事务结果后试图访问ResultSet。因此,我们不能从ResultSet检索更多的记录。
7 Fetch Size
通常,当将数据加载到ResultSet中时,数据库驱动程序决定从数据库中获取的行数。例如,在MySQL数据库上,ResultSet通常一次将所有记录加载到内存中。 然而,有时我们可能需要处理大量不适合JVM内存的记录。在本例中,我们可以在Statement或ResultSet对象上使用fetch size属性来限制最初返回的记录的数量。 每当需要额外的结果时,ResultSet就从数据库中获取另一批记录。使用fetch size属性,我们可以向数据库驱动程序提供关于每次数据库旅行要获取的行数的建议。我们指定的获取大小将应用于后续的数据库访问。 如果我们没有为ResultSet指定获取大小,那么将使用语句的获取大小。如果没有为Statement或ResultSet指定获取大小,则使用数据库默认值。
7.1 在Statement上使用Fetch SizeFetch Size
现在,让我们看看实际的fetch size on语句。我们将语句的获取大小设置为10条记录。如果我们的查询返回100条记录,那么将有10次数据库往返,每次加载10条记录:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
// iterate through the resultset
}
7.2 在ResultSet上使用Fetch SizeFetch Size
现在,让我们在前面的示例中使用ResultSet更改获取大小。 首先,我们将对Statement使用fetch大小。这允许我们的ResultSet在执行查询后初始加载10条记录。 然后,我们将修改ResultSet上的获取大小。这将覆盖我们先前在语句中指定的获取大小。因此,所有后续的行程将加载20条记录,直到加载所有记录为止。 因此,载入所有记录只需要6次数据库行程:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
rs.setFetchSize(20);
while (rs.next()) {
// iterate through the resultset
}
最后,我们将看到如何在迭代结果时修改ResultSet的获取大小。 与前面的示例类似,我们首先将语句的fetch大小设置为10。因此,我们的前3次数据库访问将在每次访问中加载10条记录。 然后,在读取第30条记录时,将ResultSet上的获取大小修改为20。因此,接下来的4次旅程将每次装载20条记录。 因此,我们需要7次数据库访问来加载所有100条记录:
PreparedStatement pstmt = dbConnection.prepareStatement(
"select * from employees",
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);
ResultSet rs = pstmt.executeQuery();
int rowCount = 0;
while (rs.next()) {
// iterate through the resultset
if (rowCount == 30) {
rs.setFetchSize(20);
}
rowCount++;
}
8 结论
在本文中,我们了解了如何使用ResultSet API从数据库检索和更新数据。我们讨论的一些高级特性依赖于我们使用的数据库。因此,在使用这些特性之前,我们需要检查对它们的支持。
原文链接:https://www.baeldung.com/jdbc-resultset
作者:Andriy Redko
译者:xieed
关注公众号
点击原文阅读更多