Java ResultSet 深度解析:高效数据处理与实践指南

Java ResultSet接口是java.sql包的核心组成部分,它是JDBC框架中的关键组件。ResultSet对象主要用于访问从关系数据库查询并检索到的结果集。

ResultSet维护一个指向查询结果中单行的光标(或称指针)。通过ResultSet提供的导航和获取方法,我们可以逐行迭代并访问数据库记录。此外,ResultSet还支持数据的更新操作。

Java ResultSet层次结构

Java ResultSet 类层次结构

上图清晰展示了ResultSet在JDBC框架中的位置。通常,ResultSet是通过执行SQL查询(使用StatementPreparedStatementCallableStatement)后获得的。

AutoCloseableWrapperResultSet的超接口。接下来,我们将探讨如何在Java程序中实际使用ResultSet

ResultSet 示例

我们将以MySQL为例进行演示。请使用以下数据库脚本创建一个数据库和表,并插入一些记录:

create database empdb;

use empdb;

create table tblemployee (empid integer primary key, firstname varchar(32), lastname varchar(32), dob date);

insert into tblemployee values  (1, 'Mike', 'Davis',' 1998-11-11');
insert into tblemployee values  (2, 'Josh', 'Martin', '1988-10-22');
insert into tblemployee values  (3, 'Ricky', 'Smith', '1999-05-11');

现在,让我们看一个示例程序,该程序将从表中获取记录并打印到控制台。请确保您的项目类路径中已包含MySQL JDBC驱动程序。

这是文章《Java ResultSet教程》的第2部分(共9部分)。

package com.scdev.examples;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;

/**
 * Java ResultSet 示例:检索记录。
 *
 * @author scdev
 *
 */

public class ResultSetDemo {

	public static void main(String[] args) {
		String query = "select empid, firstname, lastname, dob from tblemployee";
		Connection conn = null;
		Statement stmt = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/empdb", "root", "root");
			stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(query);
			while (rs.next()) {
				Integer empId = rs.getInt(1);
				String firstName = rs.getString(2);
				String lastName = rs.getString(3);
				Date dob = rs.getDate(4);
				System.out.println("员工ID:" + empId);
				System.out.println("名:" + firstName);
				System.out.println("姓:" + lastName);
				System.out.println("出生日期:" + dob);
				System.out.println("");
			}
			rs.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				stmt.close();
				conn.close();
			} catch (Exception e) {}
		}
	}
}

产出。

empId:1
firstName:Mike
lastName:Davis
dob:1998-11-11

empId:2
firstName:Josh
lastName:Martin
dob:1988-10-22

empId:3
firstName:Ricky
lastName:Smith
dob:1999-05-11

解释:

  • 通过调用 Statement 实例的 executeQuery 方法可以获取 ResultSet。最初,ResultSet 的游标指向第一行之前的位置。
  • ResultSet 的 next 方法将游标移动到下一行。如果还有更多行,则返回 true,否则返回 false
  • 我们可以使用 ResultSet 提供的 getter 方法(例如 getInt()getString()getDate())从 ResultSet 中获取数据。
  • 所有的 getter 方法都有两种变体。第一种变体接受列索引作为参数,第二种变体接受列名作为参数。
  • 最后,我们需要在 ResultSet 实例上调用 close 方法,以便正确清理所有资源。

结果集类型和并发性

在创建 Statement、PreparedStatement 或 CallableStatement 实例时,我们可以指定 ResultSet 的类型和并发方式。

创建语句对象时,指定结果集类型和结果集并发性的方法是 statement.createStatement(int resultSetType, int resultSetConcurrency)

结果集类型

1) 只能向前(ResultSet.TYPE_FORWARD_ONLY)

创建 Statement、PreparedStatement 或 CallableStatement 实例时,我们可以获取这种类型的 ResultSet。这种 ResultSet 实例只能从第一行向最后一行方向移动。通过调用 next() 方法,可以将 ResultSet 向前移动一行。

Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("select * from tbluser");

2) 不敏感滚动(ResultSet.TYPE_SCROLL_INSENSITIVE)

不敏感滚动的 ResultSet 可以在向前和向后方向上滚动。还可以通过调用 absolute() 方法滚动到绝对位置。但它对数据更改不敏感。它只在查询执行并获得 ResultSet 时具有数据。在获取数据后对数据所做的更改不会反映在其中。

Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,  		ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("select * from tbluser");

3) 滚动敏感(ResultSet.TYPE_SCROLL_SENSITIVE)

滚动敏感的 ResultSet 可以在向前和向后方向上滚动。还可以通过调用 absolute() 方法将其滚动到绝对位置。但它对数据变化敏感。在打开时,它将反映对数据所做的更改。

Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,  		ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("select * from tbluser");

结果集并发性

只读(ResultSet.CONCUR_READ_ONLY)

这是默认的并发模型。我们只能对ResultSet实例执行只读操作,不允许进行更新操作。

可更新(ResultSet.CONCUR_UPDATABLE)

在这种情况下,我们可以对ResultSet实例进行更新操作。

结果集方法

我们可以将ResultSet的方法分为以下几个类别:

  • 导航方法(Navigational Methods)
  • 获取/读取方法(Getter/Reader Methods)
  • 设置/更新方法(Setter/Updater Methods)
  • 其他方法(Miscellaneous Methods)- 包括 close() 和 getMetaData()

1. 结果集导航方法

Java ResultSet 导航方法详解(教程第5部分,共9部分)

本部分将深入探讨 ResultSet 接口中用于游标导航的关键方法。理解这些方法对于高效地处理数据库查询结果至关重要。

  • boolean absolute(int row) throws SQLException:此方法将 ResultSet 游标移动到指定的行。如果操作成功,则返回 true
  • void afterLast() throws SQLException:此方法将 ResultSet 游标移动到最后一行之后的位置。
  • void beforeFirst() throws SQLException:此方法将 ResultSet 游标移动到第一行之前的位置。
  • boolean first() throws SQLException:此方法将 ResultSet 游标移动到第一行。
  • boolean last() throws SQLException:此方法将 ResultSet 游标移动到最后一行。
  • boolean next() throws SQLException:此方法将 ResultSet 游标移动到下一行。
  • boolean previous() throws SQLException:此方法将 ResultSet 游标移动到上一行。
package com.scdev.examples;
import java.sql.*;

/**
 * Java ResultSet 导航方法示例。
 * 
 * @author scdev
 *
 */
public class ResultSetDemo {

	public static void main(String[] args) {
		String query = "select empid, firstname, lastname, dob from tblemployee";
		Connection conn = null;
		Statement stmt = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/empdb", "root", "root");
			stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
			ResultSet rs = stmt.executeQuery(query);
			System.out.println("表中的所有行:");
			while (rs.next()) { 
				// 通过调用 next() 方法移动到下一行
				displayData(rs);
			}
			System.out.println("现在直接跳转到第2行:");
			rs.absolute(2); // 直接跳转到第2行
			displayData(rs);
			System.out.println("现在跳转到上一行:");
			rs.previous(); 
			// 移动到第1行,即第2行的上一行
			displayData(rs);
			rs.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				stmt.close();
				conn.close();
			} catch (Exception e) {
			}
		}
	}

	public static void displayData(ResultSet rs) throws SQLException {
		System.out.println("员工ID:" + rs.getInt(1));
		System.out.println("名:" + rs.getString(2));
		System.out.println("姓:" + rs.getString(3));
		System.out.println("出生日期:" + rs.getDate(4));
		System.out.println("");
	}
}

上述代码演示了如何使用 ResultSet 的导航方法来遍历和定位数据。请注意,为了使用 absolute()previous() 等方法,创建 Statement 时需要指定 ResultSet.TYPE_SCROLL_INSENSITIVE 类型,这表示 ResultSet 是可滚动且对底层数据源的更改不敏感的。ResultSet.CONCUR_READ_ONLY 表示 ResultSet 是只读的,不能用于更新数据。

输出结果:

表中的所有行:
员工ID:101
名:John
姓:Doe
出生日期:1980-01-15

员工ID:102
名:Jane
姓:Smith
出生日期:1985-03-20

现在直接跳转到第2行:
员工ID:102
名:Jane
姓:Smith
出生日期:1985-03-20

现在跳转到上一行:
员工ID:101
名:John
姓:Doe
出生日期:1980-01-15
表中的所有行=>
empId:1
firstName:Mike
lastName:Davis
dob:1998-11-11

empId:2
firstName:Josh
lastName:Martin
dob:1988-10-22

empId:3
firstName:Ricky
lastName:Smith
dob:1999-05-11

现在直接跳转到第2行=>
empId:2
firstName:Josh
lastName:Martin
dob:1988-10-22

现在回到上一行=>
empId:1
firstName:Mike
lastName:Davis
dob:1998-11-11

2. 结果集获取/读取方法

  • int getInt(int columnIndex) throws SQLException: 此方法将指定列索引的值作为int类型返回。
  • long getLong(int columnIndex) throws SQLException: 此方法将指定列索引的值作为long类型返回。
  • String getString(int columnIndex) throws SQLException: 此方法将指定列索引的值作为String类型返回。
  • java.sql.Date getDate(int columnIndex) throws SQLException: 此方法将指定列索引的值作为java.sql.Date类型返回。
  • int getInt(String columnLabel) throws SQLException: 此方法将指定列名的值作为int类型返回。
  • long getLong(String columnLabel) throws SQLException: 此方法将指定列名的值作为long类型返回。
  • String getString(String columnLabel) throws SQLException: 此方法将指定列名的值作为String类型返回。
  • java.sql.Date getDate(String columnLabel) throws SQLException: 此方法将指定列名的值作为java.sql.Date类型返回。
  • ResultSet包含返回其他基本数据类型(如booleanfloatdouble)的getter方法。它还包含从数据库获取数组和二进制数据的方法。

3. 结果集设置/更新方法

  • void updateInt(int columnIndex, int x) throws SQLException: 此方法使用一个int值更新当前行指定列的值。
  • void updateLong(int columnIndex, long x) throws SQLException: 此方法使用一个long值更新当前行指定列的值。
  • void updateString(int columnIndex, String x) throws SQLException: 此方法使用一个String值更新当前行指定列的值。
  • void updateDate(int columnIndex, java.sql.Date x) throws SQLException: 此方法使用一个java.sql.Date值更新当前行指定列的值。
  • void updateInt(String columnLabel, int x) throws SQLException: 此方法使用一个int值更新当前行指定列标签的值。
  • void updateLong(String columnLabel, long x) throws SQLException: 此方法使用一个long值更新当前行指定列标签的值。
  • void updateString(String columnLabel, String x) throws SQLException: 此方法使用一个String值更新当前行指定列标签的值。
  • void updateDate(String columnLabel, java.sql.Date x) throws SQLException: 此方法使用一个java.sql.Date值更新当前行指定列标签的值。

注意:Setter / Updater 方法不会直接更新数据库中的值。只有在调用insertRow()updateRow()方法之后,数据库中的值才会被插入或更新。

package com.scdev.examples;
import java.sql.*;

/**
 * 使用更新方法(updater methods)的Java ResultSet示例。
 * 
 * @author scdev
 *
 */

public class ResultSetUpdateDemo {

	public static void main(String[] args) {
		String query = "select empid, firstname, lastname, dob from tblemployee";
		Connection conn = null;
		Statement stmt = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/empdb", "root", "root");
			stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
			ResultSet rs = stmt.executeQuery(query);
			System.out.println("现在直接跳转到第二行进行更新");
			if (rs.absolute(2)) { 
				// 直接跳转到第二行
				System.out.println("现有姓名:" + rs.getString("firstName"));
				rs.updateString("firstname", "Tyson");
				rs.updateRow();
			}
			rs.beforeFirst(); // 回到起始位置
			System.out.println("表格中的所有行如下:");
			while (rs.next()) { 
			// 通过调用next()方法移动到下一行
				displayData(rs);
			}
			rs.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				stmt.close();
				conn.close();
			} catch (Exception e) {
			}
		}
	}

	public static void displayData(ResultSet rs) throws SQLException {
		System.out.println("员工ID:" + rs.getInt(1));
		System.out.println("名字:" + rs.getString(2));
		System.out.println("姓氏:" + rs.getString(3));
		System.out.println("出生日期:" + rs.getDate(4));
		System.out.println("");
	}
}

输出:

现在直接跳转到第二行进行更新
现有姓名: [原始第二行的名字]
表格中的所有行如下:
员工ID: [第一行ID]
名字: [第一行名字]
姓氏: [第一行姓氏]
出生日期: [第一行出生日期]

员工ID: [第二行ID]
名字: Tyson
姓氏: [第二行姓氏]
出生日期: [第二行出生日期]

[…其他行数据…]

现在直接跳转到第二行进行更新
现有名称:Josh
表中所有行:
empId: 1
firstName: Mike
lastName: Davis
dob: 1998-11-11

empId: 2
firstName: Tyson
lastName: Martin
dob: 1988-10-22

empId: 3
firstName: Ricky
lastName: Smith
dob: 1999-05-11

4. 杂项结果集方法

  • void close() throws SQLException: 此方法用于释放与 ResultSet 实例关联的资源。必须调用此方法,否则会导致资源泄漏。
  • ResultSetMetaData getMetaData() throws SQLException: 此方法返回 ResultSetMetaData 实例。它提供有关查询输出列的类型和属性信息。

参考资料:Java文档

bannerAds