最近复习JDBC的一些知识,看到C3P0连接池,虽然用过很多次,但是如果说具体原理和实现方法。一时半会竟然无法回答,于是把C3P0的内容看了一下,同时自己简单的按照这个思路去手写了一个数据库连接池。
先来整理一下思路,对数据库连接池进行一个总结。
在我们对数据库进行操作时,一般分为以下几个步骤.
- 加载数据库驱动
- 获得数据库资源
- 获得数据库连接
- 编写SQL语句
- 关闭数据库连接
在这种情况下,我们每当进行一次操作,都需要开启并关闭一次连接。这种对资源的申请操作是非常消耗时间的行为。通过简单的测试,每次消耗时间大约为0.3秒左右,但从数字上看来这个值并不是不可接受,但在高访问量的环境下,必会给服务器造成巨大的竞争。我们希望在高访问下,每个连接都能够发挥其最大价值,在该连接存在的情况下处理更多的问题,把每次开启关闭操作代价降到最低。
之前有过线程池操作,同理,连接池也是将其放入一个容器中,其中保存了多个连接,当我们需要使用时调出,在使用完毕后归还。虽然其长久存在占据了一部分内存空间,但减少了多次开启的时间消耗。
线程池的思路简单。我们需要找到一个存储connection的容器,这个容器应该具有这样几个特点。
- 存取方便,消耗小
- 能够动态扩容
- 能保持内存消耗更小
这样看来,LinkedList是一个非常适合的选项,存取快捷,并能从头部删除从尾部添加。当我们要实现getConnection功能时,只需要通过linkedList中removeFirst方法,即可获取头部并将链表中头部删除。使用完成后通过将其归还,重新储存进LinkedList中。
这里值得注意的一点是,我们在使用时应该对close方法进行重写,这点在很多博客中都有提到,重写后的close并不进行连接的断开,而是将其归还给线程池。而在C3P0中,由于对connection进行了二次封装,所有调用本类的close方法可以实现归还,而调用父类close方法则能够彻底断开连接。这是值得注意的一个地方。
package DataUtils;
import java.sql.Connection;
import java.util.LinkedList;
/*
* 该类为JDBC连接池,通过配置文件加载完成后,直接从池中获取连接即可
* 按照C3P0的设计想法,模仿其设计方式,重点为close方法的重写已经对于
* 多余连接的关闭和自动创建新链接的使用.我们使用LinkedList来作为连接池的容器
*/
public class JDBCConnectionPool {
private LinkedList<Connection> can = new LinkedList<>();
/*
*首先获得数据库连接池的数据库资源,通过静态代码块进行配置
*在该类进行初始化时即可完成加载,提高执行效率
*/
private static Source dataSource;
static {
/*
* 通过静态代码块加载数据库资源,为连接池
* 之后的获取提供帮助
*/
dataSource = new Source();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setPassword("jdbc:mysql://127.0.0.1/userinformation");
dataSource.setUserName("root");
dataSource.setPassword("root");
}
/*
* 连接池数量计数器
*/
private int connectionCounter = 0;//初始状态为0
/*
* 初始连接数量,创建开始时池内链接数量
* 默认连接池内连接数量为5个,可以通过初始化构造器以及Set方法设定该容量
*/
private int initialConnectionNum = 5 ;
/*
* 连接池连接最小闲置数量,当到达改数量时连接池将自动扩容
*/
private int MinConnectionNum = 2;
/*
* 连接池汇总最大连接数量,当到达该值后,将不再创建新的连接,
* 同时通过返回值提交信息。
*/
private int MaxConnectionNum = 10;
/*
* 数据库配置文件读取地址
* 数据库连接驱动
* 数据库账号密码
*/
//private String configFileUrl;
private String DataDriver;
private String userName;
private String passWord;
private String DataSourceUrl;
public JDBCConnectionPool() {
//空参数构造器,可以通过直接创建后进行set的设置方法进行设置
/*
* 初始化连接池内连接
* 在创建完成时内部含有五个连接
*/
for(int i = 0; i< initialConnectionNum; i++) {
Connection con = dataSource.getConnection();
can.add(con);
}
}
public JDBCConnectionPool(String DataDriver,String userName,String passWord,String DataSourceUrl) {
this.DataDriver = DataDriver;
this.userName = userName;
this.passWord = passWord;
this.DataSourceUrl = DataSourceUrl;
}
/*
* 获取连接
*/
public Connection getConnection() {
/*
* 在每次获取资源时,
*/
if(can.size() < MinConnectionNum) {
for(int i = 0;i<MinConnectionNum;i++) {
Connection con = dataSource.getConnection();
can.add(con);
}
}
/*
* 该操作即为从链表中获取链接
*/
Connection con = null;
if(con == null) {
//当连接为空时,我们从链表头部取出一个连接并将链表头部删除
con = can.removeFirst();
return con;}
else {
//当链表不为空时,直接返回连接
return con;
}
}
/*
* close方法能够返还资源并存入LinkedList中
*
*/
public void close() {
//对于二次封装的connection类中的close方法进行重新
/*
* 执行该方法的类应为connection,对其方法进行重写即可完成要求
* 将连接重新放入链表中,此时出现在链表尾部,这样相当于将连接返回
* 下次再获得时,将得到头部连接
*/
}
/*
* 对于连接池初始化的各种方法
*/
public void setDataDriver(String DataDriver) {
this.DataDriver = DataDriver;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setPassword(String password) {
passWord = password;
}
/*
* 设置最大连接数等属性的各种方法
*/
public void setInitialConnectionNum(int InitialConnectionNum) {
initialConnectionNum = InitialConnectionNum;
}
public void setMinConnectionNum(int MinConnectionNum) {
this.MinConnectionNum = MinConnectionNum;
}
public void setMaxConnectionNum(int MaxConnectionNum) {
this.MaxConnectionNum = MaxConnectionNum;
}
}
以上是代码实现,通过static代码块的形式,在类加载时便对数据库资源完成初始化,实际上这一步也可以在申请connection时完成。其中并没有太难的操作,更多的是把思路理解清楚即可。
close操作
public void close(){
//调用该方法的应为connection,所有使用this关键字
//直接将调用者存储进linkedList即可完成归还
can.add(this);
}