
本文详解如何通过引入 hikaricp 连接池替代单连接模式,使 javafx 应用中多个数据库 task 真正并行执行,彻底解决“后发起的查询需等待前一个完成”的阻塞问题。
本文详解如何通过引入 hikaricp 连接池替代单连接模式,使 javafx 应用中多个数据库 task 真正并行执行,彻底解决“后发起的查询需等待前一个完成”的阻塞问题。
在 JavaFX 应用中使用 Task 异步执行数据库操作时,若 UI 未冻结但多个查询仍串行执行(如 CALL 2 必须等待 CALL 1 完成),根本原因通常并非线程调度问题,而是数据库连接层面的资源瓶颈——尤其是当 DAO 层长期复用单一 Connection 实例时,所有数据库操作被迫排队等待同一连接,导致“看似多线程,实则单通道”。
您原始代码中的 DataHandler 使用静态单例 Connection conn,这构成了典型的全局连接瓶颈:无论启动多少个 Task,它们最终都争抢同一个数据库连接,自然无法并发。即使 Executor 分配了多个线程,JDBC 操作本身被阻塞在连接获取阶段。
✅ 正确解法:引入高性能连接池(如 HikariCP),让每个数据库操作独占一个连接,真正实现 I/O 并行。
✅ 关键改造步骤
1. 添加依赖(Maven)
<dependency><groupid>com.zaxxer</groupid><artifactid>HikariCP</artifactid><version>5.0.1</version></dependency>
2. 替换单连接为连接池
原单例连接:
private static Connection conn = null; // ❌ 全局共享,串行根源
改为 HikariCP 数据源:
private static HikariDataSource dataSource = null; // ✅ 连接池管理器
private void dbConnection(String ipPort) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://" + ipPort + "/database?serverTimezone=UTC");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(30); // 根据并发需求调整
config.setAutoCommit(true);
config.setConnectionTimeout(30000); // 30秒超时
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
// 可选:启用预编译语句缓存(提升重复SQL性能)
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
dataSource = new HikariDataSource(config);
}
3. 每次查询按需获取连接(自动释放)
修改 getProduitsVendusParPeriode(...) 方法,务必使用 try-with-resources:
public ObservableList<produitvendu> getProduitsVendusParPeriode(
LocalDate dateDebut, LocalDate dateFin,
boolean isEspece, boolean isDebit, String nomSearch) {
ObservableList<produitvendu> result = FXCollections.observableArrayList();
// ✅ 每次调用获取独立连接,用完自动归还池
try (Connection conn = DataHandler.getInstance().getConnection();
PreparedStatement prepare = conn.prepareStatement(sql)) {
prepare.setString(1, "%" + nomSearch + "%");
prepare.setString(2, dateDebut.toString());
prepare.setString(3, dateFin.toString());
try (ResultSet rs = prepare.executeQuery()) {
while (rs.next()) {
result.add(new ProduitVendu(
rs.getInt("id_produit_vendu"),
rs.getInt("numero_bon"),
rs.getInt("reference"),
rs.getString("designation"),
rs.getInt("quantite"),
rs.getDouble("prix_vendu"),
rs.getDouble("benefice"),
rs.getDate("date_vente").toLocalDate(),
rs.getTime("heure_vente").toLocalTime()
));
}
}
} catch (SQLException ex) {
ex.printStackTrace();
throw new RuntimeException("Database query failed", ex);
}
return result;
}</produitvendu></produitvendu>
⚠️ 注意事项:
- 禁止缓存或复用 Connection/PreparedStatement/ResultSet 实例——必须在方法内创建、使用、关闭;
- 所有资源(Connection、Statement、ResultSet)均需纳入 try-with-resources 或显式 close();
- HikariDataSource.getConnection() 是轻量级操作,返回的是池中空闲连接,非真实物理连接重建;
- maximumPoolSize 应略大于应用最大并发数据库请求数(如 UI 同时触发 5 个查询 → 设为 8~10 更稳妥)。
4. 验证并发效果
改造后,再次执行 CALL 1(7s)与 CALL 2(550ms):
- 两者 Task 将同时进入 RUNNING 状态;
- CALL 2 在 ~550ms 后即触发 SUCCEEDED,UI 可立即更新对应区域;
- CALL 1 仍在后台执行,互不干扰。
✅ 此时 exec.execute(task) 的线程并行性才真正落地——数据库层不再成为瓶颈。
总结
- ❌ 单连接 = 伪异步,本质串行;
- ✅ 连接池 + 按需连接 + 自动回收 = 真并发;
- HikariCP 不仅解决并发问题,还带来连接复用、预编译缓存、连接健康检测等企业级能力;
- 所有数据库访问方法必须遵循“获取→使用→释放”闭环,这是发挥连接池价值的前提。
完成上述改造后,您的 JavaFX 应用将具备高响应性与可扩展的数据库访问能力,从容应对复杂业务场景下的多路并发查询需求。











