数据库连接池简介
- 数据库连接池基本原理
在内部对象池中,维护一定数量的数据库连接,并对外暴露数据库连接的获取和返回方法。
如外部使用者可以通过getConnection方法获取数据库连接,使用完毕之后再通过releaseConnection方法将连接返回,注意此时的连接并没有关闭,而是由连接池管理器回收,为下一次使用做好准备
- 数据库连接池的作用
(1)资源重用
由于数据库连接得到了重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,增进了系统环境的平稳性
(2)更快的系统的响应速度
数据库连接池在初始化的过程中,往往已经创建了若干数据库连接置于池内备用。此时连接池的初始化操作均已经完成,对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
(3)新的资源分配手段
对于多应用共享同一数据库的系统而言,可以在应用层通过数据库连接的配置,实现数据库连接技术。
(4)统一的连接管理,避免数据库连接泄露
在较为完备的数据库连接池实现中,可以根据预先的连接占用超时设定,强制收回被占用的连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
- 常用的数据库连接池
(1)C3P0
- C3P0是一个开放源代码的JDBC连接池,他在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection和Statement池的DataSources对象。
(2)BoneCP
- BoneCP是一个开源的快速的JDBC连接池。BoneCP很小,只有四十几K(运行的时候需要log4j和Google Collections的支持,这二者加起来就不小了),而相比之下C3P0要六百多k。另外个人觉得BoneCP有一个缺点是:JDBC驱动的加载是在连接池之外的,这样一些应用服务器的配置上就不够灵活。
(3)DBCP
- Tomcat的数据源使用的就是DBCP,DBCP版本与jdk版本有关
(4)Proxool
- Proxool是一个Java SQL Driver驱动程序,提供了对你选择的其他类型的驱动程序的连接池封装。可以简单的移植到现存的代码中。完全可以配置。快速,成熟,健壮。可以透明的为你现在存在的JDBC驱动程序增加连接池功能。
- DBCP与C3P0的区别
dbcp没有自动回收空闲连接的功能。C3P0有自动回收空闲连接的功能。两者主要是对数据连接的处理方式不同。
- C3P0提供最大的空闲时间,DBCP提供最大的连接数
- C3P0当连接数超过了最大的空闲连接时间,当前连接就会被断掉。DBCP当连接数超过了最大的连接数量的时候 ,所有连接就会被断掉。
- 若同时向数据库发送多个请求连接的时候,数据库会压力过大
- 数据库连接池常用的配置的参数:重要
(1)空闲连接数
初始化了连接但是还没有被使用
(2)活动连接数
正在被使用的连接
(3)最大连接数
限制最多可以创建的连接数量
- 数据库连接池,如果活动连接中某个连接执行完毕的情况下,是不会断开连接的,其是可回收的,重新进入连接池中变为空闲连接数。也就是活动连接减一,空闲连接数加一。
举例说明:
假设客户端发送jdbc请求,会现在数据库连接池中判断,该连接池有没有一个空闲的连接,如果有,取一个空闲的连接将其变为活动连接,也就是说空闲连接减一,活动连接加一。当活动连接使用完毕的时候,将当前活动连接进行回收,重新变为空闲连接。数据库连接池执行完毕后,不会关闭,而是会进行 回收,进行复用
手写数据库连接池
-
可以用阻塞队列(推荐)和wait/notify实现(这里用的是wait/notify实现的)
-
核心参数:
(1)空闲连接:是一个容器,存放没有使用的连接,初始化的时候就是初始化空闲连接。
(2)活动连接:是一个容器,存放正在使用的连接。 -
核心步骤:
(1)初始化连接池(初始化的时候初始化的是空闲的连接)
(2)调用getConnection方法获取连接
-----(2.1) 先去空闲连接容器中获取连接,存放在活动连接容器中
-----(2.2) 如过当前连接数大于了最大活跃连接数,进行等待(wait方法,等待时间为我们设置的connTimeOut,即重复获得连接的频率);如过小于最大活跃连接数,无需等待,判断空闲连接是否有连接存在,如过还有空闲连接,直接取出来用,如过此时没有空闲连接了,我们需要重新创建新的空闲连接,判断此连接是否可用,若可用存入活跃连接池,若不可用责任进行重试,进行递归重新调用getConnection
(3)调用releaseConnection方法释放连接
-----(3.1) 判断当前连接是否可用
-----(3.2) 判断空闲连接是否小于最大空闲连接数(maxConnections)。小于表示空闲连接没有满,大于表示满了;如果空闲连接满了就关闭该连接,没有满就把它放到空闲连接池中。不管是否满了把该链接从活跃连接池中取出,然后做唤醒操作,唤醒getConnection中的等待操作。 -
参数介绍:
(1)minConnections:空闲池最小的连接数,最少存在1个空闲连接
(2)maxConnections:空闲池最大的连接数,最多存在10个空闲连接
(3)initConnections:初始化连接数,就是说,我们初始化的时候,直接初始化多少个连接
(4)connTimeOut:重复获得连接的频率,表示连接的时候超过多少就超时,如过重复获取连接超时就报这个错
(5)maxActiveConnections:最大允许的活动连接的线程数,就是最大允许连接数,和数据库对应
(6)connectionTimeOut:连接超时时间,访问网络请求的时候的超时连接。 -
代码演示:
(1)代码架构:
(2)pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xiyou</groupId>
<artifactId>my-datasource</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
</dependencies>
</project>
(3)实体类,用来配置数据库连接的参数
package com.xiyou.bean;
import lombok.Data;
/**
* 数据库配置的实体类,一般是写在properties文件中的
*/
@Data
public class DbBean {
/**
* 配置数据库驱动
*/
private String driverName = "com.mysql.jdbc.Driver";
/**
* 数据库连接
*/
private String url = "jdbc:mysql://localhost:3306/test";
/**
* 用户名
*/
private String userName = "root";
/**
* 密码
*/
private String password = "05131004";
/**
* 连接池的名字,随便起
*/
private String poolName = "dataSource1";
/**
* 空闲连接池的最小连接数
*/
private Integer minConnections = 1;
/**
* 空闲连接池的最大连接数
*/
private Integer maxConnections = 10;
/**
* 初始化的连接数,默认是初始化空闲连接池,即空闲连接池的初始化连接
*/
private Integer initConnections = 5;
/**
* 重复获得连接的频率,就是一个连接获取失败会隔此时间进行重试
*/
private long connTimeOut = 1000;
/**
* 允许连接的最大的活跃连接数(就是最大正在使用的连接的数量)
*/
private Integer maxActiveConnections = 100;
/**
* 连接超时的时间,就是由于网络原因连接超时,和上面的connTimeOut属性不同
* 默认是20分钟
*/
private long connectionTimeOut = 1000 * 60 * 20;
}
(4)自定义数据库连接池
package com.xiyou.connection;
import com.xiyou.bean.DbBean;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.List;
import java.util.Vector;
/**
* 自定义实现数据库连接池
*/
public class ConnectionPool implements IConnectionPool{
/**
* 用来存储空闲连接的集合,因为是线程安全的,所以用Vector
*/
private List<Connection> freeConnection = new Vector<>();
/**
* 用来存储正在活跃连接数,线程安全用Vector
*/
private List<Connection> activeConnection = new Vector<>();
/**
* 配置的数据库参数
*/
private DbBean dbBean;
/**
* 用来记录现在已经有多少个活跃连接了
*/
private volatile Integer countConn = 0;
/**
* 构造函数
* @param dbBean
*/
public ConnectionPool(DbBean dbBean) {
// 初始化配置类
this.dbBean = dbBean;
// 初始化线程池
init();
}
/**
* 用来根据配置的相关信息,构造线程池
*/
private void init() {
// 如果配置类为空,则不继续执行,退出
if (dbBean == null) {
return;
}
// 获取连接数, 初始化配置的初始连接数的数量 initConnections表示初始化空闲的连接池
for (int i = 0; i < dbBean.getInitConnections(); i++) {
// JDBC创建连接
Connection connection = newConnection();
// 如果连接不为空,则添加到集合中
if (connection != null) {
// 初始化的连接存放在空闲连接池中
freeConnection.add(connection);
}
}
}
/**
* 用来创建Connection连接
* @return
*/
private synchronized Connection newConnection() {
try {
Class.forName(dbBean.getDriverName());
Connection connection = DriverManager.getConnection(dbBean.getUrl(), dbBean.getUserName(), dbBean.getPassword());
return connection;
} catch (Exception e) {
// 如果有错直接返回空
return null;
}
}
/**
* 从空闲连接池中获取一个连接,将其变为活跃连接
* @return
*/
@Override
public synchronized Connection getConnection(){
try {
Connection connection = null;
// 当已经创建的连接小于最大的活跃线程数(这样判断的原因是我们要把一个连接从空闲连接集合中放到活跃连接集合中)
// 因为创建了多少个连接最开始都是放到空闲连接池中的
if (countConn < dbBean.getMaxActiveConnections()) {
// 如果空闲连接还有
// 取一个连接出来,并且从空闲集合中删除
if (freeConnection.size() > 0) {
connection = freeConnection.remove(0);
} else {
// 如果当前空闲连接集合中没有多的连接了,就新建一个(因为我们最初已经判断了当前创建的连接数量是否大于最大活跃线程数),一会放到活跃连接集合中
connection = newConnection();
}
// 判断连接是否可用
boolean avaliable = isAvaliable(connection);
// 如果连接可用
if (avaliable) {
// 将其存放在活跃集合中
activeConnection.add(connection);
countConn ++;
} else {
// 利用递归进行重试
connection = getConnection();
}
} else {
// 如果当前创建的连接数量已经大于了最大活跃连接数,则进行等待,当前的活跃连接已经满了,无法再让创建的连接变为活跃连接
// 等待超时,则进行重试
wait(dbBean.getConnTimeOut());
// 进行重试(也是利用递归进行重试)
connection = getConnection();
}
return connection;
} catch (Exception e){
return null;
}
}
/**
* 根据传入的连接判断连接是否可用
* @param connection
* @return
*/
public boolean isAvaliable(Connection connection) {
try {
if (connection == null || connection.isClosed()) {
return false;
}
} catch (Exception e) {
System.out.println("连接不可用");
}
return true;
}
/**
* 释放连接
* @param connection
*/
@Override
public synchronized void releaseConnection(Connection connection){
try {
// 1. 判断连接是否可用
if(isAvaliable(connection)) {
// 2. 判断空闲连接是否已经满了,没满就回收,满了就直接关闭 getMaxConnections是空闲连接池的最大连接数量
if (freeConnection.size() < dbBean.getMaxConnections()) {
// 表示没有瞒
freeConnection.add(connection);
} else {
// 空闲连接集合满了,直接关闭
connection.close();
}
// 释放完毕,直接从活跃集合中删除
activeConnection.remove(connection);
// 当前活跃连接数量-1
countConn -- ;
// 唤醒getConnection中等待的线程
notifyAll();
}
} catch (Exception e){
}
}
}
(5)数据库连接池抽象
package com.xiyou.connection;
import java.sql.Connection;
//连接数据库池
public interface IConnectionPool {
// 获取连接(重复利用机制)
public Connection getConnection();
// 释放连接(可回收机制)
public void releaseConnection(Connection connection);
}
(6)主启动类,启动三个线程,每个线程造10个数据库连接
package com.xiyou;
import com.xiyou.bean.DbBean;
import com.xiyou.connection.ConnectionPool;
import com.xiyou.connection.ConnectionPoolManager;
import java.sql.Connection;
/**
* 主启动测试类
*/
public class Test {
public static void main(String[] args) {
ThreadConnection threadConnection = new ThreadConnection();
for (int i = 1; i < 3; i++) {
Thread thread = new Thread(threadConnection, "线程i:" + i);
thread.start();
}
}
}
class ThreadConnection implements Runnable{
private ConnectionPool connectionPool = new ConnectionPool(new DbBean());
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Connection connection = connectionPool.getConnection();
System.out.println(Thread.currentThread().getName() + " , connection : " + connection);
// 释放数据库连接
connectionPool.releaseConnection(connection);
}
}
}
(7)结果:
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@6a366212
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@7b701024
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@64f18124
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@75b7e13a
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@6a366212
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@60ce0962
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@7b701024
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@75b7e13a
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@64f18124
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@6a366212
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@7b701024
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@75b7e13a
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@64f18124
线程i:2 , connection : com.mysql.jdbc.JDBC4Connection@6a366212
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@60ce0962
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@7b701024
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@75b7e13a
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@64f18124
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@6a366212
线程i:1 , connection : com.mysql.jdbc.JDBC4Connection@60ce0962
发现连接有的是一致的表示重用了。