前言
在上一篇文章中,我們結(jié)合實(shí)際情況,詳細(xì)的講解了如何使用dbcp,但是,想要深入了解這款產(chǎn)品,學(xué)會(huì)使用僅僅是第一步。本篇我們就更進(jìn)一步,在學(xué)會(huì)使用的基礎(chǔ)上,結(jié)合源碼,更加深入的了解dbcp的工作原理和設(shè)計(jì)思路。
通用的核心功能與設(shè)計(jì)思路
在仔細(xì)分析dbcp的工作原理之前,我想先花點(diǎn)時(shí)間說一下數(shù)據(jù)庫(kù)連接池的通用核心功能和設(shè)計(jì)思路。數(shù)據(jù)庫(kù)連接池和RDBMS(關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng))類似,核心功能與設(shè)計(jì)思路這兩塊東西基本都是通用的,同個(gè)產(chǎn)品的不同版本之間的差異,以及不同產(chǎn)品之間的差異,主要在構(gòu)架設(shè)計(jì)和實(shí)現(xiàn)質(zhì)量上。把這兩項(xiàng)搞清楚再去進(jìn)行源碼分析,會(huì)有一種了然于胸的感覺,對(duì)于理解和學(xué)習(xí)作者們的思路和架構(gòu)技巧,有非常大的幫助。
那一個(gè)數(shù)據(jù)庫(kù)連接池的核心功能是什么呢?在回答這個(gè)問題之前讓我們先回想一下,假如我們不使用dbcp,要完成一次數(shù)據(jù)庫(kù)的操作需要哪些步驟呢?有熟悉JDBC編程的同學(xué)可能很快就能寫出下面這些代碼:
public static void main(String[] args) {
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
String url = "jdbc:sqlserver://127.0.0.1:1433;DatabaseName=******";
String username = "***";
String password = "***";
String sql = "select * from table where ***";
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
Class.forName(driverName);
// 使用DriverManager屬于一種非常古老的寫法,但好處是足夠通用
// DBCP提供了PoolingDriver類,用來(lái)支持此種寫法
// conn = DriverManager.getConnection(url, username, password);
// Java 1.4 之后,可以選擇使用由個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)提供的database接口的實(shí)現(xiàn),來(lái)獲取connection
OracleConnectionPoolDataSource dataSource = new OracleConnectionPoolDataSource();
dataSource.setURL(url);
dataSource.setUser(username);
dataSource.setPassword(password)
Connection conn = dataSource.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while (rs.next()) {
System.out.println(rs.getInt("id"));
}
rs.close();
stmt.close();
conn.close();
}
如果我們拿之前使用了dbcp的代碼來(lái)和上面那段代碼對(duì)比的話,會(huì)發(fā)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)連接池應(yīng)至少具備以下功能:
- 連接池要保存一組java.sql.Connection對(duì)象;
- 連接池應(yīng)中保存的connection對(duì)象個(gè)數(shù)應(yīng)該在可控的范圍之內(nèi);
- 連接池要便于使用,能夠靈活替換。
那究竟要如何實(shí)現(xiàn)這些功能呢?讓我們對(duì)照著上面的功能來(lái)看。
- 要保存一組數(shù)據(jù)對(duì)象,最方便的方法,就是使用原生java.util.Collection及其子類,但是受限于他們的應(yīng)用場(chǎng)景和性能表現(xiàn),當(dāng)在高并發(fā)等嚴(yán)苛的系統(tǒng)的環(huán)境中時(shí),更多的設(shè)計(jì)者們會(huì)選擇自己設(shè)計(jì)實(shí)現(xiàn)Collection。
- 要實(shí)現(xiàn)“可控”,連接池就應(yīng)該首先具備創(chuàng)建和銷毀池中對(duì)象的能力,然后提供一組配置項(xiàng)給用戶(最多能夠容納的個(gè)數(shù),最小容納的個(gè)數(shù)等),讓用戶按照自己的意愿來(lái)確定連接池的邊界,最后連接池按照配置,動(dòng)態(tài)的維護(hù)池中的數(shù)據(jù)。
- 一個(gè)數(shù)據(jù)庫(kù)連接池對(duì)外提供服務(wù)的類,應(yīng)該是javax.sql.DataSource的實(shí)現(xiàn),這樣就可以方便的用戶替換不同的數(shù)據(jù)源;同時(shí)它內(nèi)部維護(hù)的connection對(duì)象,應(yīng)該重寫原生物理connection對(duì)的close()方法,保證connection資源的回收動(dòng)作,完全交由連接池操作。
dbcp的設(shè)計(jì)實(shí)現(xiàn)
在介紹了數(shù)據(jù)庫(kù)連接池的基本功能和設(shè)計(jì)思路之后,我們就緊接著來(lái)看看,dbcp是如何實(shí)現(xiàn)上述功能的。
功能1、功能2
在我們閱讀源碼的過程中,我們發(fā)現(xiàn),dbcp將功能1與功能2完全委托給Apache Commons Pool去實(shí)現(xiàn),關(guān)于pool,我在另一篇文章中有詳細(xì)的介紹,這里對(duì)照著上面說的功能點(diǎn),再給大家說道說道。先說功能1,pool用來(lái)存儲(chǔ)對(duì)象的容器為CursorableLinkedList,它實(shí)現(xiàn)了List接口,由Apache Commons Collections(version 3.1)提供,它最大的特點(diǎn)就是提供了一個(gè)ListIterator(集合迭代器)對(duì)象,允許實(shí)時(shí)修改其內(nèi)部list的數(shù)據(jù)。對(duì)于個(gè)功能2,pool提供了GenericObjectPool類來(lái)實(shí)現(xiàn)對(duì)池中對(duì)象的控制,在前上一篇文章中,我們說道dbcp配置項(xiàng)中的有池的屬性,以及高可用等屬性,這些配置,實(shí)際上就是用來(lái)創(chuàng)建GenericObjectPool對(duì)象用的,這些配置就是GenericObjectPool的配置。另外,GenericObjectPool要求用戶必須提供一個(gè)實(shí)現(xiàn)了org.apache.commons.pool.PoolableObjectFactory接口工廠類,看這個(gè)接口提供的方法,
// 創(chuàng)建一個(gè)對(duì)象
org.apache.commons.pool.PoolableObjectFactory.makeObject()
// 銷毀一個(gè)對(duì)象
org.apache.commons.pool.PoolableObjectFactory.destroyObject(Object)
// 校驗(yàn)一個(gè)對(duì)象
org.apache.commons.pool.PoolableObjectFactory.validateObject(Object)
// 激活一個(gè)對(duì)象
org.apache.commons.pool.PoolableObjectFactory.activateObject(Object)
// 取消激活一個(gè)對(duì)象
org.apache.commons.pool.PoolableObjectFactory.passivateObject(Object)
我們就知道這個(gè)工廠類的作用了。
功能3
真正由dbcp自己實(shí)現(xiàn)的,其實(shí)只有功能3。那我們就先來(lái)看看,dbcp對(duì)外提供服務(wù)的BasicDataSource(org.apache.commons.dbcp.BasicDataSource)類究竟是如何設(shè)計(jì)實(shí)現(xiàn)的。按照內(nèi)部對(duì)象的類型,我將整個(gè)類分為三塊去分析。
內(nèi)部屬性
最開始我們使用dbcp的時(shí)候,就接觸到這些屬性,在本系列的第一篇文章中,我對(duì)這些屬性做了分類并分別介紹了它們的用法,而且我們也知道,這些屬性除了構(gòu)造一個(gè)“物理連接”時(shí)用到的屬性,都是用來(lái)創(chuàng)建GenericObjectPool對(duì)象的,所以這里就不重復(fù)的介紹了。-
接口方法實(shí)現(xiàn):看完這個(gè)類的屬性,就要看它接口方法的實(shí)現(xiàn)了。
- DataSource:getConnection(String, String):調(diào)用這個(gè)方法時(shí)直接拋UnsupportedOperationException,BasicDataSource不支持此類調(diào)用
- DataSource:getConnection():只有一行代碼
createDataSource().getConnection();
createDataSource是內(nèi)部方法,我們也自然而然,將目光對(duì)準(zhǔn)了這些內(nèi)部方法。
- 內(nèi)部方法:dbcp主要由下面幾個(gè)內(nèi)部方法:
- createDataSource()
- createConnectionFactory()
- createConnectionPool()
- createDataSourceInstance()
- createPoolableConnectionFactory(ConnectionFactory, KeyedObjectPoolFactory, AbandonedConfig)
這幾個(gè)方法的關(guān)系,如下圖所示:

只看圖可能比較抽象,我還是結(jié)合代碼來(lái)為大家講解,createDataSource()的源碼如下
protected synchronized DataSource createDataSource()
throws SQLException {
// 首先判斷容器(連接池)是否關(guān)閉,如果關(guān)閉的話直接拋異常
// closed是一個(gè)內(nèi)部屬性
if (closed) {
throw new SQLException("Data source is closed");
}
// 如果已經(jīng)創(chuàng)建了dataSource,就直接返回,這個(gè)dataSource也是內(nèi)部屬性對(duì)象
if (dataSource != null) {
return (dataSource);
}
// 首先創(chuàng)建一個(gè)獲得數(shù)據(jù)庫(kù)物理連接的工廠類
ConnectionFactory driverConnectionFactory = createConnectionFactory();
// 創(chuàng)建連接池
// 方法內(nèi)部的關(guān)鍵代碼為:
// GenericObjectPool gop = new GenericObjectPool();
// connectionPool = gop;
// GenericObjectPool是apache.commons.pool中的組件
// 注意:此時(shí)這個(gè)connectionPool還無(wú)法創(chuàng)建數(shù)據(jù)對(duì)象
createConnectionPool();
// 根據(jù)配置,判斷是否需要?jiǎng)?chuàng)建一個(gè)statement的pool工廠
// 最終這個(gè)工廠類將會(huì)委派給本地的connectionPool對(duì)象
// 如果poolPreparedStatements=true
// connectionPool創(chuàng)建一個(gè)connection時(shí),會(huì)同時(shí)創(chuàng)建一個(gè)statement的池
GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
-1, // unlimited maxActive (per key)
GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
0, // maxWait
1, // maxIdle (per key)
maxOpenPreparedStatements);
}
// 創(chuàng)建一個(gè)數(shù)據(jù)對(duì)象的工廠給connectionPool,讓connectionPool可以創(chuàng)建數(shù)據(jù)庫(kù)連接
createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);
// 創(chuàng)建內(nèi)部對(duì)象dataSource,由它來(lái)維護(hù)connectionPool對(duì)象
// 這個(gè)內(nèi)部的dataSource對(duì)象為PoolingDataSource的實(shí)例,其內(nèi)部對(duì)象_pool是指向connectionPool的引用
createDataSourceInstance();
// 根據(jù)創(chuàng)建的配置“蓄池”
try {
for (int i = 0 ; i < initialSize ; i++) {
// 使用數(shù)據(jù)對(duì)象工廠,創(chuàng)建物理數(shù)據(jù)庫(kù)連接并交由connectionPool維護(hù)
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
// 返回內(nèi)部dataSource對(duì)象
return dataSource;
整個(gè)方法的核心是在createPoolableConnectionFactory(ConnectionFactory, KeyedObjectPoolFactory, AbandonedConfig),這個(gè)方法的主體就是調(diào)用構(gòu)造方法,創(chuàng)建一個(gè)PoolableConnectionFactory對(duì)象,這個(gè)工廠對(duì)象,就是交給GenericObjectPool對(duì)象,管理和維護(hù)池中數(shù)據(jù)用的。
protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory,
KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration) throws SQLException {
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory =
new PoolableConnectionFactory(driverConnectionFactory,
connectionPool,
statementPoolFactory,
validationQuery,
validationQueryTimeout,
connectionInitSqls,
defaultReadOnly,
defaultAutoCommit,
defaultTransactionIsolation,
defaultCatalog,
configuration);
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
}
}
要注意這個(gè)PoolableConnectionFactory的構(gòu)造方法
public PoolableConnectionFactory(
ConnectionFactory connFactory,
ObjectPool pool,
KeyedObjectPoolFactory stmtPoolFactory,
String validationQuery,
int validationQueryTimeout,
Collection connectionInitSqls,
Boolean defaultReadOnly,
boolean defaultAutoCommit,
int defaultTransactionIsolation,
String defaultCatalog,
AbandonedConfig config) {
// 創(chuàng)建數(shù)據(jù)庫(kù)物理連接的工廠
_connFactory = connFactory;
// 指向connectionPool的引用
_pool = pool;
_config = config;
_pool.setFactory(this);
_stmtPoolFactory = stmtPoolFactory;
_validationQuery = validationQuery;
_validationQueryTimeout = validationQueryTimeout;
_connectionInitSqls = connectionInitSqls;
_defaultReadOnly = defaultReadOnly;
_defaultAutoCommit = defaultAutoCommit;
_defaultTransactionIsolation = defaultTransactionIsolation;
_defaultCatalog = defaultCatalog;
}
它將connectionPool作為它自己的內(nèi)部對(duì)象,同時(shí)又將自己作為內(nèi)部對(duì)象,保存到connectionPool里(調(diào)用 _pool.setFactory(this);),在整個(gè)BasicDataSource的生命周期中,connectionPool是一直存在的,這樣就導(dǎo)致connectionPool和poolableConnectionFactory對(duì)象彼此嵌套依賴,為了印證我的想法,我特意斷點(diǎn)跟蹤了一下,發(fā)現(xiàn)實(shí)際情況也確實(shí)如此。

說實(shí)話我不是很認(rèn)可這種設(shè)計(jì),也可能是我功力不夠,所以這里先留個(gè)引子,后續(xù)如果對(duì)這塊的設(shè)計(jì)有更好的理解,再來(lái)更新。
至此,功能3還僅實(shí)現(xiàn)了一半,另一半功能由PoolableConnectionFactory實(shí)現(xiàn)。讓我們來(lái)看看,由PoolableConnectionFactory創(chuàng)建出來(lái)的connection對(duì)象,相對(duì)于物理connection對(duì)象,有哪些不同。
public Object makeObject() throws Exception {
Connection conn = _connFactory.createConnection();
....
return new PoolableConnection(conn,_pool,_config);
}
可以看到,PoolableConnectionFactory創(chuàng)建出來(lái)的是一個(gè)PoolableConnection對(duì)象的實(shí)例,它同時(shí)保留這物理數(shù)據(jù)庫(kù)連接對(duì)象的引用和連接池對(duì)象的引用。PoolableConnection的類圖如下,它重寫了close()方法
public synchronized void close() throws SQLException {
......
boolean isUnderlyingConectionClosed;
// 連接是否已經(jīng)關(guān)閉
isUnderlyingConectionClosed = _conn.isClosed();
if (!isUnderlyingConectionClosed) {
// 將這個(gè)連接還給池
_pool.returnObject(this);
} else {
// 如果這個(gè)物理連接實(shí)際上已經(jīng)關(guān)閉了的話,就不能在還給連接池了,而是應(yīng)該銷毀它
_pool.invalidateObject(this);
}
......
}
透過現(xiàn)象看本質(zhì)
至此,我們已經(jīng)基本理清楚dbcp對(duì)于其核心功能是如何實(shí)現(xiàn)的了,但這些“實(shí)現(xiàn)”還是有些太過流于表面,說白點(diǎn),我們僅僅是知其然,但不知其所以然,其實(shí)根本就沒有觸及到一個(gè)數(shù)據(jù)庫(kù)連接池的本質(zhì),那就是如何高效穩(wěn)定的處理并發(fā)訪問!dbcp在解決這個(gè)問題的時(shí)候,找了自己的好兄弟Apache Commons Pool,我之前有文章專門講解過,傳送門在這apache-commons-pool-1.5.4 源碼解析,有興趣的可以去看看,在這我就不贅述了。