全面掌握apache-commons-dbcp之二:核心功能的設(shè)計(jì)實(shí)現(xiàn)

前言

在上一篇文章中,我們結(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)至少具備以下功能:

  1. 連接池要保存一組java.sql.Connection對(duì)象;
  2. 連接池應(yīng)中保存的connection對(duì)象個(gè)數(shù)應(yīng)該在可控的范圍之內(nèi);
  3. 連接池要便于使用,能夠靈活替換。

那究竟要如何實(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)系,如下圖所示:


dbcp內(nèi)部方法的關(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í)如此。


connectionPool與poolableConnectionFactory嵌套依賴

說實(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 源碼解析,有興趣的可以去看看,在這我就不贅述了。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 Apache-Commons-DBCP是數(shù)據(jù)庫(kù)連接池中一款優(yōu)秀的產(chǎn)品,熟悉dbcp同學(xué)都知道,dbcp底層“...
    許da廣閱讀 2,773評(píng)論 1 6
  • 本文包括傳統(tǒng)JDBC的缺點(diǎn)連接池原理自定義連接池開源數(shù)據(jù)庫(kù)連接池DBCP連接池C3P0連接池Tomcat內(nèi)置連接池...
    廖少少閱讀 16,932評(píng)論 0 37
  • JDBC概述 在Java中,數(shù)據(jù)庫(kù)存取技術(shù)可分為如下幾類:JDBC直接訪問數(shù)據(jù)庫(kù)、JDO技術(shù)、第三方O/R工具,如...
    usopp閱讀 3,630評(píng)論 3 75
  • 排骨要增肥__閱讀 253評(píng)論 0 0
  • 我叫輕輕,今年二十一歲,生活在西安。西安是陜西的省會(huì)城市,經(jīng)濟(jì)挺發(fā)達(dá)的,對(duì)我來(lái)講,它和上海、北京一樣好。但是西安不...
    _喝醉了的風(fēng)閱讀 328評(píng)論 0 0

友情鏈接更多精彩內(nèi)容