杰瑞科技汇

Java MySQL连接池如何高效管理与优化?

为什么需要数据库连接池?

在理解如何使用之前,我们先明白为什么要使用它。

Java MySQL连接池如何高效管理与优化?-图1
(图片来源网络,侵删)
  1. 性能提升:数据库连接的创建和销毁是一个非常耗时的过程(涉及网络通信、身份验证、资源分配等),连接池通过预先创建并维护一组可复用的连接,避免了每次请求都创建新连接的开销,从而显著提高响应速度。
  2. 资源控制:如果应用不使用连接池,并发量高时可能会无限制地创建数据库连接,导致数据库服务器资源耗尽(连接数耗尽),甚至整个应用崩溃,连接池可以设置最大连接数,有效管理数据库资源。
  3. 稳定性增强:连接池提供了连接的有效性检查、自动回收失效连接等功能,使得数据库连接管理更加健壮。

一个形象的比喻

没有连接池,就像每次去餐厅吃饭都要先去农场买地、种麦子、磨面、烤面包,效率极低。 有了连接池,就像餐厅有一个面包房(连接池),预先烤好了一批面包(数据库连接),客人来了直接拿一个用,用完放回,大大提高了服务效率。


主流的 Java 连接池技术

市面上有许多优秀的 Java 连接池实现,其中最主流的有以下几个:

连接池 优点 缺点 官方推荐
HikariCP 性能极高,代码简洁,稳定可靠,是目前公认的性能最好的连接池 功能相对其他几款较少。 Spring Boot 2.x 及更高版本的默认首选
Druid 功能强大,性能优秀,内置了强大的监控功能(SQL监控、Web Stat Filter等),在生产环境中非常受欢迎。 配置项相对较多,代码比 HikariCP 复杂。 阿里巴巴开源,国内广泛使用。
C3P0 老牌连接池,功能稳定,使用广泛。 性能不如 HikariCP 和 Druid,且有一些已知的 Bug。 早期项目常见,新项目不推荐首选。
DBCP Apache 旗下产品,也是一款老牌连接池。 性能一般,且在高并发下稳定性不如 HikariCP。 Spring Framework 曾作为默认,现已不再推荐。

对于新项目,HikariCP 是首选,其次是 Druid(如果需要强大的监控功能)。

Java MySQL连接池如何高效管理与优化?-图2
(图片来源网络,侵删)

实践演示:使用 HikariCP 连接 MySQL

我们将以最流行的 HikariCP 为例,演示如何在 Java 项目中配置和使用它。

准备工作

  1. 创建 MySQL 数据库和表

    CREATE DATABASE mytestdb;
    USE mytestdb;
    CREATE TABLE users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(50) NOT NULL,
        password VARCHAR(50) NOT NULL,
        email VARCHAR(100)
    );
    INSERT INTO users (username, password, email) VALUES ('zhangsan', '123456', 'zhangsan@example.com');
    INSERT INTO users (username, password, email) VALUES ('lisi', '654321', 'lisi@example.com');
  2. 创建 Maven 项目pom.xml 文件中添加必要的依赖:

    • mysql-connector-java:MySQL 驱动。
    • com.zaxxer:HikariCP:HikariCP 连接池本身。
    • slf4j-apilogback-classic:日志框架,HikariCP 依赖 SLF4J 输出日志。
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.example</groupId>
        <artifactId>java-mysql-pool-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        <dependencies>
            <!-- MySQL 8.0+ 驱动 -->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>8.0.33</version>
            </dependency>
            <!-- HikariCP 连接池 -->
            <dependency>
                <groupId>com.zaxxer</groupId>
                <artifactId>HikariCP</artifactId>
                <version>5.0.1</version>
            </dependency>
            <!-- 日志框架 SLF4J + Logback -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>2.0.7</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.4.8</version>
            </dependency>
        </dependencies>
    </project>

代码实现

硬编码配置(不推荐,仅用于演示)

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 HikariCPHardcodedDemo {
    public static void main(String[] args) {
        // 1. 创建 HikariConfig 配置对象
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mytestdb?useSSL=false&serverTimezone=UTC");
        config.setUsername("root"); // 你的 MySQL 用户名
        config.setPassword("your_password"); // 你的 MySQL 密码
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        // 2. 可选的优化配置
        config.setMaximumPoolSize(10); // 最大连接数
        config.setMinimumIdle(5);      // 最小空闲连接数
        config.setConnectionTimeout(30000); // 连接超时时间 (毫秒)
        config.setIdleTimeout(600000); // 空闲连接存活时间 (毫秒)
        config.setMaxLifetime(1800000); // 连接最大存活时间 (毫秒)
        // 3. 创建 HikariDataSource 数据源对象
        HikariDataSource dataSource = new HikariDataSource(config);
        // 4. 使用连接池获取连接并执行操作
        try (Connection connection = dataSource.getConnection()) {
            System.out.println("成功获取连接: " + connection);
            // 查询示例
            String sql = "SELECT id, username, email FROM users WHERE id = ?";
            try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
                pstmt.setInt(1, 1);
                ResultSet rs = pstmt.executeQuery();
                if (rs.next()) {
                    System.out.println("查询到用户: ID=" + rs.getInt("id") 
                                       + ", Username=" + rs.getString("username")
                                       + ", Email=" + rs.getString("email"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 5. 关闭数据源(在应用关闭时执行)
            if (dataSource != null && !dataSource.isClosed()) {
                dataSource.close();
                System.out.println("HikariCP 连接池已关闭。");
            }
        }
    }
}

使用 DataSource 接口和配置文件(推荐最佳实践)

将数据库配置信息与代码分离,是更专业、更灵活的做法。

Java MySQL连接池如何高效管理与优化?-图3
(图片来源网络,侵删)

创建 db.properties 配置文件src/main/resources 目录下创建 db.properties 文件:

# db.properties
# MySQL 8.0+ 驱动类名
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
# JDBC URL
jdbc.url=jdbc:mysql://localhost:3306/mytestdb?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
# 用户名和密码
jdbc.username=root
jdbc.password=your_password
# HikariCP 连接池配置
pool.maximumPoolSize=10
pool.minimumIdle=5
pool.connectionTimeout=30000
pool.idleTimeout=600000
pool.maxLifetime=1800000

创建工具类 DataSourceUtil.java 这个类负责加载配置文件并创建数据源实例。

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DataSourceUtil {
    private static HikariDataSource dataSource;
    static {
        try (InputStream input = DataSourceUtil.class.getClassLoader().getResourceAsStream("db.properties")) {
            Properties props = new Properties();
            if (input == null) {
                throw new RuntimeException("无法找到 db.properties 文件");
            }
            props.load(input);
            // 创建 HikariConfig
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl(props.getProperty("jdbc.url"));
            config.setUsername(props.getProperty("jdbc.username"));
            config.setPassword(props.getProperty("jdbc.password"));
            config.setDriverClassName(props.getProperty("jdbc.driverClassName"));
            // 从配置文件中读取 HikariCP 特定配置
            config.setMaximumPoolSize(Integer.parseInt(props.getProperty("pool.maximumPoolSize")));
            config.setMinimumIdle(Integer.parseInt(props.getProperty("pool.minimumIdle")));
            config.setConnectionTimeout(Long.parseLong(props.getProperty("pool.connectionTimeout")));
            config.setIdleTimeout(Long.parseLong(props.getProperty("pool.idleTimeout")));
            config.setMaxLifetime(Long.parseLong(props.getProperty("pool.maxLifetime")));
            // 创建数据源
            dataSource = new HikariDataSource(config);
        } catch (IOException e) {
            throw new RuntimeException("加载数据库配置文件失败", e);
        }
    }
    // 获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    // 关闭数据源 (通常在应用关闭时调用)
    public static void closeDataSource() {
        if (dataSource != null && !dataSource.isClosed()) {
            dataSource.close();
        }
    }
}

在业务代码中使用工具类 你的业务代码可以非常简洁,完全不需要关心连接池的细节。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserService {
    public void findUserById(int id) {
        // 使用 try-with-resources 自动关闭连接
        try (Connection connection = DataSourceUtil.getConnection();
             PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?")) {
            pstmt.setInt(1, id);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                System.out.println("用户信息: ID=" + rs.getInt("id") 
                                   + ", 用户名=" + rs.getString("username")
                                   + ", 邮箱=" + rs.getString("email"));
            } else {
                System.out.println("未找到 ID 为 " + id + " 的用户。");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        UserService userService = new UserService();
        userService.findUserById(2); // 查询 lisi
    }
}

Druid 连接池简介

如果你选择 Druid,其使用方式与 HikariCP 非常相似,只是在配置和功能上有所不同。

添加 Druid 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.18</version>
</dependency>

使用 Druid 创建数据源

import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class DruidDemo {
    public static void main(String[] args) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/mytestdb?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("your_password");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        // Druid 特有的配置,如监控
        dataSource.setFilters("stat"); // 开启监控统计功能
        try (Connection conn = dataSource.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT 'Hello Druid'")) {
            if (rs.next()) {
                System.out.println(rs.getString(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            dataSource.close();
        }
    }
}

Druid 的最大优势在于其强大的监控页面,你只需配置一个 Servlet,就可以在浏览器中访问一个页面,实时查看 SQL 执行情况、活跃连接数、慢查询等信息,对于线上问题排查非常有帮助。


总结与最佳实践

  1. 选择连接池:优先选择 HikariCP,对监控有强需求时选择 Druid
  2. 配置分离永远不要在代码中硬编码数据库连接信息,使用 .properties.yml 文件进行管理。
  3. 生命周期管理DataSource(数据源)的创建和销毁成本很高,应该在应用启动时创建一次,在应用关闭时销毁一次,不要为每个请求都创建一个新的 DataSource
  4. 连接的获取与释放:遵循 “用即取,用即还” 的原则,使用 try-with-resources 语句块可以确保 Connection, Statement, ResultSet 等资源在使用后被自动关闭,并将其归还给连接池,这是防止连接泄漏的关键。
  5. 理解配置参数:花时间理解 maximumPoolSize, connectionTimeout 等核心参数的含义,并根据你的应用负载和数据库服务器的性能进行合理调优。
分享:
扫描分享到社交APP
上一篇
下一篇