一、什么是数据库连接池
拿公交司机做比方,司机要开车了,不可能马上开车的时候去造一辆车,开完车又给销毁了,这样做很大一部分时间将浪费在造车上面,效率非常的低下。正确的做法是已经造好车辆,开车的时候,从车库里面提一辆出去,开完车又把车辆放回车库,这样做会提高效率。连接池的工作原理也是如此。
数据库的获取链接与释放连接非常消耗系统资源,在我的笔记本上,用mysql数据库,获得15个连接要花费900ms的时间,如果在算上释放连接的时间,还会更长。而使用的数据库连接查询往往很快,这就造成了数据访问效率的低下。如果在系统启动时,就获取一定部分的数据库连接,应用程序使用时直接从已有的连接中获取,程序使用完毕就回收回来,这样就减少了获取连接和释放连接的时间,提高了数据访问的效率。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。(百度百科:数据库连接池词条)
二、数据库连接池的设计思路
2.1 需求分析
Java的数据库访问方式常见的有两种:JDBC和ODBC。对于每一种连接方式,
都要有以下几个参数
Username :数据库用户名
Password : 数据库连接密码
ConnetionUrl:数据库连接字符串。如:jdbc:mysql://localhost:3306/demo_db
DriveClass : 连接驱动类 如:com.mysql.jdbc.Driver
对于连接池来讲,还有要以下几个参数
MaxPoolSize: 连接池中最大连接数
MinPoolSize: 连接池中最小连接数
InitPoolSize: 初始连接数
DispoilTime : 从未使用的连接的回收时间
对于这些参数,在开发中有可能以不同的方式配置,有可能配置到数据库中,有可能配置到xml中,也有可能配置到.property文件中。所以要有不同的读取器去加载这些配置参数,并统一返回参数信息。
2.2 UML类图设计
ConnetionPoolManager 连接池管理类。这个类的目的是一方面针对不同连接类型,选择不同的连接池实现,并对连接池生命周期进行管理,负责连接池的产生与销毁。另一方面,对于配置文件不同的存在方式,选择恰当的读取器。
reader : 接口,配置文件读取器。可以针对不同的配置文件进行具体实现。
ConnetionPropertiesReader实现关系
connectionProperties 用于封装配置文件的内容,独立与配置文件的形式,获取方式由不同的reader返回。
pool 接口,数据库连接池,提供数据库连接的核心功能。可以针对不同的类型基于不同的实现
纵观整个框架,设计应该呈现这样的框架:
连接池算法描述与实现
算法描述
数据库连接池就是事先将连接创建好,程序用到的时候给予,不用的时候回收。连接池的数量也可以动态拓展,但是有个上限maxPoolSize,同时也有个下限minPoolSize。
我们定义两个数组A,B。A数组放置空闲的连接,B数组放置正在使用的连接。
A,B两个数组的中连接数总和为currentPoolSize。其中currentPoolSize>=minPoolSize&¤tPoolSize<=maxPoolSize。
假设应用程序App此时向连接池申请一个连接。会遇到如下几种情形:
1. A数组中有闲置连接,向程序返回一个数据库连接,同时将此连接移到数组B中维护。
2. A中无可用连接。当前A、B中的连接总数currentPoolSize<maxPoolSize。此时可以新建连接。currentPoolSize自加,B中添加一个刚刚新建的连接。
3. A中无可用连接。当前A、B中的连接总数currentPoolSize>=maxPoolSize.此时应用程序应该等待。直到其他程序释放连接。或者正在使用的连接失效,从而被移除连接池,此时可以新建连接返回。
我们同时需要启动另外一个线程,定时检测哪些连接长时间闲置,如果currentPoolSize> minPoolSize的时候,我们就可以把该连接回收。这样的机制,就可以维持连接池的连接池维持在minPoolSize与maxPoolSize之间。
因为currentPoolSize与maxPoolSize大小关系会发生变化,所以我们要启动一个checkEqual线程去检测这两个变量之间的关系,这个线程在获取连接wait()时启动。
API介绍
算法实现
package com.acvoice.connection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author IT民工
* @time 2016.7.18
* jdbc连接池
*/
public class JdbcConnectionPool implements ConnectionPool {
private ConnectionProperties properties;
volatile private int currentPoolSize;
private List<ComConnection> pool; //可用连接池
private List<Connection> usedPoll;//已用连接池
private ExecutorService exc = Executors.newCachedThreadPool();
private static ConnectionPool connectionPool = null;
/**
* 初始化连接池
* @param pro
* @throws ClassNotFoundException
* @throws SQLException
*/
private JdbcConnectionPool(ConnectionProperties pro) throws ClassNotFoundException, SQLException{
this.properties = pro;
pool = new ArrayList<ComConnection>(properties.getInitPoolSize());
usedPoll = new ArrayList<Connection>();
Class.forName(properties.getDriverClass());
for(int i = 0;i<properties.getInitPoolSize();i++){
Connection con = getConnectionByManager();
pool.add(new ComConnection(con, 0));
}
checkOut();
}
/**
* @param pro
* @return 单例模式获取连接池,注意同步
* @throws ClassNotFoundException
* @throws SQLException
*/
synchronized public static ConnectionPool getConnnectionPool(ConnectionProperties pro) throws ClassNotFoundException, SQLException{
if(connectionPool ==null){
connectionPool = new JdbcConnectionPool(pro);
}
return connectionPool;
}
/**
* 当前可以获取连接时即currentPoolSize<maxPoolSize
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
synchronized public Connection getConect() throws InterruptedException, ExecutionException{
Connection con = null;
if(pool.size()==0){//当前可用为0,并且还可以拓展
Callable<Connection> c = new Callable<Connection>() {
public Connection call() throws Exception {
return getConnectionByManager();
}
};
for(int i = 0;i<3;i++){//尝试三次,获取,如果三次都没有获取到,就跑出异常
Future<Connection> future= exc.submit(c);
con = future.get();
if(con!=null){
usedPoll.add(con);
currentPoolSize ++;
return con;
}
}
throw new RuntimeException("尝试了最大次数,并没有获得链接");
}else{//不为0,可以从空闲列表中获取
con = pool.get(pool.size()-1).getConnection();
usedPoll.add(con);
pool.remove(pool.size()-1);
return con;
}
}
/**
* @return
* @throws SQLException
* 从连接池manger中获取
*/
private Connection getConnectionByManager() throws SQLException{
Connection connection = DriverManager.getConnection(
properties.getConnectionUrl(),properties.getUsername(),properties.getPassword());
return connection;
}
/* (non-Javadoc)
* @see com.acvoice.ConnectionPool#getConnection()
*/
synchronized public Connection getConnection(){
Connection con = null;
try {
if(currentPoolSize>=properties.getMaxPoolSize()&&pool.size()==0){
checkEquel();//启动一个现成去检测currentPoolSize与MaxPoolSize的关系
wait();//等待,可以拓展
con = getConect();
}else{
con = getConect();
}
} catch (InterruptedException e) {
e.printStackTrace();
}catch (ExecutionException e) {
e.printStackTrace();
}
return con;
}
/* (non-Javadoc)
* @see com.acvoice.ConnectionPool#realse(java.sql.Connection)
*/
synchronized public void realse(Connection connection ){
pool.add(new ComConnection(connection, 0));//回收可用,并且重新计时
usedPoll.remove(connection);//把已用连接池中的该连接的引用移除掉
notify();
}
/**
* 每隔1分钟维护列表,把空闲时间比较长的连接释放掉,把不可用的连接也释放掉
*/
synchronized private void checkOut(){
Runnable r = new Runnable() {
public void run() {
try {
for(int i=usedPoll.size()-1;i>=0;i--){
try {
//检测连接是否可用
if(usedPoll.get(i).isValid(100)){
}else{
usedPoll.remove(i);
currentPoolSize--;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
for(int i=pool.size()-1;i>=0;i--){
try {
//检测连接是否可用
if(pool.get(i).getConnection().isValid(100)){
}else{
pool.remove(i);
currentPoolSize--;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
for(int i =pool.size()-1;i>=0;i--){//长时间空闲的释放掉
ComConnection com = pool.get(i);
if(pool.get(i).getCount()>properties.getDispoilTime()&¤tPoolSize>properties.getMinPoolSize()){
try {
com.getConnection().close();
pool.remove(i);
com =null;
currentPoolSize--;
} catch (SQLException e) {
e.printStackTrace();
}
}else{
com.setCount(com.getCount()+1);
}
}
} finally{
}
}
};
ScheduledExecutorService set = Executors.newScheduledThreadPool(1);
//定时检测
set.scheduleWithFixedDelay(r, 1, 1, TimeUnit.MINUTES);
}
/**
* 改线程检测currentPoolSize与maxPoolSize的关系,如果关系发生变化,就唤醒等待线程
*/
synchronized private void checkEquel(){
Runnable r = new Runnable() {
public void run() {
while(currentPoolSize>=properties.getMaxPoolSize()){
}
notify();
}
};
exc.execute(r);
}
/**
* @author zhao
* 该内部类用处存储当前Connection与空闲时间
* count为计时几次。
*/
private class ComConnection implements Comparable<ComConnection>{
public Connection getConnection() {
return connection;
}
public ComConnection(Connection connection, Integer count) {
super();
this.connection = connection;
this.count = count;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
Connection connection;
Integer count;
public int compareTo(ComConnection o) {
if(o.getCount()>count)
return -1;
return 1;
}
}
synchronized public void destroy() {
for(int i=pool.size();i>=0;i--){
try {
pool.get(i).getConnection().close();
pool.remove(i);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int i=usedPoll.size();i>=0;i--){
try {
usedPoll.get(i).close();
usedPoll.remove(i);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}