使用Java连接池Druid连接数据库

如果您的应用侧主要使用Java语言,且数据库连接创建频繁(例如短连接场景)或连接数量较大(大于MySQL数据库的连接数限制),您可以使用Java连接池Druid连接数据库,降低连接建立频率以减少数据库主线程的开销。

前提条件

  • 应用服务器已安装Java环境且JDK版本在1.8及以上。

  • 已将服务器IP地址添加至RDS实例白名单中,详情请参见设置IP白名单

    说明

    若您的应用程序部署在阿里云ECS服务器上,且ECS与RDS已实现内网互通(ECS与RDS实例的地域、VPC均相同),则无需设置IP白名单。

准备工作

本文以Maven项目为例,介绍使用Druid连接池连接数据库前的准备工作。

说明

Maven是一个Java项目管理工具,其为开发者提供了一套标准化的Java项目构建流程与依赖管理机制。使用Maven创建的Java项目带有标准化的目录结构,其中pom.xml为项目描述文件,src/main/java目录存放项目的Java源代码,src/main/resources目录存放项目资源文件。

  • 安装Druid:您需要在pom.xml文件的dependencies中添加以下依赖项安装Druid,Druid连接池版本建议选择1.2.13及以上

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.19</version>
    </dependency>
  • 通过druid-spring-boot-starter使用连接池Druid时,为了简化依赖关系,便于后续Druid版本的更新维护,您需要在pom.xml文件的dependencies中先排除其依赖的Druid组件,再显式依赖Druid组件,如下所示。

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.19</version>
        <exclusions>
            <exclusion>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.19</version>
    </dependency>

使用Druid连接数据库

  1. 配置连接池参数:您需要在项目的src/main/resources目录中新建druid.properties文件,并在文件中添加以下内容为连接池配置相关参数,参数说明详情请参见Druid连接池常见参数配置

    说明

    RDS MySQL数据库连接地址和端口的获取方法,请参见查看和管理实例连接地址和端口

    # 驱动类名,无需替换
    driverClassName=com.mysql.jdbc.Driver
    
    # url、username、password、database需要替换为业务实际的内容
    url=jdbc:mysql://rm-bp**************.mysql.rds.aliyuncs.com:3306/database
    username=****
    password=****
    
    # 初始化时创建的连接数
    initialSize=20
    # 最小空闲连接数
    minIdle=20
    # 连接池最大连接数
    maxActive=100
    # 一条物理连接的存活时间
    phyTimeoutMillis=3600000
    # 获取连接最大等待时间,如果连接池中没有可用连接且已达到最大连接数,则应用获取连接的请求将被阻塞,最多等待maxWait(毫秒),建议保持不变
    maxWait=5000
    # 连接超时时间,表示数据库驱动和数据库服务器之间建立TCP连接的超时时间,单位毫秒
    connectTimeout=20000
    # Socket超时时间,表示通过TCP连接发送数据(执行的sql)后,等待响应的超时时间,单位毫秒
    socketTimeout=60000
    
    # 连接验证配置项,建议保持不变
    testWhileIdle=true
    testOnBorrow=false
    testOnReturn=false
    
    # PreparedStatement缓存配置项,此处配置为true表示打开缓存,建议保持不变
    poolPreparedStatements=true
    maxPoolPreparedStatementPerConnectionSize=100
  2. 使用连接池访问数据库:您需要在src/main/java目录存放的源代码文件中导入相关依赖,构建DruidPoolDemo类,通过连接池Druid获取JDBC的连接信息并访问MySQL数据库。

    // 导入相关依赖
    package com.aliyun.rdsfinops.collector.impl;
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    import javax.sql.DataSource;
    import java.io.InputStream;
    import java.sql.*;
    import java.util.Properties;
    
    public class DruidPoolDemo {
        private static DataSource dataSource = null;
        String tableName = "sql_table_test";
        // 加载参数
        static {
            Properties properties = new Properties();
            // 获取连接池配置
            InputStream inputStream = DruidPoolDemo.class.getClassLoader().getResourceAsStream("druid.properties");
            try {
                properties.load(inputStream);
                // 初始化连接池
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /* 
          后续数据库操作(见后文)
          创建表:  public void createTable()
          插入数据:public void insertData()
          查询数据:public void selectData()
          删除数据:public void deleteData()
        */
    }

连接池常见参数配置

在使用Druid连接数据库时,建议您根据下述内容为连接池设置合适的参数,使数据库的运行更稳定高效。

重要

为了最大程度地避免潜在的风险和不确定性,在将新的参数值用于生产环境前,建议您至少进行一轮完整的功能测试和性能测试,以确保系统稳定性和可靠性。

推荐配置的参数

推荐您在使用Druid连接池时设置以下参数,降低数据库运行风险。

参数名

含义

默认值

推荐值

说明

initialSize

连接池初始化时创建的连接数。

0

20~80

  • 设置合理的initialSize,应用启动后可以立即处理并发需求。

  • 建议设置为平均并发连接数的60%至80%。

minIdle

最小空闲连接数。

0

20~80

  • 适当配置minIdle可以预留一定数量的连接,快速处理突发的数据库请求。

  • 建议参考initialSize的参数设置或设置为JVM在无压力时保留的连接数。

  • 活跃连接数与此参数无关,其受maxActive参数控制。

maxActive

连接池最大连接数。

8

100

  • 该参数值需根据应用程序的实际需求及数据库的处理能力决定。

  • 建议设置为数据库能同时处理的最大连接数,或略大于预计峰值并发量,预留一定的连接应对突发流量。

phyTimeoutMillis

一条物理连接的存活时间(毫秒)。

-1

3600000~28800000

  • 默认值-1表示连接永久存在。

  • 参数值需要根据实际情况决定,设置该值是为了防止空闲连接长时间存在,浪费资源。

maxWait

获取连接时的最大等待时间(毫秒)。

-1

5000

  • 如果连接池中没有可用连接且已经达到最大连接数,则应用获取连接的请求将被阻塞,最多等待该参数设定的时间。

  • 默认值-1表示一直等待,0表示不等待(不建议设置为-10)。

  • 正常情况下,获取数据库连接的等待时间为10至30毫秒,建议按实际需求或推荐值设置参数。

connectTimeout

连接超时的时间(毫秒)。

10000

3000

  • 数据库驱动与数据库服务器建立TCP连接的超时时间。

  • 建议设置为1-10秒之间,取决于网络质量高低,以及应用端与服务端的距离。

socketTimeout

socket的超时时间(毫秒)。

10000

10000~60000

  • 通过TCP连接发送数据(待执行的SQL命令)后,等待响应的超时时间。

  • 该参数值不宜过短,针对SQL执行时间明显过长而导致超时的问题,应该首先检查SQL或者数据库本身,而不是优先调整该参数。

testWhileIdle

是否开启空闲连接的检测。

false

true

建议设置为true,连接池会定期检查空闲连接的状态。

可选择配置的参数

使用Druid连接池时,您可以选择性地配置以下参数,提升数据库性能。

参数名

含义

默认值

推荐值

说明

poolPreparedStatements

是否缓存PreparedStatement对象。

true

true

  • 开启后,在重复执行相同SQL语句时,可以减少编译SQL语句的次数,提高性能。

  • 开启后,可以有效防止SQL注入攻击,增强安全性。

  • 如果业务SQL变化较大或业务有很多大字段,设置该参数会导致大量的JVM内存消耗,也会导致较多的数据库服务端内存消耗。

maxPoolPreparedStatementPerConnectionSize

每个连接缓存PreparedStatement对象的最大数量。

10

100

  • 如果开启了缓存PreparedStatement对象,连接池会限制每个连接缓存的最大数量。

  • 对于执行大量重复SQL语句的应用,可以将该参数值调高,但设置过高会导致内存浪费。

保持默认配置的参数

对于以下常见的连接池参数,您可以选择直接使用默认配置或根据自身需求调整参数。

参数名

含义

默认值

推荐值

说明

failFast

用于控制在获取连接出现错误时的行为。

false

false

  • 一般情况下,保持默认设置不变。

  • 在错误敏感、获取连接频繁失败的场景下,可以将该参数设置为true

    • 快速响应错误:如果连接池已满或连接超时等,连接获取操作会在出现错误时立即失败,不会进行重试或等待。

    • 避免长时间等待:在高并发的场景下,可以与maxWaitThreadCount参数一起使用,避免长时间等待连接获取。

timeBetweenEvictionRunsMillis

检测空闲连接的时间间隔(毫秒)。

60000

60000

  • 连接池会定期(按此参数时间间隔)检查连接,判断空闲时间是否超过minEvictableIdleTimeMillis参数设定的最小空闲时间。

  • 建议保持默认参数值不变。如果要修改此值,应当根据具体情况进行调整,以达到最佳的连接池利用率和性能表现。

后续数据库操作

您可以在DruidPoolDemo类中添加自定义函数,满足对数据库操作的需求,本文以创建数据表、插入数据、查询数据、删除数据为例。

创建表

String tableName = "sql_table_test";
public void createTable() throws SQLException {
    // 创建表
    try (Connection connection = dataSource.getConnection()) {
        try (Statement statement = connection.createStatement()) {
            String sql = "create table if not exists " + tableName + "(id VARCHAR(255), name VARCHAR(255), PRIMARY KEY (id))";
            int ret = statement.executeUpdate(sql);
            System.out.println(ret);
        }
    }
}

插入数据

String tableName = "sql_table_test";
public void insertData(){
    // 插入数据
    try (Connection connection = dataSource.getConnection()) {
        String sql = "insert into " + tableName + "(id,name) values(?,?)";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setString(1, "aa");
            ps.setString(2, "bb");
            int ret = ps.executeUpdate();
            System.out.println(ret);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

查询数据

String tableName = "sql_table_test";
public void selectData() throws SQLException {
    // 查询数据
    try (Connection connection = dataSource.getConnection()) {
        String sql = "select * from " + tableName + " where id=?";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setString(1, "aa");
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                String id = rs.getString(1);
                String name = rs.getString(2);
                System.out.println("id=" + id);
                System.out.println("name=" + name);
            }
        }
    }
}

删除数据

String tableName = "sql_table_test";
public void deleteData(){
    // 删除数据
    try (Connection connection = dataSource.getConnection()) {
        String sql = "delete from " + tableName + " where id=?";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setString(1, "aa");
            ps.executeUpdate();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

相关文档