Connection.close()未正确关闭连接致数据库连接耗尽问题求助
我来帮你排查下这个连接耗尽的问题——明明调用了close()且connection.isClosed()返回true,却还是出现连接耗尽,大概率是代码里的细节漏洞或者JDBC使用方式的问题,咱们一步步来梳理:
1. 重复注册Driver的隐患
你的getConnection方法里每次获取连接都手动注册Driver:
Driver driver = (Driver) Class.forName(clazz).newInstance(); DriverManager.registerDriver(driver);
这会导致Driver被多次注册,干扰DriverManager的连接管理逻辑。而且JDBC 4.0+版本(对应JDK 1.6+)已经支持自动加载Driver,只要驱动包在classpath里,DriverManager会自动识别并加载,完全不需要手动实例化和注册。
修改后的getConnection方法可以简化成这样:
private Connection getConnection(JspWriter out, String host, String dbname, String port, String user, String pwd) { try { if (host == null || dbname == null) { throw new IllegalArgumentException("Invalid host or dbname"); } String url = "jdbc:mysql://" + host + ":" + port + "/" + dbname; // 移除手动Driver注册逻辑,JDBC会自动处理 return DriverManager.getConnection(url, user, pwd); } catch (Exception e) { // 改用System.err输出错误,同时把错误反馈到页面方便调试 System.err.println("Exception occurred while getting connection: " + e); try { out.println("Connection error: " + e.getMessage()); } catch (IOException ioEx) { ioEx.printStackTrace(); } } return null; }
2. 手动关闭资源的潜在漏洞
你在finally块里手动关闭ResultSet、PreparedStatement和Connection,但这种方式容易因为异常嵌套导致资源未被正确关闭。比如如果rs.close()抛出异常,后续的st.close()和connection.close()会不会被执行?虽然你用了独立的try-catch,但Java 7+提供的try-with-resources语法能更可靠地自动关闭所有实现AutoCloseable接口的资源,从根源上避免遗漏。
重写后的executeQuery方法可以改成这样:
private JSONArray executeQuery(JspWriter out, Connection connection, String query, Object[] params) throws Exception { if (connection == null) { return null; } // try-with-resources会自动关闭st和rs,无需手动处理 try (PreparedStatement st = connection.prepareStatement(query)) { int c = 1; for (Object param : params) { st.setObject(c++, param); } try (ResultSet rs = st.executeQuery()) { ResultSetMetaData meta = rs.getMetaData(); List<String> columns = getColumnNames(meta); JSONArray json = new JSONArray(); while (rs.next()) { JSONObject obj = new JSONObject(); for (String column : columns) { obj.put(column, rs.getObject(column)); } json.put(obj); } return json; } } catch (Exception e) { System.err.println("Exception occurred while executing query: " + e); try { out.println("Query error: " + e.getMessage()); } catch (IOException ioEx) { ioEx.printStackTrace(); } // 不要吞掉异常,抛出给调用方处理,方便排查问题 throw e; } finally { // 确保Connection被关闭,这里用独立的try-catch避免异常扩散 try { if (!connection.isClosed()) { connection.close(); System.out.println("Closed connection successfully: " + connection.hashCode()); } } catch (Exception ex) { System.err.println("Failed to close connection: " + ex); } } }
3. 添加连接生命周期日志追踪
为了确认是否真的有连接泄漏,你可以在代码里添加日志,追踪每个连接的创建和关闭:
- 在
getConnection方法返回连接时,打印:System.out.println("Acquired connection: " + connection.hashCode()); - 在
finally块关闭连接时,打印:System.out.println("Closed connection: " + connection.hashCode());
这样你就能在日志里看到每个连接的hashCode,对比创建和关闭的数量,如果创建数远大于关闭数,就能定位到哪里的连接没有被关闭。
4. 改用连接池管理连接
直接用DriverManager获取连接是非常低效的,而且手动管理连接很容易出现泄漏。建议改用成熟的连接池(比如HikariCP、C3P0),连接池会自动负责连接的分配、复用和回收,避免手动管理的风险。
比如用HikariCP的话,只需要配置数据源,然后从数据源获取连接,用完后调用close()(实际是把连接放回池里,不是真的关闭),代码会更简洁可靠。
总结
先从移除重复注册Driver的代码开始,然后改用try-with-resources确保资源关闭,再添加日志追踪连接生命周期,最后考虑切换到连接池,应该就能解决连接耗尽的问题。
内容的提问来源于stack exchange,提问作者user7665040

