ResultSet操作偶发NullPointerException与SQLException问题求助
问题分析与解决办法
你的问题主要源于多线程环境下共享数据库连接实例以及资源管理不当,下面具体拆解问题并给出可落地的修复方案:
核心问题点
线程安全隐患:共享成员变量
dbConnection
Web应用是多线程模型(每个请求对应一个独立线程),而你的dbConnection是类的成员变量,当多个请求同时调用getAvailableDates时:- 线程A刚拿到
ResultSet准备遍历,线程B可能已经通过connect()重新赋值了dbConnection,或者在finally块关闭了连接,导致线程A的ResultSet关联的连接已失效,从而抛出Operation not allowed after ResultSet closed异常。 - 如果
connect()获取连接失败(比如数据库宕机、连接池满),dbConnection会保持null,后续操作可能导致ResultSet内部状态异常,触发NullPointerException。
- 线程A刚拿到
冗余的
ResultSet空判断
JDBC规范中,executeQuery()永远不会返回null——哪怕查询结果为空,也会返回一个空的ResultSet实例,所以代码里if (rs == null)的判断完全没必要,也不会被触发。SQL注入风险
你通过字符串拼接生成SQL语句:"SELECT AVAILDATE FROM AVAILABILITY WHERE LISTINGNUM = " +listingNumber +";",这种写法存在严重的SQL注入漏洞,恶意用户可以构造特殊参数破坏数据库。资源关闭的不严谨性
当前finally块中,如果rs.close()抛出异常,会导致statement和dbConnection无法正常关闭,虽然这不是当前报错的直接原因,但会引发资源泄漏问题。
修复后的代码
1. 重构getAvailableDates方法(用局部变量管理资源,避免线程共享)
public HashSet<LocalDate> getAvailableDates(long listingNumber) throws SQLException { // 用局部变量存储连接、语句、结果集,彻底避免多线程冲突 Connection dbConnection = null; PreparedStatement statement = null; ResultSet rs = null; String getDatesAvail = "SELECT AVAILDATE FROM AVAILABILITY WHERE LISTINGNUM = ?"; HashSet<LocalDate> avail = new HashSet<>(); try { // 改造connect方法返回连接对象,不再依赖成员变量 dbConnection = connect(); if (dbConnection == null) { throw new SQLException("Failed to establish database connection"); } // 使用PreparedStatement防止SQL注入 statement = dbConnection.prepareStatement(getDatesAvail); statement.setLong(1, listingNumber); // 安全绑定参数 rs = statement.executeQuery(); while(rs.next()) { avail.add(LocalDate.parse(rs.getString("AVAILDATE"), DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } } catch (SQLException e) { System.err.println("Error fetching available dates: " + e.getMessage()); e.printStackTrace(); throw e; // 保留原始异常信息,不要抛出空的SQLException } finally { // 逆序关闭资源,每个操作单独捕获异常,避免一个失败导致其他资源泄漏 try { if (rs != null) rs.close(); } catch (SQLException e) { System.err.println("Failed to close ResultSet: " + e.getMessage()); } try { if (statement != null) statement.close(); } catch (SQLException e) { System.err.println("Failed to close PreparedStatement: " + e.getMessage()); } try { if (dbConnection != null) dbConnection.close(); } catch (SQLException e) { System.err.println("Failed to close Connection: " + e.getMessage()); } } return avail; }
2. 改造connect方法(返回Connection,不再依赖成员变量)
private Connection connect() { try { // 新版本JDBC驱动可省略Class.forName,保留也不影响兼容性 Class.forName(DB_DRIVER); return DriverManager.getConnection(DB_CONNECTION, DB_USER, DB_PASSWORD); } catch (ClassNotFoundException e) { System.err.println("JDBC Driver not found: " + e.getMessage()); } catch (SQLException e) { System.err.println("Database connection failed: " + e.getMessage()); e.printStackTrace(); } return null; }
额外优化建议
- 使用数据库连接池:直接用
DriverManager获取连接性能差且易泄漏,建议替换为HikariCP、Apache DBCP等连接池组件,它们会自动管理连接的创建、复用和关闭,线程安全且性能更优。 - 使用try-with-resources语法:Java 7+支持该语法,可自动关闭实现
AutoCloseable接口的资源(Connection、Statement、ResultSet均实现),代码更简洁安全:public HashSet<LocalDate> getAvailableDates(long listingNumber) throws SQLException { String getDatesAvail = "SELECT AVAILDATE FROM AVAILABILITY WHERE LISTINGNUM = ?"; HashSet<LocalDate> avail = new HashSet<>(); try (Connection dbConnection = connect(); PreparedStatement statement = dbConnection.prepareStatement(getDatesAvail)) { statement.setLong(1, listingNumber); try (ResultSet rs = statement.executeQuery()) { while(rs.next()) { avail.add(LocalDate.parse(rs.getString("AVAILDATE"), DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } } } catch (SQLException e) { System.err.println("Error fetching available dates: " + e.getMessage()); e.printStackTrace(); throw e; } return avail; }
内容的提问来源于stack exchange,提问作者Lyndt
相关产品推荐
相关产品推荐

