创建数据库连接是一个比较消耗性能的操作,同时在并发量较大的情况下创建过多的连接对服务器形成巨大的压力。对于资源的频繁分配﹑释放所造成的问题,使用连接池技术是一种比较好的解决方式。

在Java中,连接池已经有很多开源实现了,在这里使用commons-dbcp2这个包来

创建JDBC连接池:

public final class JDBCUtil{
private static DataSource myDataSource=null;
private JDBCUtil(){
}
static {
try{
Properties pro=new Properties();
InputStream is=JDBCUtil.class.getClassLoader().getResourceAsStream("mysqlPoolConf.properties");
pro.load(is);
myDataSource=BasicDataSourceFactory.createDataSource(pro);
}
catch(Exception e){
e.printStackTrace();
}
}
public static Connection getConnection()throws SQLException{
return myDataSource.getConnection();
}
public static void close(Connection conn){
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

使用很简单:

(1)    创建DataSource对象实例myDataSource

(2)    通过myDataSource的getConnection()方法获得数据库连接并使用

(3)    用完后调用连接close()方法来归还连接。

当然,这人我们不禁心存疑问,调用连接close()方法不是不是将连接关闭了吗?那连接回到连接池又是怎么实现的?一起来跟着源码看看连接池的实现过程:

首先是连接池配置文件mysqlPoolConf.properties:

为连接池的一些属性配置,这里只列举了一些常用的:

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://172.16.20.242:3306/test?characterEncoding=utf8
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=20
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#最大连接数量
maxActive=100
#是否在自动回收超时连接的时候打印连接的超时错误
logAbandoned=true
#是否自动回收超时连接
removeAbandoned=true
#超时时间(以秒数为单位)
#设置超时时间有一个要注意的地方,超时时间=现在的时间-程序中创建Connection的时间,如果 maxActive比较大,比如超过100,那么removeAbandonedTimeout可以设置长一点比如180,也就是三分钟无响应的连接进行回收,当然应用的不同设置长度也不同。
removeAbandonedTimeout=180
#<!-- 超时等待时间以毫秒为单位 -->
#maxWait代表当Connection用尽了,多久之后进行回收丢失连接
maxWait=1000

跟随源码看看连接池的实现逻辑:

BasicDataSourceFactory为一个工厂类,在调用它的方法createDataSource(Properties properties)时,通过createDataSource()创建了一个PoolingDataSource对象的实例

createDataSource()源码如下,我在源码中加了一部分注释:

protected DataSource createDataSource() throws SQLException {
if (closed) {
throw new SQLException("Data source is closed");
}
// Return the pool if we have already created it
// This is double-checked locking. This is safe since dataSource is
// volatile and the code is targeted at Java 5 onwards.
if (dataSource != null) {//检查dataSource是否已经关闭
return dataSource;
}
synchronized (this) {//同步代码块,避免多个线程来同时创建连接池
//如果dataSource已经创建,直接返回, 由于同步代码块需要获得锁,
//可能其他线程已经创建了dataSource,再释放锁,
//因此,在获得锁之后还需要再判断一下,这一点在平时编程中容易忽略。
if (dataSource != null) {
return dataSource;
}
jmxRegister();//jmx注册,不影响整体流程
// create factory which returns raw physical connections
//调用createConnectionFactory()创建JDBC连接工厂driverConnectionFactory,这个工厂使用数据库驱动来创建最底层的JDBC连接(即物理连接)
ConnectionFactory driverConnectionFactory = createConnectionFactory();
// Set up the poolable connection factory
//调用createConnectionPool()创建数据源使用的连接池,连接池顾名思义就是缓存JDBC连接的地方。
boolean success = false;
PoolableConnectionFactory poolableConnectionFactory;//连接池工厂,保存了一系列的状态信息 和 JDBC连接工厂
try {
poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
poolableConnectionFactory.setMaxOpenPrepatedStatements(maxOpenPreparedStatements);
success = true;
} catch (SQLException se) {
throw se;
} catch (RuntimeException rte) {
throw rte;
} catch (Exception ex) {
throw new SQLException("Error creating connection factory", ex);
}
if (success) {
// create a pool for our connections
//创建 GenericObjectPool<PoolableConnection>类的实例
//GenericObjectPool是apach-commons-pool包的一个通用对象池类,其构造函数参数是一个对象工厂实例
createConnectionPool(poolableConnectionFactory);
}
// Create the pooling data source to manage connections
DataSource newDataSource;
success = false;
try {
newDataSource = createDataSourceInstance();//创建了一个PoolingDataSource的实例
newDataSource.setLogWriter(logWriter);
success = true;
} catch (SQLException se) {
throw se;
} catch (RuntimeException rte) {
throw rte;
} catch (Exception ex) {
throw new SQLException("Error creating datasource", ex);
} finally {
if (!success) {
closeConnectionPool();
}
}
// If initialSize > 0, preload the pool
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();//初始化initialSize个JDBC连接
}
} catch (Exception e) {
closeConnectionPool();
throw new SQLException("Error preloading the connection pool", e);
}
// If timeBetweenEvictionRunsMillis > 0, start the pool's evictor task
startPoolMaintenance();
dataSource = newDataSource;
return dataSource;
}
}

在这一步创建了一个池化连接类工厂的实例,真正的连接是由这个工厂创建并放入连接池,GenericObjectPool是连接池的具体管理者,它是在commons-pool2这个包中实现的一个通用的对象池,GenericObjectPool负责连接池的管理(包括初始化连接,将多余的空闲连接关闭等)。之后,在使用需要使用数据库的Connection的时候,通过PoolingDataSource的getConnection() 方法获得一个连接,getConnection()方法实现如下:

public Connection getConnection() throws SQLException {
try {
//_pool是在BasicDataSource创建的GenericObjectPool<PoolableConnection> connectionPool这个实例
C conn = _pool.borrowObject();//从JDBC连接池中获得一个JDBC连接
if (conn == null) {
return null;
}
//PoolGuardConnectionWrapper是DBCP对JDBC Connection这个类的一个封装
return new PoolGuardConnectionWrapper<>(conn);
} catch(SQLException e) {
throw e;
} catch(NoSuchElementException e) {
throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLException("Cannot get a connection, general error", e);
}
}

_pool.borrowObject()得到是PoolableConnection对象的实例,其继承自DelegatingConnection。

PoolGuardConnectionWrapper继承了DelegatingConnection这个类且对父类并没有做太多的改动。DelegatingConnection实现了java.sql.Connection接口,createStatement(),prepareStatement(),commit(),rollback()这些方法和我们以往

通过DriverManager.getConnection()得到的Connection使用方法是一致的。

重点在调用colse()方法时,并不是真正的关闭了连接,而是对连接做了状态清理后将

通过GenericObjectPool的returnObject方法将其归还至连接池了,看看源码就明白了。

先看看DelegatingConnection这个类实现的close方法,主要是做了Statement和ResultSet的清理:

public void close() throws SQLException {
if (!_closed) {
closeInternal();
}
}
protected final void closeInternal() throws SQLException {
try {
passivate();
} finally {
if (_conn != null) {
try {
_conn.close();
} finally {
_closed = true;
}
} else {
_closed = true;
}
}
}
protected void passivate() throws SQLException {
// The JDBC spec requires that a Connection close any open
// Statement's when it is closed.
// DBCP-288. Not all the traced objects will be statements
//关闭 Statement and ResultSet
List<AbandonedTrace> traces = getTrace();
if(traces != null && traces.size() > 0) {
Iterator<AbandonedTrace> traceIter = traces.iterator();
while (traceIter.hasNext()) {
Object trace = traceIter.next();
if (trace instanceof Statement) {
((Statement) trace).close();
} else if (trace instanceof ResultSet) {
// DBCP-265: Need to close the result sets that are
// generated via DatabaseMetaData
((ResultSet) trace).close();
}
}
clearTrace();
}
setLastUsed(0);
}

PoolableConnection对close()方法进行了复写:

/**
* Returns me to my pool.
*/
@Override
public synchronized void close() throws SQLException {
if (isClosedInternal()) {
// already closed
return;
}
boolean isUnderlyingConectionClosed;
try {
isUnderlyingConectionClosed = getDelegateInternal().isClosed();
} catch (SQLException e) {
try {
_pool.invalidateObject(this);
} catch(IllegalStateException ise) {
passivate();//在这个方法内部还是调用的父类DelegatingConnection的passivate()方法
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING the original exception will be rethrown
}
throw new SQLException("Cannot close connection (isClosed check failed)", e);
} if (isUnderlyingConectionClosed) {
try {
_pool.invalidateObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch (Exception e) {
throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
}
} else {
try {
_pool.returnObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLException("Cannot close connection (return to pool failed)", e);
}
}
}
@Override
protected void passivate()throws SQLException {
super.passivate();
setClosedInternal(true);
}

基本逻辑是调用父类的passivate()方法做状态清理,然后将连接归还给连接池。因此,回到本篇开始的地方,调用close()方法是不会关闭该连接的。

最后,DBCP这个包大致的框架结构如下

主要有以下几点:

1、 实现池化连接对象PoolableConnection,这个即是我们操作数据的连接对象

2、 池化连接对象工厂类PoolableConnectionFactory,池化连接对象的创建工厂

3、 BasicDataSource工厂类BasicDataSourceFactory,用以创建PoolingDataSource类的实例

4、 PoolingDataSource使用PoolableConnectionFactory创建池化连接,并使用GenericObjectPool作为池对象的管理者。

5、 由于一个connection可以对应多个statement,PoolableConnection内部使用KeyedObjectPool来管理这些statement。

个人理解,不足或者错误之处敬请指出~~~

JDBC线程池创建与DBCP源码阅读的更多相关文章

  1. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  2. 线程池 ThreadPoolExecutor 类的源码解析

    线程池 ThreadPoolExecutor 类的源码解析: 1:数据结构的分析: private final BlockingQueue<Runnable> workQueue;  // ...

  3. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

  4. 线程池 ThreadPoolExecutor 原理及源码笔记

    前言 前面在学习 JUC 源码时,很多代码举例中都使用了线程池 ThreadPoolExecutor,并且在工作中也经常用到线程池,所以现在就一步一步看看,线程池的源码,了解其背后的核心原理. 公众号 ...

  5. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  6. Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明

    Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 作者: Grey 原文地址: 博客园:Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明 C ...

  7. 理解线程池到走进dubbo源码

    引言 合理利用线程池能够带来三个好处. ​ 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. ​ 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. ...

  8. 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)

    前两篇文章讲了线程池的源码分析,再来看这篇文章就比较简单了, 本文主要讲解 Executors 这个工具类,看看长江创建线程池的几种方法. newFixedThreadPool 生成一个固定大小的线程 ...

  9. 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)

    史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构:    先简单说说这个继承结构,E ...

随机推荐

  1. 使用LINGO来解决0/1背包算法问题

    1.问题说明 0/1背包问题:我们有n种物品,物品j的重量为wj,价格为pj.我们假定所有物品的重量和价格都是非负的.背包所能承受的最大重量为W.如果限定每种物品只能选择0个或1个,则问题称为0-1背 ...

  2. CF 472 div1 D. Contact ATC

    #include <algorithm> #include <cmath> #include <cstdio> #include <cstring> # ...

  3. SpringBoot: 配置加载顺序

    在应用程序中各种配置是不可避免的,Spring中对配置的抽象(Environment)可以说是达到了极致,其中有一项尤为突出:PropertySource(配置来源),配置来源通常包括命令行参数,系统 ...

  4. HTML笔记04---计时事件

    JavaScript运动01 计时事件 1.语法:var t=setTimeout("javascript语句",毫秒); setTimeout() 方法会返回某个值.在上面的语句 ...

  5. Python网络爬虫笔记(五):下载、分析京东P20销售数据

    (一)  分析网页 下载下面这个链接的销售数据 https://item.jd.com/6733026.html#comment 1.      翻页的时候,谷歌F12的Network页签可以看到下面 ...

  6. 初学servlet之form表单

    使用doget向页面写表单,使用dopost读取表单内容,包括了单选按钮,复选框,下拉列表等等的读取方法,form的action属性不写,默认表单会被提交给请求他时用的相同的URL package a ...

  7. 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池)

    一.本文产生原由: 之前文章<总结消息队列RabbitMQ的基本用法>已对RabbitMQ的安装.用法都做了详细说明,而本文主要是针对在高并发且单次从RabbitMQ中消费消息时,出现了连 ...

  8. (MariaDB/MySQL)MyISAM存储引擎读、写操作的优先级

    MariaDB/MySQL中使用表级锁的存储引擎(例如MyISAM.Aria(MariaDB对MyISAM引擎的改进,前身是MyISAM))在读(select).写操作(insert.delete.u ...

  9. [SDOI2005]反素数

    题目描述 对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4. 如果某个正整数x满足:g(x)>g(i) 0<i<x,则称x为反质数.例如,整数1,2,4,6 ...

  10. hdu 5869 区间不同GCD个数(树状数组)

    Different GCD Subarray Query Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K ( ...