0

0

Java JDBC“结果集结束之后”错误详解与优化策略

碧海醫心

碧海醫心

发布时间:2025-11-19 15:59:21

|

768人浏览过

|

来源于php中文网

原创

Java JDBC“结果集结束之后”错误详解与优化策略

本文深入探讨了java jdbc操作mysql数据库时常见的“after end of result set”错误,该错误通常源于不当的数据库资源(如`connection`、`preparedstatement`、`resultset`)管理,特别是当这些资源被共享或未及时关闭时。文章分析了导致此问题的代码模式,并提供了三种优化策略:局部化资源、利用内存缓存进行查找以及使用sql join语句,旨在帮助开发者构建更健壮、高效的jdbc应用。

Java JDBC“After end of result set”错误分析与解决方案

在使用Java JDBC与MySQL数据库交互时,开发者可能会遇到java.sql.SQLException: After end of result set这样的错误。这个错误通常发生在尝试访问一个已经遍历到末尾或者已经被关闭/覆盖的ResultSet对象时。本教程将深入分析导致此问题的原因,并提供几种健壮且高效的解决方案。

1. 问题根源:共享的数据库资源与不当管理

在提供的代码示例中,ConnectionMysql类将conexion (Connection), st (PreparedStatement), 和 rs (ResultSet) 作为成员变量。这是导致“After end of result set”错误的核心原因。

让我们详细分析loadOrderList方法:

  1. loadOrderList方法首先获取一个新的数据库连接,并执行查询以获取订单列表。
  2. 它开始遍历订单的ResultSet (rs)。
  3. 在循环内部,为了获取每个订单关联的客户和产品信息,它调用了findItem和findClient方法。
  4. findItem方法内部会调用loadItemList,而findClient方法内部会调用loadCustomerList。
  5. loadItemList和loadCustomerList方法都会:
    • 重新获取一个新的数据库连接,并赋值给共享的conexion成员变量。
    • 执行新的查询,并将其PreparedStatement和ResultSet分别赋值给共享的st和rs成员变量。

问题在于: 当loadItemList或loadCustomerList执行时,它们会覆盖ConnectionMysql实例中正在被loadOrderList使用的st和rs成员变量。一旦loadOrderList的循环尝试在下一次迭代中访问其原始的rs(现在已经被覆盖),就会导致“After end of result set”错误,因为它实际上在尝试访问一个不再指向有效结果集或者已经被关闭的结果集。

立即学习Java免费学习笔记(深入)”;

此外,每次调用loadCustomerList、loadItemList和loadOrderList都会创建一个新的数据库连接,这不仅效率低下,而且如果不及时关闭,还会导致资源泄露。

2. 优化策略一:局部化数据库资源

最直接且根本的解决方案是避免共享Connection、PreparedStatement和ResultSet对象。每个方法都应该在自己的作用域内创建、使用并关闭这些资源。

改进后的ConnectionMysql示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 假设Cliente, Item, Producto, Pedido是已定义的POJO类
// 为简化示例,这里省略了它们的定义

public class ConnectionMysql {
    private String url;
    private String user;
    private String password;

    public ConnectionMysql(String url, String user, String password) {
        this.url = url;
        this.user = user;
        this.password = password;
        // 确保MySQL驱动已加载
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.err.println("MySQL JDBC Driver not found.");
            e.printStackTrace();
        }
    }

    // 辅助方法,用于获取连接
    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection(this.url, this.user, this.password);
    }

    public Map loadCustomerList() throws SQLException {
        Map listaClientes = new HashMap<>();
        String query = "SELECT * FROM cliente";
        // 使用try-with-resources确保资源自动关闭
        try (Connection conn = getConnection();
             PreparedStatement st = conn.prepareStatement(query);
             ResultSet rs = st.executeQuery()) {

            while (rs.next()) {
                String dni = rs.getString(1);
                String nombre = rs.getString(2);
                String apellidos = rs.getString(3);
                int descuento = rs.getInt(4);
                listaClientes.put(dni, new Cliente(dni, nombre, apellidos, descuento));
            }
        } // conn, st, rs 会在此处自动关闭
        return listaClientes;
    }

    public List loadItemList() throws SQLException {
        List listaItems = new ArrayList<>();
        String query = "SELECT * FROM item;";
        try (Connection conn = getConnection();
             PreparedStatement st = conn.prepareStatement(query);
             ResultSet rs = st.executeQuery()) {

            while (rs.next()) {
                String nombre = rs.getString(1);
                double precio = rs.getDouble(2);
                int existencias = rs.getInt(3);
                listaItems.add(new Item(nombre, precio, existencias));
            }
        }
        return listaItems;
    }

    // findClient 和 findItem 不再直接访问数据库,而是依赖于外部提供的列表
    // 这需要在调用时先加载所有客户和产品,然后进行内存查找
    // 见优化策略二

    // loadOrderList 方法也需要修改,以避免在循环中重复查询数据库
    // 见优化策略二和三
}

// 示例 POJO 类 (请根据实际情况补充完整)
class Cliente {
    String dni;
    String nombre;
    String apellidos;
    int descuento;
    public Cliente(String dni, String nombre, String apellidos, int descuento) {
        this.dni = dni; this.nombre = nombre; this.apellidos = apellidos; this.descuento = descuento;
    }
    public String getDni() { return dni; }
}
class Item {
    String nombre;
    double precio;
    int existencias;
    public Item(String nombre, double precio, int existencias) {
        this.nombre = nombre; this.precio = precio; this.existencias = existencias;
    }
    public String getNombre() { return nombre; }
}
class Producto extends Item { // 假设Producto继承自Item或有相似结构
    public Producto(String nombre, double precio, int existencias) { super(nombre, precio, existencias); }
}
class Pedido {
    Producto producto;
    int unidad;
    Cliente cliente;
    public Pedido(Producto producto, int unidad, Cliente cliente) {
        this.producto = producto; this.unidad = unidad; this.cliente = cliente;
    }
}

注意事项:

  • try-with-resources: 这是Java 7及更高版本推荐的资源管理方式,可以确保Connection、Statement和ResultSet在代码块结束时自动关闭,即使发生异常。
  • 连接池: 在生产环境中,每次操作都创建新连接是低效的。应使用连接池(如HikariCP, c3p0, DBCP等)来管理数据库连接,以提高性能和资源复用。

3. 优化策略二:预加载数据与内存查找

原代码在loadOrderList的循环中,为每个订单调用findClient和findItem,而这两个方法又会重复地加载整个客户列表和商品列表。这种模式效率极低。更好的方法是先一次性加载所有客户和产品数据到内存中(例如Map),然后在处理订单时直接从内存中查找。

Meku
Meku

AI应用和网页开发工具

下载

改进后的ConnectionMysql和PruebaComercio示例:

// ... ConnectionMysql 类中 loadCustomerList 和 loadItemList 保持不变 (如上) ...

public class ConnectionMysql {
    // ... (构造函数和getConnection方法) ...

    public Map loadCustomerList() throws SQLException { /* ... 保持不变 ... */ return new HashMap<>(); }
    public List loadItemList() throws SQLException { /* ... 保持不变 ... */ return new ArrayList<>(); }

    // loadOrderList 现在依赖于外部提供的客户和产品映射
    public List loadOrderList(Map allClients, Map allProducts) throws SQLException {
        List listaPedidos = new ArrayList<>();
        String query = "SELECT `unidad`, `nombre_producto`, `dni_cliente` FROM pedido WHERE `Numero Factura` IS NULL"; // 假设字段名

        try (Connection conn = getConnection();
             PreparedStatement st = conn.prepareStatement(query);
             ResultSet rs = st.executeQuery()) {

            while (rs.next()) {
                int unidad = rs.getInt("unidad"); // 根据实际列名获取
                String nombreProducto = rs.getString("nombre_producto");
                String dniCliente = rs.getString("dni_cliente");

                Producto producto = allProducts.get(nombreProducto);
                Cliente cliente = allClients.get(dniCliente);

                if (producto != null && cliente != null) {
                    listaPedidos.add(new Pedido(producto, unidad, cliente));
                } else {
                    // 处理找不到客户或产品的情况,例如记录日志
                    System.err.println("警告: 订单关联的客户或产品未找到. 产品名称: " + nombreProducto + ", 客户DNI: " + dniCliente);
                }
            }
        }
        return listaPedidos;
    }
}

// 主操作类 PruebaComercio
public class PruebaComercio {

    private Map listaClientes;
    private Map listaProductos; // 用于存储产品,方便通过名称查找
    private List listaPedidos;
    // ... 其他成员变量 ...
    private ConnectionMysql connection;

    public PruebaComercio() throws SQLException {
        this.connection = new ConnectionMysql("jdbc:mysql://localhost:3306/db_name", "user", "password"); // 替换为你的数据库信息

        // 1. 一次性加载所有客户和产品数据
        this.listaClientes = connection.loadCustomerList();
        this.listaProductos = new HashMap<>();
        for (Item item : connection.loadItemList()) {
            this.listaProductos.put(item.getNombre(), (Producto) item); // 假设Item和Producto兼容
        }

        // 2. 加载订单时,传入已加载的客户和产品列表进行查找
        this.listaPedidos = connection.loadOrderList(this.listaClientes, this.listaProductos);

        // ... 其他初始化 ...
    }
}

优点:

  • 显著减少数据库查询次数,提高整体性能。
  • 避免了“After end of result set”错误,因为所有数据库操作都是独立的,且资源局部化。

4. 优化策略三:利用SQL JOIN语句

最推荐且最高效的方式是使用SQL JOIN语句在数据库层面将相关数据一次性查询出来,而不是在Java代码中进行多次查询和查找。这利用了数据库的优化能力,减少了网络往返次数。

SQL JOIN查询示例:

假设pedido表包含nombre_producto和dni_cliente字段,分别关联item表和cliente表。

SELECT
    p.unidad,
    i.nombre AS producto_nombre,
    i.precio AS producto_precio,
    i.existencias AS producto_existencias,
    c.dni AS cliente_dni,
    c.nombre AS cliente_nombre,
    c.apellidos AS cliente_apellidos,
    c.descuento AS cliente_descuento
FROM
    pedido p
JOIN
    item i ON p.`nombre_producto` = i.nombre
JOIN
    cliente c ON p.`dni_cliente` = c.dni
WHERE
    p.`Numero Factura` IS NULL;

改进后的loadOrderList方法(使用JOIN):

public class ConnectionMysql {
    // ... (构造函数和getConnection方法) ...

    public List loadOrderListWithJoin() throws SQLException {
        List listaPedidos = new ArrayList<>();
        String query = "SELECT " +
                       "    p.unidad, " +
                       "    i.nombre AS producto_nombre, " +
                       "    i.precio AS producto_precio, " +
                       "    i.existencias AS producto_existencias, " +
                       "    c.dni AS cliente_dni, " +
                       "    c.nombre AS cliente_nombre, " +
                       "    c.apellidos AS cliente_apellidos, " +
                       "    c.descuento AS cliente_descuento " +
                       "FROM " +
                       "    pedido p " +
                       "JOIN " +
                       "    item i ON p.`nombre_producto` = i.nombre " +
                       "JOIN " +
                       "    cliente c ON p.`dni_cliente` = c.dni " +
                       "WHERE " +
                       "    p.`Numero Factura` IS NULL";

        try (Connection conn = getConnection();
             PreparedStatement st = conn.prepareStatement(query);
             ResultSet rs = st.executeQuery()) {

            while (rs.next()) {
                // 构建Producto对象
                String prodNombre = rs.getString("producto_nombre");
                double prodPrecio = rs.getDouble("producto_precio");
                int prodExistencias = rs.getInt("producto_existencias");
                Producto producto = new Producto(prodNombre, prodPrecio, prodExistencias);

                // 构建Cliente对象
                String cliDni = rs.getString("cliente_dni");
                String cliNombre = rs.getString("cliente_nombre");
                String cliApellidos = rs.getString("cliente_apellidos");
                int cliDescuento = rs.getInt("cliente_descuento");
                Cliente cliente = new Cliente(cliDni, cliNombre, cliApellidos, cliDescuento);

                // 获取订单单位
                int unidad = rs.getInt("unidad");

                listaPedidos.add(new Pedido(producto, unidad, cliente));
            }
        }
        return listaPedidos;
    }
}

// 主操作类 PruebaComercio
public class PruebaComercio {
    // ... (其他成员变量) ...
    private List listaPedidos;
    private ConnectionMysql connection;

    public PruebaComercio() throws SQLException {
        this.connection = new ConnectionMysql("jdbc:mysql://localhost:3306/db_name", "user", "password");

        // 直接调用使用JOIN的方法加载订单
        this.listaPedidos = connection.loadOrderListWithJoin();

        // ... 其他初始化 ...
    }
}

优点:

  • 最高效: 减少了数据库交互次数,利用数据库引擎进行高效的数据关联。
  • 代码简洁: 避免了复杂的内存查找逻辑。
  • 数据一致性: 一次性获取所有相关数据,减少了因多次查询导致的数据不一致风险。

总结与最佳实践

“After end of result set”错误通常是由于JDBC资源管理不当引起的。为了避免此类问题并构建健壮的Java数据库应用程序,请遵循以下最佳实践:

  1. 局部化资源: 始终在方法内部声明和管理Connection、PreparedStatement和ResultSet,避免将它们作为类的成员变量共享。
  2. 使用try-with-resources: 利用Java 7+的try-with-resources语句确保数据库资源(Connection, Statement, ResultSet)在不再需要时自动关闭。
  3. 连接池: 在生产环境中,务必使用数据库连接池(如HikariCP、c3p0等)来管理和复用数据库连接,而不是每次都创建新连接。
  4. 优化数据访问模式:
    • 预加载/缓存: 对于频繁查询的静态或半静态数据,可以一次性加载到内存中(如Map),然后进行内存查找。
    • SQL JOIN: 利用SQL的强大功能,通过JOIN语句在数据库层面关联数据,一次性获取所有所需信息,减少网络往返和Java代码中的复杂逻辑。
  5. 错误处理: 妥善处理SQLException,记录详细日志,以便于问题诊断。

通过采纳这些策略,开发者可以有效避免“After end of result set”这类常见错误,并显著提升Java数据库应用的性能和稳定性。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

830

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

735

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

733

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

396

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16925

2023.08.03

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

25

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 1.7万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 785人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号