从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

    • JavaEE 概念

    • Ant

    • Maven

    • 日志

    • Junit

    • JDBC

      • 什么是 JDBC
      • JDBC 连接、查询和更新
      • JDBC 连接字符串
      • JDBC 常用类介绍
      • PreparedStatement 是如何防止SQL注入的
      • ResultSet 结果封装为对象
      • JDBC 工具类
      • JDBC 更新
      • JDBC 事务和批量
      • JDBC 连接池
        • 使用数据库连接池之前
        • 数据库连接池的概念
        • JDBC 中的连接池
        • C3P0
        • Druid
        • 总结
      • JDBCTemplate
    • XML 和 JSON

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java
  • JavaEE
  • JDBC
2023-03-24
目录

JDBC 连接池

# 30.JDBC 连接池

概述:在操作系统中,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。

类似的,在执行 JDBC 的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁 JDBC 连接的开销就太大了。为了避免频繁地创建和销毁 JDBC 连接,我们可以通过连接池复用已经创建好的连接。

‍

# 使用数据库连接池之前

假设我们不使用数据库连接池,应用程序和数据库建立连接的过程是这样的:

  1. 首先通过 TCP 协议的三次握手和数据库服务器建立连接,然后发送数据库用户账号密码,等待数据库验证用户身份。
  2. 完成用户身份验证后,系统才可以提交 SQL 语句到数据库执行。
  3. 完成一次 SQL 查询后,我们要把连接关闭,关闭连接就需要和数据库通信,告诉它我们要断开连接了,然后再 TCP 四次挥手最后完成关闭。

这个过程中每一次发起 SQL 查询所经历的 TCP 建立连接,数据库验证用户身份,数据库用户登出,TCP 断开连接消耗的等待时间都是可以避免的,这明显是一种浪费。

打个比方,你去网吧去玩游戏,每次去到呢先插网线,然后开机登录游戏,玩了一会儿要去上厕所,你就退出游戏,然后关机拔网线。去完厕所回来就又重新插网线开机登游戏。 ‍

# 数据库连接池的概念

每次 SQL 查询都创建链接,查询完后又关闭连接这个做法,对操作系统的开销很大。

合理的做法就应该是系统启动的时候就创建数据库连接,然后需要使用 SQL 查询的时候,就从系统拿出数据库连接对象并提交查询,查询完了就把连接对象还给系统。系统在整个程序运行结束的时候再把数据库连接关闭。

考虑到一般数据库应用都是 Web 多用户并发应用,那么只有一个数据库连接对象肯定不够用,所以系统启动的时候就应该多创建几个数据库连接对象供多个线程使用,这一批数据库连接对象集合在一起就被称之为数据库连接池(Connection Pool)。

数据库连接池就是典型的用空间换时间的思想,系统启动预先创建多个数据库连接对象虽然会占用一定的内存空间,但是可以省去后面每次 SQL 查询时创建连接和关闭连接消耗的时间。 ‍

其实不仅仅是数据库有连接池的概念,很多其他技术也用到的连接池的概念。

# JDBC 中的连接池

JDBC 连接池有一个标准的接口 javax.sql.DataSource,注意这个类位于 Java 标准库中,但仅仅是接口。要使用 JDBC 连接池,我们必须选择一个 JDBC 连接池的实现,这很好理解,因为 JDBC 都只是一套接口而已,而连接池是基于 JDBC 的。接口有如下方法:

  • 获取连接:Connection getConnection()
  • 归还连接:Connection.close()。如果连接对象 Connection 是从连接池中获取的,那么调用 Connection.close() 方法,则不会再关闭连接了,而是归还连接

一般来说,我们也不会自己去实现数据库连接池,而是用数据库厂商提供的。常用的 JDBC 连接池有:

  • C3P0:早期开源的 JDBC 连接池。单线程,性能较差,适用于小型系统,代码 600KB 左右。
  • Druid:翻译为德鲁伊,几乎是 Java 语言中最好的数据库连接池,高效,由阿里巴巴开发。开源项目,GitHub 地址:alibaba/druid: 为监控而生的数据库连接池 (opens new window)
  • HikariCP:非常快,官网地址:brettwooldridge/HikariCP: 光 HikariCP (opens new window)
  • .....

因为会了后切换到其他连接池技术也很简单,这里仅仅介绍下 C3P0 和 Druid。

# C3P0

GitHub 地址:swaldman/c3p0 (opens new window) ‍ 使用步骤:

  • 先导入数据库驱动 jar 包

  • 导入 jar 包 (两个)c3p0-0.9.5.2.jar,mchange-commons-java-0.2.12.jar

  • 定义配置文件:

    • 名称: c3p0.properties 或者 c3p0-config.xml
    • 路径:直接将文件放在 src 目录下即可,会自动去该路径下寻找配置文件
  • 创建核心对象:数据库连接池对象 ComboPooledDataSource

  • 获取连接: Connection getConnection() ‍

# 导入依赖

可以从我的 GitHub 仓库里下载 jar 包:

Gitee:lib · /LearnJavaEE - Gitee (opens new window)

GitHub:LearnJavaEE/lib at master · Peter-JXL/LearnJavaEE (opens new window)

# c3p0-config.xml

这里直接给出一个配置文件:连接参数就是 JDBC 连接字符串和用户名密码等,

initialPoolSize:初始化申请的连接数量

maxPoolSize:连接池最大的连接数量,这里配置的比较小,仅仅是演示用,具体多少得看数据库性能

checkoutTimeout:超时时间,单位毫秒,3000 就是 3000 毫秒

<c3p0-config>
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
  	<!--  连接参数 -->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/learnjdbc</property>
    <property name="user">learn</property>
    <property name="password">learnpassword</property>
  
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>
</c3p0-config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

‍

# 使用 c3p0

接下来我们就可以验证下是否可以正常获取 Connection 对象了:

package chapter2JDBC;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;

public class JDBCDemo13C3P0 {
    public static void main(String[] args) throws Exception{
        // 1. 创建数据库连接池对象
        DataSource ds = new ComboPooledDataSource();

        // 2.获取连接对象
        Connection conn = ds.getConnection();

        // 3. 查看是否正常获取 Connection,不为 null 则正常
        System.out.println(conn);

        conn.close();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 验证连接池配置

我们在配置文件里定义了连接池的一些配置,例如最大连接数量是 10,接下来我们验证下这个配置。

例如,我们申请 11 个会怎么样呢?会等待超时(这里配了 3 秒),然后报错:

package chapter2JDBC;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;

public class JDBCDemo14C3P0 {
    public static void main(String[] args) throws Exception{
        // 1. 创建数据库连接池对象
        DataSource ds = new ComboPooledDataSource();

        for (int i = 1; i <= 11; i++) {
            Connection conn = ds.getConnection();
            System.out.println(conn);
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍ 运行结果:

com.mchange.v2.c3p0.impl.NewProxyConnection@30c7da1e [wrapping: com.mysql.cj.jdbc.ConnectionImpl@5b464ce8]
com.mchange.v2.c3p0.impl.NewProxyConnection@19dfb72a [wrapping: com.mysql.cj.jdbc.ConnectionImpl@17c68925]
com.mchange.v2.c3p0.impl.NewProxyConnection@3d24753a [wrapping: com.mysql.cj.jdbc.ConnectionImpl@59a6e353]
com.mchange.v2.c3p0.impl.NewProxyConnection@71be98f5 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@6fadae5d]
com.mchange.v2.c3p0.impl.NewProxyConnection@2d6e8792 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@2812cbfa]
com.mchange.v2.c3p0.impl.NewProxyConnection@506e6d5e [wrapping: com.mysql.cj.jdbc.ConnectionImpl@96532d6]
com.mchange.v2.c3p0.impl.NewProxyConnection@67b64c45 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@4411d970]
com.mchange.v2.c3p0.impl.NewProxyConnection@60f82f98 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@35f983a6]
com.mchange.v2.c3p0.impl.NewProxyConnection@edf4efb [wrapping: com.mysql.cj.jdbc.ConnectionImpl@2f7a2457]
com.mchange.v2.c3p0.impl.NewProxyConnection@6108b2d7 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@1554909b]
Exception in thread "main" java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
	at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:118)
	at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:77)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:690)
	at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140)
	at chapter2JDBC.JDBCDemo14C3P0.main(JDBCDemo14C3P0.java:13)
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@36d64342 -- timeout at awaitAvailable()
	at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1467)
	at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:644)
	at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:554)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:758)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:685)
	... 2 more
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

‍ 如果我们及时归还了数据库连接对象,那么后续的申请连接就能获取到了已有的连接了,例如:

DataSource ds = new ComboPooledDataSource();

for (int i = 1; i <= 11; i++) {
    Connection conn = ds.getConnection();
    System.out.println(i + ": "+ conn.toString());
    if( 5 == i){
        conn.close();
    }
}
1
2
3
4
5
6
7
8
9

部分运行结果:可以看到第 5 个和第 6 个申请的对象是同一个(哈希地址一样),也就是用的是同一个连接

5: com.mchange.v2.c3p0.impl.NewProxyConnection@2d6e8792 [wrapping: com.mysql.cj.jdbc.ConnectionImpl@2812cbfa]
6: com.mchange.v2.c3p0.impl.NewProxyConnection@3796751b [wrapping: com.mysql.cj.jdbc.ConnectionImpl@2812cbfa]
1
2

‍

# 多个数据源

有时候我们需要多个数据源,因此可以在配置文件里定义多个数据源:

<c3p0-config>
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
  	<!--  连接参数 -->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/learnjdbc</property>
    <property name="user">learn</property>
    <property name="password">learnpassword</property>
  
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>

  <name-config name="otherc3p0">
    <!--  连接参数 -->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/learnjdbc</property>
    <property name="user">learn</property>
    <property name="password">learnpassword</property>

    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </name-config>
</c3p0-config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

不同数据源用名字来区分,例如第 16 行 <name-config name="otherc3p0"> ‍ 获取数据源时,如果不传任何参数,则获取的是默认数据源,也就是用 <default-config> 定义的数据源。

如果指定名称,则可以获取指定的数据源:

DataSource ds = new ComboPooledDataSource("otherc3p0");
1

# Druid

使用步骤

  • 导入 jar 包 druid-1.0.9.jar

  • 定义配置文件:

    • 是 properties 形式的
    • 可以叫任意名称,可以放在任意目录下
  • 加载配置文件

  • 获取数据库连接池对象,通过工厂来来获取 DruidDataSourceFactory

  • 获取连接:getConnection

# 下载依赖

可以去一个叫做 Maven 仓库的地方下载 jar 包:Maven Repository: com.alibaba » druid (opens new window) ‍ 如果你使用 Maven,可以这样配置:

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>${druid-version}</version>
</dependency>
1
2
3
4
5

也可以从我的 GitHub 仓库里下载:

Gitee:lib · /LearnJavaEE - Gitee (opens new window)

GitHub:LearnJavaEE/lib at master · Peter-JXL/LearnJavaEE (opens new window) ‍

# 定义配置文件

例如我们定义一个 druid.properties 文件,文件路径为 src,文件内容如下:里面写的内容相信大家都能看到,这里就不解释了

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///learnjdbc
username=learn
password=learnpassword

# 初始化连接数量
initialSize=5

# 最大连接数
maxActive=10

# 最大等待时间
maxWait=3000
1
2
3
4
5
6
7
8
9
10
11
12
13

‍

# 使用 Druid

接下来我们定义 Druid 来获取 Connection:

package chapter2JDBC;


import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class JDBCDemo15Druid {
    public static void main(String[] args) throws Exception {

        Properties pro = new Properties();
        InputStream is =  JDBCDemo15Druid.class.getClassLoader().getResourceAsStream("durid.properties");
        pro.load(is);
        DataSource ds = DruidDataSourceFactory.createDataSource(pro);

        Connection conn = ds.getConnection();
        System.out.println(conn);

        conn.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

能正常打印出 Connection 对象,说明获取成功。其他连接池参数就不一一验证了。

# Druid 工具类

如果每次都要创建连接池对象,然后获取连接,还是有点麻烦的,并且连接池应该只有一个。为此,我们还是会定义一个工具类,需求如下:

  • 定义一个类 DruidUtils

  • 提供静态代码块加载配置文件,初始化连接池对象

  • 提供方法

    • 获取连接方法:通过数据库连接池获取连接
    • 释放资源
    • 获取连接池的方法(有的框架里面需要自己去调用获取连接的方法,这里为了通用,因此提供了这样的代码)

完整代码

package chapter2JDBC;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * Druid 连接池的工具类
 */
public class DruidUtils {
    private static DataSource ds;

    static {
        try {
            // 1。加载配置文件
            Properties pro = new Properties();
            pro.load(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnecton() throws SQLException {
        return ds.getConnection();
    }

    /**
     * 释放资源
     */
    public static void close(Statement statement, Connection conn){
        if( null != statement){
            try{
                statement.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }

        if( null != conn){
            try{
                conn.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 释放资源
     */
    public static void close(ResultSet rs, Statement statement, Connection conn){
        if( null != rs){
            try{
                statement.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
        close(statement, conn);
    }

    public static DataSource getDataSourse(){
        return ds;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 总结

数据库连接池是一种复用 Connection 的组件,它可以避免反复创建新连接,提高 JDBC 代码的运行效率,还可以配置连接池的详细参数并监控连接池。

参考

我们为什么要使用数据库连接池? - 知乎 (opens new window)

主流 Java 数据库连接池比较及前瞻 - 知乎 (opens new window)

黑马系列九之 JDBC 入门至精通(c3p0/druid/)_哔哩哔哩_bilibili (opens new window) ‍

上次更新: 2025/6/3 09:31:54
JDBC 事务和批量
JDBCTemplate

← JDBC 事务和批量 JDBCTemplate→

最近更新
01
学点统计学:轻松识破一本正经的胡说八道
06-05
02
2025 年 5 月记
05-31
03
《贫穷的本质》很棒,但可能不适合你
05-27
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式