并发执行多个数据库查询:解决单连接阻塞问题

聖光之護

聖光之護

2026-08-03

181人浏览

原创

并发执行多个数据库查询:解决单连接阻塞问题

本文介绍如何通过引入 hikaricp 连接池替代全局单数据库连接,实现多个 ui 后台任务真正并行执行数据库操作,避免长耗时查询阻塞其他快速查询。

本文介绍如何通过引入 hikaricp 连接池替代全局单数据库连接,实现多个 ui 后台任务真正并行执行数据库操作,避免长耗时查询阻塞其他快速查询。

在 JavaFX 应用中,使用 Task 在后台线程执行数据库操作是防止 UI 冻结的标准做法。但若所有数据库调用共享同一个 Connection 实例(如原代码中 DataHandler.conn 是静态单例),即使任务在不同线程中启动,它们仍会串行争用同一物理连接——因为 JDBC Connection 对象不是线程安全的,且 MySQL 单连接在同一时刻只能处理一个活跃请求。这就是为何 CALL 1(7 秒)未完成时,CALL 2(550ms)虽已进入 RUNNING 状态,却实际处于连接等待中,导致“假并行”。

根本解法是:让每个数据库操作独占一个连接,并由连接池统一管理资源。HikariCP 是高性能、轻量级的 JDBC 连接池,能高效复用连接、控制并发数、自动回收失效连接。

✅ 正确实践:基于 HikariCP 的并发查询

首先,在项目中添加 HikariCP 依赖(Maven):

<dependency><groupid>com.zaxxer</groupid><artifactid>HikariCP</artifactid><version>5.0.1</version></dependency>

然后重构 DataHandler,移除静态 Connection,改用 HikariDataSource 管理连接池:

笔灵AI答辩PPT
笔灵AI答辩PPT

答辩PPT、千字自述稿一键生成,预测导师提问,答辩一次过!

下载
public class DataHandler {
    private static volatile DataHandler instance;
    private static HikariDataSource dataSource;

    private DataHandler() {
        initDataSource();
    }

    private void initDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/database?serverTimezone=UTC");
        config.setUsername("user");
        config.setPassword("password");
        config.setMaximumPoolSize(20); // 根据并发预期调整,通常 10–30 足够
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30_000);
        config.setIdleTimeout(600_000);
        config.setMaxLifetime(1800_000);

        // 关键优化:启用预编译语句缓存(提升重复 SQL 性能)
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("useServerPrepStmts", "true");

        dataSource = new HikariDataSource(config);
    }

    public static DataHandler getInstance() {
        if (instance == null) {
            synchronized (DataHandler.class) {
                if (instance == null) {
                    instance = new DataHandler();
                }
            }
        }
        return instance;
    }

    public Connection getConnection() throws SQLException {
        return dataSource.getConnection(); // 每次调用获取新连接(来自池)
    }

    // 数据库方法需显式获取并释放连接
    public ObservableList<produitvendu> getProduitsVendusParPeriode(
            LocalDate dateDebut, LocalDate dateFin,
            boolean isEspece, boolean isDebit, String nomSearch) {

        String sql = buildQuerySql(isEspece, isDebit);
        try (Connection conn = getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {

            stmt.setString(1, "%" + nomSearch + "%");
            stmt.setString(2, dateDebut.toString());
            stmt.setString(3, dateFin.toString());

            try (ResultSet rs = stmt.executeQuery()) {
                ObservableList<produitvendu> result = FXCollections.observableArrayList();
                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()
                    ));
                }
                return result;
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("Database query failed", e);
        }
    }

    private String buildQuerySql(boolean isEspece, boolean isDebit) {
        // ... 构建动态 SQL(同原逻辑,略)
        return sql;
    }
}</produitvendu></produitvendu>

? 关键点说明

  • try (Connection conn = getConnection()) 确保连接在作用域结束时自动关闭(归还至池),无需手动 close();
  • 每个 Task 调用 getProduitsVendusParPeriode(...) 时,都会从池中获取独立连接,彼此互不阻塞;
  • HikariDataSource 是线程安全的,可被任意数量线程并发调用 getConnection()。

✅ UI 层调用保持简洁(无需修改 Task 结构)

你的 loadHistoriqueVentesData() 方法完全无需改动——只需确保 db 实例是 DataHandler.getInstance() 返回的单例,其内部已使用连接池:

private void loadHistoriqueVentesData() {
    // ... 参数校验(不变)

    loadingHbox.setVisible(true);

    Task<void> task = new Task() {
        @Override
        protected Void call() {
            if (!searchString.isBlank()) {
                // ✅ 此处调用已自动使用独立连接
                ObservableList<produitvendu> data = 
                    db.getProduitsVendusParPeriode(dateDebut, dateFin, 
                                                  MHEspeceCheck.isSelected(), 
                                                  MHDebitCheck.isSelected(), 
                                                  searchString);
                Platform.runLater(() -> {
                    MHHistoriqueTable.setItems(data);
                    MHHNbreResultatsText.setText(data.size() + " vente(s)");
                });
            }
            return null;
        }
    };

    task.stateProperty().addListener((obs, old, newState) -> {
        if (newState == Worker.State.SUCCEEDED) {
            loadingHbox.setVisible(false);
        }
    });

    exec.execute(task); // 使用你原有的 Executor(推荐为 ForkJoinPool.commonPool() 或自定义线程池)
}</produitvendu></void>

⚠️ 注意事项与最佳实践

  • 不要缓存 Connection:永远不要将 Connection 存为成员变量或静态字段;每次操作都应 getConnection() → 使用 → 自动关闭。
  • 线程池配置匹配负载:Executor 的线程数建议 ≤ 连接池最大数(如 maximumPoolSize=20,则 exec 不宜超过 20 线程),避免连接耗尽。
  • SQL 优化不可替代连接池:连接池解决并发瓶颈,但慢查询本身仍需索引、分页、执行计划分析等优化。
  • 异常处理要完整:try-with-resources 已覆盖连接/语句/结果集的关闭,但业务异常(如空结果)需在 UI 层合理反馈。
  • 连接泄漏检测:HikariCP 提供 leakDetectionThreshold(毫秒),启用后可记录未关闭连接的堆栈,开发期强烈建议开启。

通过以上改造,CALL 1 和 CALL 2 将真正并行执行:一个走连接 A 执行 7 秒查询,另一个立即获得连接 B 执行 550ms 查询,UI 响应速度与任务实际耗时对齐,彻底告别“伪异步”。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

3745

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

2776

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

2799

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

636

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

601

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

705

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1289

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

18844

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

646

8

热门下载

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

精品课程

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