一、Bug 场景
在一个企业级的 Java Web 应用程序中,使用数据库连接池来管理与 MySQL 数据库的连接。应用程序主要负责处理用户的订单操作,包括订单的创建、查询和更新。在高并发的业务场景下,随着时间的推移,系统性能逐渐下降,最终可能导致数据库连接耗尽,新的请求无法获取连接而失败。
二、代码示例
假设使用 HikariCP 连接池,以下是简化的代码片段:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCConnectionPoolBug {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/yourdatabase");
config.setUsername("root");
config.setPassword("password");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
String sql = "SELECT * FROM orders WHERE user_id =?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("order_name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 错误示范:没有正确关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 遗漏关闭Connection
}
}).start();
}
}
}
三、问题描述
- 资源未完全关闭:在上述代码的
finally块中,虽然关闭了ResultSet和PreparedStatement,但遗漏了关闭Connection。这就导致连接对象没有被正确地返回到连接池中,随着每次请求获取新连接而不释放,连接池中的可用连接逐渐减少。 - 高并发影响:在高并发环境下,这种资源泄漏问题会被迅速放大。大量线程不断获取连接却不归还,很快连接池中的所有连接都会被占用且无法回收,后续的请求就会因为无法获取连接而抛出
SQLException,如Timeout waiting for connection from pool,严重影响系统的正常运行。
四、解决方案
- 正确关闭连接:在
finally块中确保关闭Connection。
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCConnectionPoolBugFixed {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/yourdatabase");
config.setUsername("root");
config.setPassword("password");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
String sql = "SELECT * FROM orders WHERE user_id =?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("order_name"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
- 使用
try - with - resources语句:从 Java 7 开始,可以使用try - with - resources语句来自动关闭实现了AutoCloseable接口的资源,使代码更简洁且不易出错。
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCConnectionPoolBugFixedWithTryWithResources {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/yourdatabase");
config.setUsername("root");
config.setPassword("password");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
String sql = "SELECT * FROM orders WHERE user_id =?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 1);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("order_name"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}).start();
}
}
}