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<String, Cliente> loadCustomerList() throws SQLException {
        Map<String, Cliente> 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<Item> loadItemList() throws SQLException {
        List<Item> 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),然后在处理订单时直接从内存中查找。

What-the-Diff
What-the-Diff

检查请求差异,自动生成更改描述

下载

改进后的ConnectionMysql和PruebaComercio示例:

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

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

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

    // loadOrderList 现在依赖于外部提供的客户和产品映射
    public List<Pedido> loadOrderList(Map<String, Cliente> allClients, Map<String, Producto> allProducts) throws SQLException {
        List<Pedido> 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<String, Cliente> listaClientes;
    private Map<String, Producto> listaProductos; // 用于存储产品,方便通过名称查找
    private List<Pedido> 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<Pedido> loadOrderListWithJoin() throws SQLException {
        List<Pedido> 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<Pedido> 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数据库应用的性能和稳定性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

1133

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

340

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

381

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2152

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

380

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

1663

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

585

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

440

2024.04.29

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

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

精品课程

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

共48课时 | 2.5万人学习

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

共3课时 | 0.3万人学习

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

共1课时 | 846人学习

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

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