概述

  数据库连接池是负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。那么其中的运行机制又是怎样的呢?今天主要介绍一下数据库连接池原理和常用的连接池。

一、为什么要使用连接池

  数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。

  数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。

  连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

数据库连接池技术带来的优势:

  1. 资源重用
    由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
  2. 更快的系统响应速度
    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
  3. 新的资源分配手段
    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
  4. 统一的连接管理,避免数据库连接泄漏
    在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。

二、传统的连接机制与数据库连接池运行机制区别

1、不使用连接池流程

下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。

不使用数据库连接池的步骤:

  1. TCP建立连接的三次握手
  2. MySQL认证的三次握手
  3. 真正的SQL执行
  4. MySQL的关闭
  5. TCP的四次握手关闭

可以看到,为了执行一条SQL,却多了非常多网络交互。

优点:

  • 实现简单

缺点:

  • 网络IO较多
  • 数据库的负载较高
  • 响应时间较长及QPS较低
  • 应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
  • 在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)

2、使用连接池流程

使用数据库连接池的步骤:

第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。

优点:

  • 较少了网络开销
  • 系统的性能会有一个实质的提升
  • 没了麻烦的TIME_WAIT状态

三、数据库连接池的工作原理

连接池的工作原理主要由三部分组成,分别为

  • 连接池的建立
  • 连接池中连接的使用管理
  • 连接池的关闭

一、连接池的建立

  一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。

Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

二、连接池的管理

  连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:

当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。

当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

三、连接池的关闭

当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

四、都有那些连接池方案

1、c3p0、dbcp、druid三大连接池的区别

c3p0、dbcp、druid三大连接池的区别
c3p0开放的源代码的JDBC连接池,
DBCP依赖Jakarta commons-pool对象池机制的数据库连接池  
druid阿里出品,淘宝与支付宝专用的数据库连接池,它还包括了一个ProxyDriver、一系列内置的JDBC组件库,一个SQL Parser。支持所有JDBC兼容的数据库
属性对比
连接池的配置大体可以分为:基本配置、关键配置、性能配置等主要配置
基本配置:连接池进行数据库连接的四个必须配置
基本配置 DBPCC3P0Druid
用户名usernameuerusername
密码passwordpasswordpassword
URLurljdbcUrljdbcUrl
驱动类名driverClassNamedriverClassdriverClassName
注:在Druid连接池配置中,driverClassName可配可不配,不配置的话可以根据url自动识别数据库类型,然后选择相应的driverClassName。
关键配置:为了发挥数据库连接池的作用。
关键配置 DBCPc3p0Druid
最小连接数minldle(0)miniPoolSize(3)minldle(0)
初始化连接数innitialSize(0)initialPoolSize(3)initialSize
最大连接数maxTotal(8)maxPoolSize(15)maxActive(8)
最大等待时间maxWaitMillis(毫秒)maxIdleTime(0秒)maxWait(毫秒)
说明最小连接数是数据库一直保持的数据库连接数
初始化连接数连接池启动时创建的初始化数据库连接数量
最大连接数连接池能申请的最大连接数,请求超出此数时,后面的数据库连接请求被加入等待队列中。
最大等待时间当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置为0或负数,无限等待。
在DBCP连接池的配置中,还有一个maxldle的属性,表示最大空闲连接数,超过的空闲连接将被释放。对应的该属性在Druid中不再使用,配置了也不会有效果;而c3p0就没有对应的属性。

数据库连接池在初始化的时候回创建initialSize个连接,当有数据库操作时,会从池中取出一个连接。

如果连接数等于maxActive,则会等待一段时间,等待其他操作释放掉一个连接,如果这个时间超过了maxWait,就会报错。如果当前使用的数量没有达到maxActive,则会判断当前是否空闲连接,有的话直接使用空闲连接,没有的话,则新建一个连接。连接使用完毕后,放入池中,等待其他操作复用。      

性能配置:预缓存设置、连接有效性检测设置、连接超时关闭设置
 预缓存设置:用于控制PreparedStatement数量,提升数据库性能。
预缓存 DBCPc3p0Druid性能配置
开启缓存功能poolPreparedStatementsmaxStatementspoolPreparedStatements
单个连接拥有的最大缓存数maxOpenPreparedStatementsmaxStatementsPerConnectionmaxOpenPreparedStatements
 连接有效性检测设置:连接池内部有机制判断,如果当前的总连接数少于minildle,则会建立新的空闲连接,以保证连接数达到minildle。如果当前连接池中某个连接处于空闲,则被物理性的关闭掉。有些数据库连接的时候有超时的限制(mysql连接8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效,这时候,设置一个testWhileldle参数为true,可以保证连接池中,定时检测连接可用性,不可用的连接会被抛出或者重建,保证池中connection可用。
连接有效性检测设置 DBCPc3p0Druid
申请连接检测testOnBorrowtestConnectionOnCheckintestOnBorrow
是否超时检测testWhileldle testWhileldle
空闲时间timeBetweenEvictionRuns MillisidleConnectionTestPeriodtimeBetweenEvictionRunsMillis
校验sql语句validationQuerypreferredTestQueryvalidationQuery
归还连接检测testOnReturntestConnectionOnCheckouttestOnReturn
 超时连接关闭设置:用来检测当前使用的连接是否发生泄漏,所以在代码内部就假定如果一个连接建立连接时间很长,则认定为泄漏,继而强制关闭。
超时连接关闭设置 DBCPc3p0Druid
是否超时关闭连接removeAbandonedbreakAfterAcquireFailureremoveAbandoned
超时时间removeAbandonedTimeoutcheckoutTimeoutremoveAbandonedTimeout
是否记录日志logAbandoned logAbandoned



2、c3p0、dbcp、druid三大连接池的技术实现

C3P0技术

  导包操作:c3p0-0.9.1.2.jar

  查看说明文档:

 实现的方式一:硬编码的形式

 1 @Test
 2     public void testGetConnection() throws Exception {
 3         // 获取数据库连接池的实例化对象
 4         ComboPooledDataSource cpds = new ComboPooledDataSource() ;
 5         
 6         // 设置连接需要的基本信息
 7         cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
 8         cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8");
 9         cpds.setUser("root");
10         cpds.setPassword("password");
11         // 设置与管理数据库连接池相关的属性
12         // 设置连接池中初始连接的数量
13         cpds.setInitialPoolSize(10);
14         
15         // 获取数据库连接
16         Connection conn = cpds.getConnection() ;
17         System.out.println(conn);
18     }

实现的方式二:配置文件的方式

  配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <c3p0-config>
 3     <named-config name="myc3p0">
 4             <!-- 提供用于获取连接的基本信息 -->
 5         <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
 6         <property name="jdbcUrl">jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8</property>
 7             <!-- 主机号与端口号为本机3306时,可以省略:即jdbc:mysql:///test?serverTimezone=GMT%2B8 -->
 8         <property name="user">root</property>
 9         <property name="password">password</property>
10     
11             <!-- 管理数据库连接池的基本信息 -->
12             <!-- 当数据库连接池的连接不够时,c3p0 一次性向数据库服务器申请的连接数 -->
13         <property name="acquireIncrement">5</property>
14             <!-- c3p0数据库连接池中初始化时的连接数 -->
15         <property name="initialPoolSize">10</property>
16         
17             <!-- c3p0数据库连接池中维护的最少连接数 -->
18         <property name="minPoolSize">10</property>
19             <!-- c3p0数据库连接池中维护的最多连接数 -->
20         <property name="maxPoolSize">100</property>
21         
22             <!-- c3p0数据库连接池中最多维护的Statement的个数 -->
23         <property name="maxStatements">50</property>
24             <!-- 每个连接中最多可以使用的Statement的个数 -->
25         <property name="maxStatementsPerConnection">2</property>
26     </named-config>
27 </c3p0-config>

说明:

  1. 配置文件的名称要求必须为 “c3p0-config.xml”  ;

  2. 第 3 行的 <named-config name="myc3p0"> 主要用于设置该配置文件的名称 ;

  3. 提供用于获取连接的基本信息的 “name” 可以参考方式一中的方法名,注意不能出现错误。

1 @Test
2     public void testGetConnection2() throws Exception {
3         // 实例化数据库连接池的对象并传入配置文件
4         ComboPooledDataSource cpds = new ComboPooledDataSource("myc3p0");
5         // 获取数据库连接
6         Connection conn = cpds.getConnection() ;
7         System.out.println("myc3p0:" + conn);
8 }

DBCP数据连接池技术:

  导包操作:commons-dbcp-1.4.jar、commons-pool-1.5.5.jar

  查看说明文档:

方式一:

 1 @Test
 2     public void testGetConnection() throws Exception {
 3         // 获取DBCP数据库连接池的实例化对象
 4         BasicDataSource bds = new BasicDataSource() ;
 5         
 6         // 设置连接需要的基本信息
 7         bds.setDriverClassName("com.mysql.cj.jdbc.Driver");
 8         bds.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8");
 9         bds.setUsername("root");
10         bds.setPassword("password");
11         // 设置其他与数据库连接池管理的相关信属性
12         bds.setInitialSize(10);
13         
14         // 获取数据库连接
15         Connection conn = bds.getConnection() ;
16         System.out.println(conn);
17     }

dbcp连接池常用基本配置属性:

属性默认值说明
initialSize0连接池启动时创建的初始化连接数量
maxActive8连接池中可同时连接的最大的连接数
maxIdle8连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制
minIdle0连接池中最小的空闲的连接数,低于这个数量会被创建新的连接。该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大。
maxWait无限制最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待
poolPreparedStatementsfalse开启池的Statement是否prepared
maxOpenPreparedStatements无限制开启池的prepared 后的同时最大连接数
minEvictableIdleTimeMillis连接池中连接,在时间段内一直空闲, 被逐出连接池的时间
removeAbandonedTimeout300超过时间限制,回收没有用(废弃)的连接
removeAbandonedfalse超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收

方式二:使用配置文件:

配置文件如下:

1 driverClassName=com.mysql.cj.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
3 username=root
4 password=password

 1     @Test
 2     public void testGetConnection2() throws Exception{
 3 //        方式一:使用类的加载器
 4 //        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
 5 //        方式二:普通的文件流(当前工程下)
 6         FileInputStream is = new FileInputStream(new File("src/dbcp.properties")) ;
 7         Properties pros = new Properties() ;
 8         pros.load(is) ;
 9         DataSource ds = BasicDataSourceFactory.createDataSource(pros) ;
10         
11         Connection conn = ds.getConnection() ;
12         System.out.println(conn);
13     }

Druid数据库连接池技术:

  配置文件的方式:

1 driverClassName=com.mysql.cj.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
3 username=root
4 password=password

1 @Test
2     public void testGetConnection() throws Exception {
3         InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties") ;
4         Properties pros = new Properties() ;
5         pros.load(is);
6         DataSource ds = DruidDataSourceFactory.createDataSource(pros) ;
7         Connection conn = ds.getConnection() ;
8         System.out.println(conn);
9     }

详细配置参数

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

总结:利用以上三种数据库连接池技术实现数据库的工具类"JDBCUtil“

  1 package edu.cn.ahpu4.util;
  2 
  3 import java.io.InputStream;
  4 import java.sql.Connection;
  5 import java.sql.ResultSet;
  6 import java.sql.SQLException;
  7 import java.sql.Statement;
  8 import java.util.Properties;
  9 import javax.sql.DataSource;
 10 import org.apache.commons.dbcp.BasicDataSourceFactory;
 11 import com.alibaba.druid.pool.DruidDataSourceFactory;
 12 import com.mchange.v2.c3p0.ComboPooledDataSource;
 13 
 14 /**
 15  * 
 16  * @Description 操作数据库的工具类
 17  * @author XiaoFeng Email:1431230065@qq.com
 18  * @version
 19  * @date 2020年8月20日下午4:02:59
 20  *
 21  */
 22 public class JDBCUtil {
 23 
 24     /***
 25      * 
 26      * @Description 使用C3P0技术获取数据库连接
 27      * @author XiaoFeng
 28      * @date 2020年8月20日下午4:12:17
 29      * @return 数据库连接
 30      * @throws SQLException 
 31      */
 32     // 实例化数据库连接池的对象并传入配置文件
 33     private static ComboPooledDataSource cpds = new ComboPooledDataSource("myc3p0");
 34     public static Connection getConnection1() throws SQLException {
 35         // 获取并返回数据库连接
 36         return cpds.getConnection() ;
 37     }
 38     
 39     /***
 40      * 
 41      * @Description    使用DBCP技术获取数据库连接
 42      * @author XiaoFeng  
 43      * @date 2020年8月26日上午11:16:47  
 44      * @return
 45      * @throws Exception
 46      */
 47     private static DataSource ds2 ;
 48     static {
 49         try {
 50             InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
 51             Properties pros = new Properties() ;
 52             pros.load(is) ;
 53             // 创建一个DBCP数据库连接池
 54             ds2 = BasicDataSourceFactory.createDataSource(pros) ;
 55         } catch (Exception e) {
 56             e.printStackTrace();
 57         }
 58     }
 59     public static Connection testGetConnection2() throws Exception{
 60         Connection conn = ds2.getConnection() ;
 61         return conn ;
 62     }
 63 
 64     /***
 65      * 
 66      * @Description    使用druid技术获取数据库连接
 67      * @author XiaoFeng  
 68      * @date 2020年8月26日上午11:38:41  
 69      * @return
 70      * @throws Exception
 71      */
 72     private static DataSource ds3 ;
 73     static {
 74         try {
 75             InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties") ;
 76             Properties pros = new Properties() ;
 77             pros.load(is);
 78             ds3 = DruidDataSourceFactory.createDataSource(pros) ;
 79         } catch (Exception e) {
 80             e.printStackTrace();
 81         }
 82     }
 83     public static Connection testGetConnection3() throws SQLException{
 84         Connection conn = ds3.getConnection() ;
 85         return conn ;
 86     }
 87     
 88     /***
 89      * 
 90      * @Description    关闭资源
 91      * @author XiaoFeng  
 92      * @date 2020年8月20日下午4:15:35  
 93      * @param conn    connection资源
 94      * @param ps PreparedStatement资源
 95      */
 96     public static void closeResource(Connection conn,Statement ps) {
 97         if (ps != null) {
 98             try {
 99                 ps.close();
100             } catch (SQLException e) {
101                 e.printStackTrace();
102             }
103         }
104         if (conn != null) {
105             try {
106                 conn.close();
107             } catch (SQLException e) {
108                 e.printStackTrace();
109             }
110         }
111     }
112     
113     /***
114      * 
115      * @Description    关闭资源
116      * @author XiaoFeng  
117      * @date 2020年8月20日下午5:39:25  
118      * @param conn
119      * @param ps
120      * @param rs ResultSet资源
121      */
122     public static void closeResource(Connection conn,Statement ps,ResultSet rs) {
123         if (ps != null) {
124             try {
125                 ps.close();
126             } catch (SQLException e) {
127                 e.printStackTrace();
128             }
129         }
130         if (conn != null) {
131             try {
132                 conn.close();
133             } catch (SQLException e) {
134                 e.printStackTrace();
135             }
136         }
137         if(rs != null) {
138             try {
139                 rs.close();
140             } catch (SQLException e) {
141                 e.printStackTrace();
142             }
143         }
144     }
145 } 

参考:数据库连接池的实现及原理(图文详解) - 知乎

参考:c3p0、dbcp、druid三大连接池的区别_不务正业且不专业的程序猿的博客-CSDN博客_druid和c3p0哪个好

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐