DBCP数据源连接池实现原理分析

时间:2023-03-08 17:54:41

前些天在调试公司系统的时候发现这样的一个问题:mysql数据库服务停止一段时间后再次重启后吗,tomcat服务无法请求数据库服务,调试了半天对这个问题进行定位解决,期间也搞了很多有关mysql数据库的知识,包括数据库连接池的问题,以前没有遇到问题的时候只知道数据库连接池这个概念和如何配置,但是当遇到问题的时候就要去看怎么实现了,比如很简单的默认的数据库连接池的个数是多少呢,我相信没有看过源代码的是不知道的,答案是8.下面就针对最近学习的org.apache.commons.dbcp.BasicDataSource这个数据源的连接池做一个分享吧。

分享之前有关数据库连接池的一些概念性的问题就不解释,可以参考http://www.cnblogs.com/duanxiaojun/p/5413502.html

下面我们以一个完成的数据库操作来分析详细的dbcp数据源连接池实现的原理:

 package cn.com.chnsys.dbcpDataSource;

 import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.dbcp.BasicDataSource;
/**
*
* <p>dbcp数据源连接池分析</p>
*
* 类说明
*
* @author duanxj
* @version 1.0
*/
public class BasicDataSourceExample { public static void main(String[] args) {
//设置数据源基本配置项
DataSource dataSource = setupDataSource(args[0]);
//创建连接
Connection conn = null;
Statement stmt = null;
ResultSet rset = null;
try {
//创建连接对象
conn = dataSource.getConnection();
//创建Statement 对象,这里我们使用Statement prepareStatement也是一样的
stmt = conn.createStatement();
//创建结果返回集
rset = stmt.executeQuery(args[1]);
//得到查询影响记录数
int numcols = rset.getMetaData().getColumnCount();
while(rset.next()) {
for(int i=1;i<=numcols;i++) {
System.out.print("\t" + rset.getString(i));
}
System.out.println("");
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
try { if (rset != null) rset.close(); } catch(Exception e) { }
try { if (stmt != null) stmt.close(); } catch(Exception e) { }
try { if (conn != null) conn.close(); } catch(Exception e) { }
}
} /**
* 创建数据源,并设置数据源基本配置项
* @param connectURI
* @return
*/
public static DataSource setupDataSource(String connectURI) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver"); //设置驱动
ds.setUsername("root"); //设置用户名
ds.setPassword("root"); //设置密码
ds.setUrl(connectURI);//设置连接url
return ds;
}
/**
* 打印创建的数据源的配置项
* @param ds
*/
public static void printDataSourceStats(DataSource ds) {
BasicDataSource bds = (BasicDataSource) ds;
System.out.println("NumActive: " + bds.getNumActive());
System.out.println("NumIdle: " + bds.getNumIdle());
}
/**
* 关闭销毁
* @param ds
* @throws SQLException
*/
public static void shutdownDataSource(DataSource ds) throws SQLException {
BasicDataSource bds = (BasicDataSource) ds;
bds.close();
}
}

上面的代码大家应该能看懂,我们只说其中比较关键的。首先哟啊创建一个dataSource,并对这个dataSource设置一些必须的配置项(数据库驱动,URL,用户名,密码)等,然后关键的操作是在dataSource.getConnection();处,我们查看dbcp源码的实现就可以知道,datasource的配置只是配置一些有关的配置信息,真正的创建连接池pool的操作是在用户第一次去获取connection的时候创建的,下面是源码getConnection()的实现:

public Connection getConnection()
throws SQLException
{
return createDataSource().getConnection();
}
protected synchronized DataSource createDataSource()
throws SQLException
{
if (this.closed) {
throw new SQLException("Data source is closed");
} if (this.dataSource != null) {
return this.dataSource;
} ConnectionFactory driverConnectionFactory = createConnectionFactory(); createConnectionPool(); GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null, -1, (byte)0, 0L, 1, this.maxOpenPreparedStatements);
} createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, this.abandonedConfig); createDataSourceInstance();
try
{
for (int i = 0; i < this.initialSize; i++)
this.connectionPool.addObject();
}
catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
} return this.dataSource;
}

上面的代码我做如下的分析:

首先我们看到这个createDataSource()方法是用synchronized修饰的我们知道这是线程安全的。

1  首先是在getConnection()方法中先是去创建一个createDataSource()。在createDataSource(),的方法中先判断该数据源是否关闭,如果关闭直接抛出异常,如果这个数据源已经创建成功则直接返回,否则去创建这个数据源,

2 创建数据源之前先创建数据库的连接工厂createConnectionFactory();在这个方法中主要是通过反射CLASS.FORNAME()创建一个数据库连接对象,其中涉及到的参数主要是driverClassName,url,validationQuery,username,password(对这些链接数据库的配置应该不陌生,这些参数的作用就是创建一个连接工厂)。

ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, this.url, this.connectionProperties);并返回创建成功的连接工厂示例在下面创建连接池createConnectionPool使用。

3 下面是真正的创建连接池的核心部分createConnectionPool();我们先看一下这个方法的具体实现,这里的前提是我们已经创建成功一个与数据库的连接对象,

  protected void createConnectionPool()
{
GenericObjectPool gop;
GenericObjectPool gop;
if ((this.abandonedConfig != null) && (this.abandonedConfig.getRemoveAbandoned())) {
gop = new AbandonedObjectPool(null, this.abandonedConfig);
}
else {
gop = new GenericObjectPool();
}
gop.setMaxActive(this.maxActive);
gop.setMaxIdle(this.maxIdle);
gop.setMinIdle(this.minIdle);
gop.setMaxWait(this.maxWait);
gop.setTestOnBorrow(this.testOnBorrow);
gop.setTestOnReturn(this.testOnReturn);
gop.setTimeBetweenEvictionRunsMillis(this.timeBetweenEvictionRunsMillis);
gop.setNumTestsPerEvictionRun(this.numTestsPerEvictionRun);
gop.setMinEvictableIdleTimeMillis(this.minEvictableIdleTimeMillis);
gop.setTestWhileIdle(this.testWhileIdle);
this.connectionPool = gop;
}

在源码中我们可以看到使用了GenericObjectPool对象,这个是使用commons-pool的GenericObjectPool 实现的,创建一个GenericObjectPool对象并设置连接池的配置参数信息,(这里知道我们再配置文件中配置的选项是如何在源码中起作用的了吧~~)。然后返回这个GenericObjectPool(这个commons-pool的GenericObjectPool的源码后续我们再分析,大家只要知道这里是使用的commons-pool的GenericObjectPool创建了一个池 )。

3.1 创建成功  this.connectionPool后

就是创建GenericKeyedObjectPoolFactory(暂时不知道这个是个什么东东?????)

// 设置连接池工厂  createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);

// 建立数据库连接池实例   createDataSourceInstance();

然后是根据我们配置文件中配置的初始的数据库连接池的大小去设置连接池的初始个数

// 根据配置,初始化建立一些数据库连接  =

try {

for (int i = 0 ; i < initialSize ; i++) {

connectionPool.addObject();

}

} catch (Exception e) {

throw new SQLNestedException("Error preloading the connection pool", e);

}

这样一个完成的数据源dataSource和连接池就创建完成了。