什么是数据库连接池?

数据库连接池(Database Connection Pool)是一种用于管理和复用数据库连接的技术,旨在提高数据库操作的性能和资源利用率。

为什么需要连接池?

每次应用程序与数据库建立连接时,都需要进行以下操作:

  • 建立网络连接

  • 用户身份验证

  • 分配数据库资源

这些操作开销较大,尤其在高并发场景下,频繁地创建和关闭连接会显著影响系统性能。

连接池的工作原理

数据库连接池在应用程序启动时预先创建一定数量的数据库连接,并将这些连接保存在一个“池子”中。当应用程序需要访问数据库时:

  1. 从池中获取连接:而不是新建连接。

  2. 使用连接执行SQL操作

  3. 使用完毕后归还连接:连接不会被关闭,而是返回连接池,供后续请求复用。

CP30

添加依赖(Maven)

pom.xml 中添加 C3P0 和数据库驱动(以 MySQL 为例):

<dependencies>
    <!-- C3P0 连接池 -->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.5</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

配置 C3P0 连接池

C3P0 支持多种配置方式:代码配置、配置文件配置(c3p0-config.xml)、修改application.yml配置文件

方式一:代码方式配置

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.SQLException;

public class C3P0Example {
    // 创建数据源(连接池)
    private static ComboPooledDataSource dataSource;

    static {
        dataSource = new ComboPooledDataSource();
        try {
            // 数据库连接信息
            dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC");
            dataSource.setUser("root");
            dataSource.setPassword("123456");

            // 连接池参数配置
            dataSource.setInitialPoolSize(5);           // 初始连接数
            dataSource.setMinPoolSize(5);               // 最小空闲连接
            dataSource.setMaxPoolSize(20);              // 最大连接数
            dataSource.setMaxIdleTime(3000);            // 连接最大空闲时间(秒)
            dataSource.setAcquireIncrement(5);          // 获取连接时增加的连接数
            dataSource.setMaxStatements(50);            // 开启 PreparedStatement 缓存
            dataSource.setIdleConnectionTestPeriod(60); // 每隔60秒检查空闲连接
            dataSource.setTestConnectionOnCheckin(true); // 检查归还的连接是否有效
            dataSource.setTestConnectionOnCheckout(false); // 获取连接时不检查(影响性能)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    // 关闭连接(归还到池中)
    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close(); // 实际上是归还,不是关闭
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 测试连接
    public static void main(String[] args) {
        try (Connection conn = getConnection()) {
            System.out.println("数据库连接成功!");
            System.out.println("连接对象: " + conn);
        } catch (SQLException e) {
            System.err.println("连接失败: " + e.getMessage());
        }
    }
}

方式二:使用 c3p0-config.xml 配置文件

src/main/resources 目录下创建文件:c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

    <!-- 默认配置 -->
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb?useSSL=false&amp;serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <property name="initialPoolSize">5</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
        <property name="acquireIncrement">5</property>
        <property name="maxIdleTime">3000</property>
        <property name="maxStatements">50</property>
        <property name="idleConnectionTestPeriod">60</property>
        <property name="testConnectionOnCheckin">true</property>
        <property name="testConnectionOnCheckout">false</property>
    </default-config>

    <!-- 命名配置(可选,用于不同数据源) -->
    <named-config name="production">
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://prod-db:3306/appdb?useSSL=false&amp;serverTimezone=UTC</property>
        <property name="user">prod_user</property>
        <property name="password">prod_pass</property>
        <property name="maxPoolSize">50</property>
        <property name="acquireIncrement">10</property>
    </named-config>

</c3p0-config>
使用配置文件创建数据源
import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.SQLException;

public class C3P0WithConfigFile {
    private static ComboPooledDataSource dataSource;

    static {
        // 使用默认配置
        dataSource = new ComboPooledDataSource();

        // 或使用命名配置
        // dataSource = new ComboPooledDataSource("production");
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void main(String[] args) {
        try (Connection conn = getConnection()) {
            System.out.println("通过配置文件连接成功!");
            System.out.println("连接: " + conn);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

方法三:修改application.yml配置文件

c3p0:                                   # 定义C3P0配置
jdbcUrl: jdbc:mysql://localhost:3306/mldn # 数据库连接地址
user: root                              # 数据库用户名
password: mysqladmin                    # 数据库密码
driverClass: org.gjt.mm.mysql.Driver    # 数据库驱动程序
minPoolSize: 1                          # 最小连接数
maxPoolSize: 1                          # 最大连接数
maxIdleTime: 3000                       # 最大等待时间
initialPoolSize: 1                      # 初始化连接数

如何通过 C3P0 获取数据库连接并进行基本操作:

  • 获取连接:通过 dataSource.getConnection() 获取数据库连接。

  • 执行 SQL:使用 PreparedStatement 执行查询,并处理结果集。

  • 资源管理:通过 finally 块确保 ResultSetPreparedStatementConnection 被正确关闭(实际是归还到连接池)。

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3P0Example {

    // 创建数据源(连接池)
    private static ComboPooledDataSource dataSource;

    static {
        try {
            // 使用默认配置
            dataSource = new ComboPooledDataSource();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    // 关闭连接(归还到池中)
    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close(); // 实际上是归还,不是关闭
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 测试查询
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 获取连接
            conn = getConnection();

            // 执行查询
            String sql = "SELECT * FROM users";
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();

            // 处理结果集
            while (rs.next()) {
                System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            closeResultSet(rs);
            closePreparedStatement(pstmt);
            closeConnection(conn);
        }
    }

    // 关闭 ResultSet
    public static void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 关闭 PreparedStatement
    public static void closePreparedStatement(PreparedStatement pstmt) {
        if (pstmt != null) {
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Druid

添加依赖(Maven)

<dependencies>
    <!-- Druid 连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.20</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

配置 Druid 连接池

Druid 支持多种配置方式:代码配置、属性文件、Spring 集成。

方式一:代码方式配置

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.Connection;
import java.sql.SQLException;

public class DruidExample {
    private static DruidDataSource dataSource;

    static {
        dataSource = new DruidDataSource();
        // 基本数据库配置
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        // 连接池配置
        dataSource.setInitialSize(5);        // 初始连接数
        dataSource.setMinIdle(5);            // 最小空闲连接
        dataSource.setMaxActive(20);         // 最大连接数
        dataSource.setMaxWait(60000);        // 获取连接最大等待时间(毫秒)
        dataSource.setTimeBetweenEvictionRunsMillis(60000); // 检测间隔
        dataSource.setMinEvictableIdleTimeMillis(300000);   // 最小空闲时间
        dataSource.setValidationQuery("SELECT 1");          // 验证 SQL
        dataSource.setTestWhileIdle(true);                  // 空闲时检测
        dataSource.setTestOnBorrow(false);                  // 获取时检测
        dataSource.setTestOnReturn(false);                  // 归还时检测

        // 打开 PSCache(PreparedStatement 缓存)
        dataSource.setPoolPreparedStatements(true);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);

        // 配置监控统计拦截器
        dataSource.setFilters("stat,wall"); // stat:监控, wall:防御SQL注入
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close(); // 归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try (Connection conn = getConnection()) {
            System.out.println("Druid 连接成功!");
            System.out.println("连接对象: " + conn);
        } catch (SQLException e) {
            System.err.println("连接失败: " + e.getMessage());
        }
    }
}

方式二:使用 druid.properties 配置文件

src/main/resources/druid.properties 中配置:

# 数据库连接
druid.driverClassName=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
druid.username=root
druid.password=123456

# 连接池配置
druid.initialSize=5
druid.minIdle=5
druid.maxActive=20
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 1
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=20

# 监控配置
druid.filters=stat,wall

加载配置文件的代码:

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;

public class DruidWithProperties {
    private static DataSource dataSource;

    static {
        Properties props = new Properties();
        try {
            props.load(DruidWithProperties.class.getClassLoader()
                    .getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws Exception {
        return dataSource.getConnection();
    }

    public static void main(String[] args) {
        try (Connection conn = getConnection()) {
            System.out.println("通过配置文件连接成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

方法三:与 Spring 集成(Spring XML 配置)

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/testdb?useSSL=false&amp;serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>

    <property name="initialSize" value="5"/>
    <property name="minIdle" value="5"/>
    <property name="maxActive" value="20"/>
    <property name="maxWait" value="60000"/>
    <property name="validationQuery" value="SELECT 1"/>
    <property name="testWhileIdle" value="true"/>

    <!-- 配置监控 -->
    <property name="filters" value="stat,wall"/>
</bean>

启用 Druid 内置监控页面(Web 项目)

如果你使用的是 Web 项目(如 Spring Boot),可以开启 Druid 的监控页面。

1. 添加 Servlet 和 Filter(web.xml)

<!-- Druid 监控页面 -->
<servlet>
    <servlet-name>DruidStatView</servlet-name>
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
    <init-param>
        <param-name>allow</param-name>
        <param-value>127.0.0.1</param-value>
    </init-param>
    <init-param>
        <param-name>deny</param-name>
        <param-value></param-value>
    </init-param>
    <init-param>
        <param-name>loginUsername</param-name>
        <param-value>admin</param-value>
    </init-param>
    <init-param>
        <param-name>loginPassword</param-name>
        <param-value>123456</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>DruidStatView</servlet-name>
    <url-pattern>/druid/*</url-pattern>
</servlet-mapping>

<!-- Web 监控过滤器 -->
<filter>
    <filter-name>DruidWebStatFilter</filter-name>
    <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
    <init-param>
        <param-name>exclusions</param-name>
        <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>DruidWebStatFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. 访问监控页面

http://localhost:8080/your-app/druid