从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

  • JavaSenior

  • JavaEE

    • JavaEE概念

    • Ant

    • Maven

    • 日志

    • Junit

    • JDBC

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

    • Java
  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • 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)

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/3/30 09:46:17
JDBC事务和批量
JDBCTemplate

← JDBC事务和批量 JDBCTemplate→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式