MySql數據庫連接池專題


一、什么是數據庫連接池?

官方:數據庫連接池(Connection pooling)是程序啟動時建立足夠的數據庫連接,並將這些連接組成一個連接池,由程序動態地對池中的連接進行申請,使用,釋放。
個人理解:創建數據庫連接是一個很耗時的操作,也容易對數據庫造成安全隱患。所以,在程序初始化的時候,集中創建多個數據庫連接,並把他們集中管理,供程序使用,可以保證較快的數據庫讀寫速度,還更加安全可靠。

二、傳統的連接機制與數據庫連接池的運行機制區別

 傳統統鏈接:     一般來說,Java應用程序訪問數據庫的過程是:

  ①裝載數據庫驅動程序;

  ②通過JDBC建立數據庫連接;

  ③訪問數據庫,執行SQL語句;

  ④斷開數據庫連接。

使用了數據庫連接池的機制:
(1)  程序初始化時創建連接池
(2) 使用時向連接池申請可用連接
(3) 使用完畢,將連接返還給連接池
(4) 程序退出時,斷開所有連接,並釋放資源

 

一. 為何要使用數據庫連接池
假設網站一天有很大的訪問量,數據庫服務器就需要為每次連接創建一次數據庫連接,極大的浪費數據庫的資源,並且極易造成數據庫服務器內存溢出、拓機。
數據庫連接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤為突出.對數據庫連接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標.數據庫連接池正式針對這個問題提出來的.數據庫連接池負責分配,管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個

數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中, 這些數據庫連接的數量是由最小數據庫連接數來設定的.無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量.連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中.

      數據庫連接池的最小連接數和最大連接數的設置要考慮到以下幾個因素:

  1, 最小連接數:是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費.
  2, 最大連接數:是連接池能申請的最大連接數,如果數據庫連接請求超過次數,后面的數據庫連接請求將被加入到等待隊列中,這會影響以后的數據庫操作
  3, 如果最小連接數與最大連接數相差很大:那么最先連接請求將會獲利,之后超過最小連接數量的連接請求等價於建立一個新的數據庫連接.不過,這些大於最小連接數的數據庫連接在使用完不會馬上被釋放,他將被           放到連接池中等待重復使用或是空間超時后被釋放.

二、使用數據庫連接池的關鍵點

1、並發問題

  為了使連接管理服務具有最大的通用性,必須考慮多線程環境,即並發問題。這個問題相對比較好解決,因為各個語言自身提供了對並發管理的支持像java,c#等等,使用synchronized(java)lock(C#)關鍵字即可確保線程是同步的。使用方法可以參考,相關文獻。

2、事務處理

DB連接池必須要確保某一時間內一個 conn 只能分配給一個線程。不同 conn 的事務是相互獨立的。 

  我們知道,事務具有原子性,此時要求對數據庫的操作符合“ALL-ALL-NOTHING”原則,即對於一組SQL語句要么全做,要么全不做。 
  我們知道當2個線程共用一個連接Connection對象,而且各自都有自己的事務要處理時候,對於連接池是一個很頭疼的問題,因為即使Connection類提供了相應的事務支持,可是我們仍然不能確定那個數據庫操作是對應那個事務的,這是由於我們有2個線程都在進行事務操作而引起的。為此我們可以使用每一個事務獨占一個連接來實現,雖然這種方法有點浪費連接池資源但是可以大大降低事務管理的復雜性。 

 

3、連接池的分配與釋放

  連接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,可以提高連接的復用度,從而降低建立新連接的開銷,同時還可以加快用戶的訪問速度。 
  對於連接的管理可使用一個List。即把已經創建的連接都放入List中去統一管理。每當用戶請求一個連接時,系統檢查這個List中有沒有可以分配的連接。如果有就把那個最合適的連接分配給他(如何能找到最合適的連接文章將在關鍵議題中指出);如果沒有就拋出一個異常給用戶,List中連接是否可以被分配由一個線程來專門管理捎后我會介紹這個線程的具體實現。

4、連接池的配置與維護

  連接池中到底應該放置多少連接,才能使系統的性能最佳?系統可采取設置最小連接數(minConnection)和最大連接數(maxConnection)等參數來控制連接池中的連接。比方說,最小連接數是系統啟動時連接池所創建的連接數。如果創建過多,則系統啟動就慢,但創建后系統的響應速度會很快;如果創建過少,則系統啟動的很快,響應起來卻慢。這樣,可以在開發時,設置較小的最小連接數,開發起來會快,而在系統實際使用時設置較大的,因為這樣對訪問客戶來說速度會快些。最大連接數是連接池中允許連接的最大數目,具體設置多少,要看系統的訪問量,可通過軟件需求上得到。 
  如何確保連接池中的最小連接數呢?有動態和靜態兩種策略。動態即每隔一定時間就對連接池進行檢測,如果發現連接數量小於最小連接數,則補充相應數量的新連接,以保證連接池的正常運轉。靜態是發現空閑連接不夠時再去檢查。

 

三、使用數據庫連接池的優勢和其工作原理

1、連接池的優勢

連接池用於創建和管理數據庫連接的緩沖池技術,緩沖池中的連接可以被任何需要他們的線程使用。當一個線程需要用JDBC對一個數據庫操作時,將從池中請求一個連接。當這個連接使用完畢后,將返回到連接池中,等待為其他的線程服務。

連接池的主要優點有以下三個方面。

第一、減少連接創建時間。連接池中的連接是已准備好的、可重復使用的,獲取后可以直接訪問數據庫,因此減少了連接創建的次數和時間。

第二、簡化的編程模式。當使用連接池時,每一個單獨的線程能夠像創建一個自己的JDBC連接一樣操作,允許用戶直接使用JDBC編程技術。

第三、控制資源的使用。如果不使用連接池,每次訪問數據庫都需要創建一個連接,這樣系統的穩定性受系統連接需求影響很大,很容易產生資源浪費和高負載異常。連接池能夠使性能最大化,將資源利用控制在一定的水平之下。連接池能控制池中的連接數量,增強了系統在大量用戶應用時的穩定性。

2、連接池的工作原理

下面,簡單的闡述下連接池的工作原理。

連接池技術的核心思想是連接復用,通過建立一個數據庫連接池以及一套連接使用、分配和管理策略,使得該連接池中的連接可以得到高效、安全的復用,避免了數據庫連接頻繁建立、關閉的開銷。

連接池的工作原理主要由三部分組成,分別為連接池的建立、連接池中連接的使用管理、連接池的關閉。

第一、連接池的建立。一般在系統初始化時,連接池會根據系統配置建立,並在池中創建了幾個連接對象,以便使用時能從連接池中獲取。連接池中的連接不能隨意創建和關閉,這樣避免了連接隨意建立和關閉造成的系統開銷。Java中提供了很多容器類可以方便的構建連接池,例如Vector、Stack等。

第二、連接池的管理。連接池管理策略是連接池機制的核心,連接池內連接的分配和釋放對系統的性能有很大的影響。其管理策略是:

  • 當客戶請求數據庫連接時,首先查看連接池中是否有空閑連接,如果存在空閑連接,則將連接分配給客戶使用;如果沒有空閑連接,則查看當前所開的連接數是否已經達到最大連接數,如果沒達到就重新創建一個連接給請求的客戶;如果達到就按設定的最大等待時間進行等待,如果超出最大等待時間,則拋出異常給客戶。
  • 當客戶釋放數據庫連接時,先判斷該連接的引用次數是否超過了規定值,如果超過就從連接池中刪除該連接,否則保留為其他客戶服務。

    該策略保證了數據庫連接的有效復用,避免頻繁的建立、釋放連接所帶來的系統資源開銷。

第三、連接池的關閉。當應用程序退出時,關閉連接池中所有的連接,釋放連接池相關的資源,該過程正好與創建相反。

 3、常用的連接池:

     (1) dbcp
dbcp可能是使用最多的開源連接池,原因大概是因為配置方便,而且很多開源和tomcat應用例子都是使用的這個連接池吧。
這個連接池可以設置最大和最小連接,連接等待時間等,基本功能都有。這個連接池的配置參見附件壓縮包中的:dbcp.xml
使用評價:在具體項目應用中,發現此連接池的持續運行的穩定性還是可以,不過速度稍慢,在大並發量的壓力下穩定性
有所下降,此外不提供連接池監控

常用的參數(阿里面試問常用的參數):  


我們來看DBCP 的例子, 然后根據例子來分析:

#連接設置
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/day14 username=root password=abc #<!-- 初始化連接 --> initialSize=10 #最大連接數量 maxActive=50 #<!-- 最大空閑連接 --> maxIdle=20 #<!-- 最小空閑連接 --> minIdle=5 #<!-- 超時等待時間以毫秒為單位 60000毫秒/1000等於60秒 --> maxWait=60000 #JDBC驅動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;] #注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。 connectionProperties=useUnicode=true;characterEncoding=utf8 #指定由連接池所創建的連接的自動提交(auto-commit)狀態。 defaultAutoCommit=true #driver default 指定由連接池所創建的連接的只讀(read-only)狀態。 #如果沒有設置該值,則“setReadOnly”方法將不被調用。(某些驅動並不支持只讀模式,如:Informix) defaultReadOnly= #driver default 指定由連接池所創建的連接的事務級別(TransactionIsolation)。 #可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=REPEATABLE_READ DBCP配置文件

配置參數詳解:

MaxActive,連接池的最大數據庫連接數。設為0表示無限制。maxActive是最大激活連接數,這里取值為20,表示同時最多有20個數據庫連 
maxIdle 連接池中最多可空閑maxIdle個連接,maxIdle是最大的空閑連接數,這里取值為20,表示即使沒有數據庫連接時依然可以保持20空閑的連接,而不被清除,隨時處於待命狀態
minIdle 連接池中最少空閑maxIdle個連接 
initialSize 初始化連接數目 
maxWait 連接池中連接用完時,新的請求等待時間,毫秒 MaxWait是最大等待秒鍾數,這里取值-1,表示無限等待,直到超時為止,也可取值9000,表示9秒后超時。
maxIdle,最大空閑數,數據庫連接的最大空閑時間。超過空閑時間,數據庫連
接將被標記為不可用,然后被釋放。設為0表示無限制。

   (2) c3p0
c3p0是另外一個開源的連接池,在業界也是比較有名的,這個連接池可以設置最大和最小連接,連接等待時間等,基本功能都有。
這個連接池的配置參見附件壓縮包中的:c3p0.xml。
使用評價:在具體項目應用中,發現此連接池的持續運行的穩定性相當不錯,在大並發量的壓力下穩定性也有一定保證,
          此外不提供連接池監控。          

   1.Apache commons-dbcp 連接池

        下載:http://commons.apache.org/proper/commons-dbcp/ 

       2.c3p0 數據庫連接池

        下載:http://sourceforge.net/projects/c3p0/

程序開發過程中,存在很多問題:

首先,每一次web請求都要建立一次數據庫連接。建立連接是一個費時的活動,每次都得花費0.05s~1s的時間,而且系統還要分配內存資源。這個時間對於一次或幾次數據庫操作,或許感覺不出系統有多大的開銷。

可是對於現在的web應用,尤其是大型電子商務網站,同時有幾百人甚至幾千人在線是很正常的事。在這種情況下,頻繁的進行數據庫連接操作勢必占用很多的系統資源,網站的響應速度必定下降,嚴重的甚至會造成服務器的崩潰。不是危言聳聽,這就是制約某些電子商務網站發展的技術瓶頸問題。其次,對於每一次數據庫連接,使用完后都得斷開。否則,如果程序出現異常而未能關閉,將會導致數據庫系統中的內存泄漏,最終將不得不重啟數據庫

     通過上面的分析,我們可以看出來,“數據庫連接”是一種稀缺的資源,為了保障網站的正常使用,應該對其進行妥善管理。其實我們查詢完數據庫后,如果不關閉連接,而是暫時存放起來,當別人使用時,把這個連接給他們使用。就避免了一次建立數據庫連接和斷開的操作時間消耗。

數據庫連接池的基本思想就是為數據庫連接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之后再放回去。我們可以通過設定連接池最大連接數來防止系統無盡的與數據庫連接

創建數據庫連接池大概有3個步驟:

① 創建ConnectionPool實例,並初始化創建10個連接,保存在Vector中(線程安全)單例模式實現
② 實現getConnection()從連接庫中獲取一個可用的連接
③ returnConnection(conn) 提供將連接放回連接池中方法

  連接池的實現

  1、連接池模型

  本文討論的連接池包括一個連接池類(DBConnectionPool)和一個連接池管理類(DBConnetionPoolManager)。連接池類是對某一數據庫所有連接的“緩沖池”,主要實現以下功能:①從連接池獲取或創建可用連接;②使用完畢之后,把連接返還給連接池;③在系統關閉前,斷開所有連接並釋放連接占用的系統資源;④還能夠處理無效連接(原來登記為可用的連接,由於某種原因不再可用,如超時,通訊問題),並能夠限制連接池中的連接總數不低於某個預定值和不超過某個預定值。

  連接池管理類是連接池類的外覆類(wrapper),符合單例模式,即系統中只能有一個連接池管理類的實例。其主要用於對多個連接池對象的管理,具有以下功能:①裝載並注冊特定數據庫的JDBC驅動程序;②根據屬性文件給定的信息,創建連接池對象;③為方便管理多個連接池對象,為每一個連接池對象取一個名字,實現連接池名字與其實例之間的映射;④跟蹤客戶使用連接情況,以便需要是關閉連接釋放資源。連接池管理類的引入主要是為了方便對多個連接池的使用和管理,如系統需要連接不同的數據庫,或連接相同的數據庫但由於安全性問題,需要不同的用戶使用不同的名稱和密碼。

連接池源碼:

ConnectionPool.Java 

[java] view plain copy print?
//////////////////////////////// 數據庫連接池類 ConnectionPool.java ////////////////////////////////////////  
  
/* 
 這個例子是根據POSTGRESQL數據庫寫的, 
 請用的時候根據實際的數據庫調整。 
 調用方法如下: 
 ① ConnectionPool connPool  
 = new ConnectionPool("com.microsoft.jdbc.sqlserver.SQLServerDriver" 
 ,"jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=MyDataForTest" 
 ,"Username" 
 ,"Password"); 
 ② connPool .createPool(); 
 Connection conn = connPool .getConnection(); 
 connPool.returnConnection(conn);  
 connPool.refreshConnections(); 
 connPool.closeConnectionPool(); 
 */  
import java.sql.Connection;  
import java.sql.DatabaseMetaData;  
import java.sql.Driver;  
import java.sql.DriverManager;  
import java.sql.SQLException;  
import java.sql.Statement;  
import java.util.Enumeration;  
import java.util.Vector;  
  
public class ConnectionPool {  
    private String jdbcDriver = ""; // 數據庫驅動  
    private String dbUrl = ""; // 數據 URL  
    private String dbUsername = ""; // 數據庫用戶名  
    private String dbPassword = ""; // 數據庫用戶密碼  
    private String testTable = ""; // 測試連接是否可用的測試表名,默認沒有測試表  
      
    private int initialConnections = 10; // 連接池的初始大小  
    private int incrementalConnections = 5;// 連接池自動增加的大小  
    private int maxConnections = 50; // 連接池最大的大小  
    private Vector connections = null; // 存放連接池中數據庫連接的向量 , 初始時為 null  
    // 它中存放的對象為 PooledConnection 型  
  
    /** 
     * 構造函數 
     *  
     * @param jdbcDriver 
     *            String JDBC 驅動類串 
     * @param dbUrl 
     *            String 數據庫 URL 
     * @param dbUsername 
     *            String 連接數據庫用戶名 
     * @param dbPassword 
     *            String 連接數據庫用戶的密碼 
     *  
     */  
    public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername,  
            String dbPassword) {  
        this.jdbcDriver = jdbcDriver;  
        this.dbUrl = dbUrl;  
        this.dbUsername = dbUsername;  
        this.dbPassword = dbPassword;  
    }  
  
    /** 
     * 返回連接池的初始大小 
     *  
     * @return 初始連接池中可獲得的連接數量 
     */  
    public int getInitialConnections() {  
        return this.initialConnections;  
    }  
    /** 
     * 設置連接池的初始大小 
     *  
     * @param 用於設置初始連接池中連接的數量 
     */  
    public void setInitialConnections(int initialConnections) {  
        this.initialConnections = initialConnections;  
    }  
    /** 
     * 返回連接池自動增加的大小 、 
     *  
     * @return 連接池自動增加的大小 
     */  
    public int getIncrementalConnections() {  
        return this.incrementalConnections;  
    }  
    /** 
     * 設置連接池自動增加的大小 
     *  
     * @param 連接池自動增加的大小 
     */  
  
    public void setIncrementalConnections(int incrementalConnections) {  
        this.incrementalConnections = incrementalConnections;  
    }  
    /** 
     * 返回連接池中最大的可用連接數量 
     *  
     * @return 連接池中最大的可用連接數量 
     */  
    public int getMaxConnections() {  
        return this.maxConnections;  
    }  
    /** 
     * 設置連接池中最大可用的連接數量 
     *  
     * @param 設置連接池中最大可用的連接數量值 
     */  
    public void setMaxConnections(int maxConnections) {  
        this.maxConnections = maxConnections;  
    }  
  
    /** 
     * 獲取測試數據庫表的名字 
     *  
     * @return 測試數據庫表的名字 
     */  
  
    public String getTestTable() {  
        return this.testTable;  
    }  
  
    /** 
     * 設置測試表的名字 
     *  
     * @param testTable 
     *            String 測試表的名字 
     */  
  
    public void setTestTable(String testTable) {  
        this.testTable = testTable;  
    }  
  
    /** 
     *  
     * 創建一個數據庫連接池,連接池中的可用連接的數量采用類成員 initialConnections 中設置的值 
     */  
  
    public synchronized void createPool() throws Exception {  
        // 確保連接池沒有創建  
        // 如果連接池己經創建了,保存連接的向量 connections 不會為空  
        if (connections != null) {  
            return; // 如果己經創建,則返回  
        }  
        // 實例化 JDBC Driver 中指定的驅動類實例  
        Driver driver = (Driver) (Class.forName(this.jdbcDriver).newInstance());  
        DriverManager.registerDriver(driver); // 注冊 JDBC 驅動程序  
        // 創建保存連接的向量 , 初始時有 0 個元素  
        connections = new Vector();  
        // 根據 initialConnections 中設置的值,創建連接。  
        createConnections(this.initialConnections);  
        // System.out.println(" 數據庫連接池創建成功! ");  
    }  
  
    /** 
     * 創建由 numConnections 指定數目的數據庫連接 , 並把這些連接 放入 connections 向量中 
     *  
     * @param numConnections 
     *            要創建的數據庫連接的數目 
     */  
  
    private void createConnections(int numConnections) throws SQLException {  
        // 循環創建指定數目的數據庫連接  
        for (int x = 0; x < numConnections; x++) {  
            // 是否連接池中的數據庫連接的數量己經達到最大?最大值由類成員 maxConnections  
            // 指出,如果 maxConnections 為 0 或負數,表示連接數量沒有限制。  
            // 如果連接數己經達到最大,即退出。  
            if (this.maxConnections > 0  
                    && this.connections.size() >= this.maxConnections) {  
                break;  
            }  
            // add a new PooledConnection object to connections vector  
            // 增加一個連接到連接池中(向量 connections 中)  
            try {  
                connections.addElement(new PooledConnection(newConnection()));  
            } catch (SQLException e) {  
                System.out.println(" 創建數據庫連接失敗! " + e.getMessage());  
                throw new SQLException();  
            }  
            // System.out.println(" 數據庫連接己創建 ......");  
        }  
    }  
    /** 
     * 創建一個新的數據庫連接並返回它 
     *  
     * @return 返回一個新創建的數據庫連接 
     */  
    private Connection newConnection() throws SQLException {  
        // 創建一個數據庫連接  
        Connection conn = DriverManager.getConnection(dbUrl, dbUsername,  
                dbPassword);  
        // 如果這是第一次創建數據庫連接,即檢查數據庫,獲得此數據庫允許支持的  
        // 最大客戶連接數目  
        // connections.size()==0 表示目前沒有連接己被創建  
        if (connections.size() == 0) {  
            DatabaseMetaData metaData = conn.getMetaData();  
            int driverMaxConnections = metaData.getMaxConnections();  
            // 數據庫返回的 driverMaxConnections 若為 0 ,表示此數據庫沒有最大  
            // 連接限制,或數據庫的最大連接限制不知道  
            // driverMaxConnections 為返回的一個整數,表示此數據庫允許客戶連接的數目  
            // 如果連接池中設置的最大連接數量大於數據庫允許的連接數目 , 則置連接池的最大  
            // 連接數目為數據庫允許的最大數目  
            if (driverMaxConnections > 0  
                    && this.maxConnections > driverMaxConnections) {  
                this.maxConnections = driverMaxConnections;  
            }  
        }  
        return conn; // 返回創建的新的數據庫連接  
    }  
  
    /** 
     * 通過調用 getFreeConnection() 函數返回一個可用的數據庫連接 , 如果當前沒有可用的數據庫連接,並且更多的數據庫連接不能創 
     * 建(如連接池大小的限制),此函數等待一會再嘗試獲取。 
     *  
     * @return 返回一個可用的數據庫連接對象 
     */  
  
    public synchronized Connection getConnection() throws SQLException {  
        // 確保連接池己被創建  
        if (connections == null) {  
            return null; // 連接池還沒創建,則返回 null  
        }  
        Connection conn = getFreeConnection(); // 獲得一個可用的數據庫連接  
        // 如果目前沒有可以使用的連接,即所有的連接都在使用中  
        while (conn == null) {  
            // 等一會再試  
            // System.out.println("Wait");  
            wait(250);  
            conn = getFreeConnection(); // 重新再試,直到獲得可用的連接,如果  
            // getFreeConnection() 返回的為 null  
            // 則表明創建一批連接后也不可獲得可用連接  
        }  
        return conn;// 返回獲得的可用的連接  
    }  
  
    /** 
     * 本函數從連接池向量 connections 中返回一個可用的的數據庫連接,如果 當前沒有可用的數據庫連接,本函數則根據 
     * incrementalConnections 設置 的值創建幾個數據庫連接,並放入連接池中。 如果創建后,所有的連接仍都在使用中,則返回 null 
     *  
     * @return 返回一個可用的數據庫連接 
     */  
    private Connection getFreeConnection() throws SQLException {  
        // 從連接池中獲得一個可用的數據庫連接  
        Connection conn = findFreeConnection();  
        if (conn == null) {  
            // 如果目前連接池中沒有可用的連接  
            // 創建一些連接  
            createConnections(incrementalConnections);  
            // 重新從池中查找是否有可用連接  
            conn = findFreeConnection();  
            if (conn == null) {  
                // 如果創建連接后仍獲得不到可用的連接,則返回 null  
                return null;  
            }  
        }  
        return conn;  
    }  
  
    /** 
     * 查找連接池中所有的連接,查找一個可用的數據庫連接, 如果沒有可用的連接,返回 null 
     *  
     * @return 返回一個可用的數據庫連接 
     */  
  
    private Connection findFreeConnection() throws SQLException {  
        Connection conn = null;  
        PooledConnection pConn = null;  
        // 獲得連接池向量中所有的對象  
        Enumeration enumerate = connections.elements();  
        // 遍歷所有的對象,看是否有可用的連接  
        while (enumerate.hasMoreElements()) {  
            pConn = (PooledConnection) enumerate.nextElement();  
            if (!pConn.isBusy()) {  
                // 如果此對象不忙,則獲得它的數據庫連接並把它設為忙  
                conn = pConn.getConnection();  
                pConn.setBusy(true);  
                // 測試此連接是否可用  
                if (!testConnection(conn)) {  
                    // 如果此連接不可再用了,則創建一個新的連接,  
                    // 並替換此不可用的連接對象,如果創建失敗,返回 null  
                    try {  
                        conn = newConnection();  
                    } catch (SQLException e) {  
                        System.out.println(" 創建數據庫連接失敗! " + e.getMessage());  
                        return null;  
                    }  
                    pConn.setConnection(conn);  
                }  
                break; // 己經找到一個可用的連接,退出  
            }  
        }  
        return conn;// 返回找到到的可用連接  
    }  
  
    /** 
     * 測試一個連接是否可用,如果不可用,關掉它並返回 false 否則可用返回 true 
     *  
     * @param conn 
     *            需要測試的數據庫連接 
     * @return 返回 true 表示此連接可用, false 表示不可用 
     */  
  
    private boolean testConnection(Connection conn) {  
        try {  
            // 判斷測試表是否存在  
            if (testTable.equals("")) {  
                // 如果測試表為空,試着使用此連接的 setAutoCommit() 方法  
                // 來判斷連接否可用(此方法只在部分數據庫可用,如果不可用 ,  
                // 拋出異常)。注意:使用測試表的方法更可靠  
                conn.setAutoCommit(true);  
            } else {// 有測試表的時候使用測試表測試  
                // check if this connection is valid  
                Statement stmt = conn.createStatement();  
                stmt.execute("select count(*) from " + testTable);  
            }  
        } catch (SQLException e) {  
            // 上面拋出異常,此連接己不可用,關閉它,並返回 false;  
            closeConnection(conn);  
            return false;  
        }  
        // 連接可用,返回 true  
        return true;  
    }  
  
    /** 
     * 此函數返回一個數據庫連接到連接池中,並把此連接置為空閑。 所有使用連接池獲得的數據庫連接均應在不使用此連接時返回它。 
     *  
     * @param 需返回到連接池中的連接對象 
     */  
  
    public void returnConnection(Connection conn) {  
        // 確保連接池存在,如果連接沒有創建(不存在),直接返回  
        if (connections == null) {  
            System.out.println(" 連接池不存在,無法返回此連接到連接池中 !");  
            return;  
        }  
        PooledConnection pConn = null;  
        Enumeration enumerate = connections.elements();  
        // 遍歷連接池中的所有連接,找到這個要返回的連接對象  
        while (enumerate.hasMoreElements()) {  
            pConn = (PooledConnection) enumerate.nextElement();  
            // 先找到連接池中的要返回的連接對象  
            if (conn == pConn.getConnection()) {  
                // 找到了 , 設置此連接為空閑狀態  
                pConn.setBusy(false);  
                break;  
            }  
        }  
    }  
  
    /** 
     * 刷新連接池中所有的連接對象 
     *  
     */  
  
    public synchronized void refreshConnections() throws SQLException {  
        // 確保連接池己創新存在  
        if (connections == null) {  
            System.out.println(" 連接池不存在,無法刷新 !");  
            return;  
        }  
        PooledConnection pConn = null;  
        Enumeration enumerate = connections.elements();  
        while (enumerate.hasMoreElements()) {  
            // 獲得一個連接對象  
            pConn = (PooledConnection) enumerate.nextElement();  
            // 如果對象忙則等 5 秒 ,5 秒后直接刷新  
            if (pConn.isBusy()) {  
                wait(5000); // 等 5 秒  
            }  
            // 關閉此連接,用一個新的連接代替它。  
            closeConnection(pConn.getConnection());  
            pConn.setConnection(newConnection());  
            pConn.setBusy(false);  
        }  
    }  
  
    /** 
     * 關閉連接池中所有的連接,並清空連接池。 
     */  
  
    public synchronized void closeConnectionPool() throws SQLException {  
        // 確保連接池存在,如果不存在,返回  
        if (connections == null) {  
            System.out.println(" 連接池不存在,無法關閉 !");  
            return;  
        }  
        PooledConnection pConn = null;  
        Enumeration enumerate = connections.elements();  
        while (enumerate.hasMoreElements()) {  
            pConn = (PooledConnection) enumerate.nextElement();  
            // 如果忙,等 5 秒  
            if (pConn.isBusy()) {  
                wait(5000); // 等 5 秒  
            }  
            // 5 秒后直接關閉它  
            closeConnection(pConn.getConnection());  
            // 從連接池向量中刪除它  
            connections.removeElement(pConn);  
        }  
        // 置連接池為空  
        connections = null;  
    }  
  
    /** 
     * 關閉一個數據庫連接 
     *  
     * @param 需要關閉的數據庫連接 
     */  
  
    private void closeConnection(Connection conn) {  
        try {  
            conn.close();  
        } catch (SQLException e) {  
            System.out.println(" 關閉數據庫連接出錯: " + e.getMessage());  
        }  
    }  
    /** 
     * 使程序等待給定的毫秒數 
     *  
     * @param 給定的毫秒數 
     */  
  
    private void wait(int mSeconds) {  
        try {  
            Thread.sleep(mSeconds);  
        } catch (InterruptedException e) {  
        }  
    }  
    /** 
     *  
     * 內部使用的用於保存連接池中連接對象的類 此類中有兩個成員,一個是數據庫的連接,另一個是指示此連接是否 正在使用的標志。 
     */  
  
    class PooledConnection {  
        Connection connection = null;// 數據庫連接  
        boolean busy = false; // 此連接是否正在使用的標志,默認沒有正在使用  
  
        // 構造函數,根據一個 Connection 構告一個 PooledConnection 對象  
        public PooledConnection(Connection connection) {  
            this.connection = connection;  
        }  
  
        // 返回此對象中的連接  
        public Connection getConnection() {  
            return connection;  
        }  
  
        // 設置此對象的,連接  
        public void setConnection(Connection connection) {  
            this.connection = connection;  
        }  
  
        // 獲得對象連接是否忙  
        public boolean isBusy() {  
            return busy;  
        }  
  
        // 設置對象的連接正在忙  
        public void setBusy(boolean busy) {  
            this.busy = busy;  
        }  
    }  
  
}  
View Code

 

ConnectionPoolUtils.java

/*連接池工具類,返回唯一的一個數據庫連接池對象,單例模式*/  
public class ConnectionPoolUtils {  
    private ConnectionPoolUtils(){};//私有靜態方法  
    private static ConnectionPool poolInstance = null;  
    public static ConnectionPool GetPoolInstance(){  
        if(poolInstance == null) {  
            poolInstance = new ConnectionPool(                     
                    "com.mysql.jdbc.Driver",                   
                    "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8",                
                    "root", "123456");  
            try {  
                poolInstance.createPool();  
            } catch (Exception e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
        return poolInstance;  
    }  
}  

ConnectionPoolTest.java 

import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.ResultSet;  
import java.sql.SQLException;  
import java.sql.Statement;  
  
  
public class ConnectionTest {  
  
    /** 
     * @param args 
     * @throws Exception  
     */  
    public static void main(String[] args) throws Exception {  
         try {  
                  /*使用連接池創建100個連接的時間*/   
                   /*// 創建數據庫連接庫對象 
                   ConnectionPool connPool = new ConnectionPool("com.mysql.jdbc.Driver","jdbc:mysql://localhost:3306/test", "root", "123456"); 
                   // 新建數據庫連接庫 
                   connPool.createPool();*/  
               
                  ConnectionPool  connPool=ConnectionPoolUtils.GetPoolInstance();//單例模式創建連接池對象  
                    // SQL測試語句  
                   String sql = "Select * from pet";  
                   // 設定程序運行起始時間  
                   long start = System.currentTimeMillis();  
                         // 循環測試100次數據庫連接  
                          for (int i = 0; i < 100; i++) {  
                              Connection conn = connPool.getConnection(); // 從連接庫中獲取一個可用的連接  
                              Statement stmt = conn.createStatement();  
                              ResultSet rs = stmt.executeQuery(sql);  
                              while (rs.next()) {  
                                  String name = rs.getString("name");  
                               //  System.out.println("查詢結果" + name);  
                              }  
                              rs.close();  
                              stmt.close();  
                              connPool.returnConnection(conn);// 連接使用完后釋放連接到連接池  
                          }  
                          System.out.println("經過100次的循環調用,使用連接池花費的時間:"+ (System.currentTimeMillis() - start) + "ms");  
                          // connPool.refreshConnections();//刷新數據庫連接池中所有連接,即不管連接是否正在運行,都把所有連接都釋放並放回到連接池。注意:這個耗時比較大。  
                         connPool.closeConnectionPool();// 關閉數據庫連接池。注意:這個耗時比較大。  
                          // 設定程序運行起始時間  
                          start = System.currentTimeMillis();  
                            
                          /*不使用連接池創建100個連接的時間*/  
                         // 導入驅動  
                          Class.forName("com.mysql.jdbc.Driver");  
                          for (int i = 0; i < 100; i++) {  
                              // 創建連接  
                             Connection conn = DriverManager.getConnection(  
                                      "jdbc:mysql://localhost:3306/test", "root", "123456");  
                              Statement stmt = conn.createStatement();  
                              ResultSet rs = stmt.executeQuery(sql);  
                             while (rs.next()) {  
                              }  
                             rs.close();  
                             stmt.close();  
                             conn.close();// 關閉連接  
                         }  
                         System.out.println("經過100次的循環調用,不使用連接池花費的時間:"  
                                 + (System.currentTimeMillis() - start) + "ms");  
                     } catch (SQLException e) {  
                        e.printStackTrace();  
                     } catch (ClassNotFoundException e) {  
                         e.printStackTrace();  
                    }  
    }  
View Code

 

DBCPUtils:

 

public class DBCPUtils {
    private static DataSource ds;//定義一個連接池對象
    static{
        try {
            Properties pro = new Properties();
            pro.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));
            ds = BasicDataSourceFactory.createDataSource(pro);//得到一個連接池對象
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化連接錯誤,請檢查配置文件!");
        }
    }
    //從池中獲取一個連接
    public static Connection getConnection() throws SQLException{
        return ds.getConnection();
    }
    
    public static void closeAll(ResultSet rs,Statement stmt,Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        
        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        
        if(conn!=null){
            try {
                conn.close();//關閉
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

 

6.導入的jar包

commons-dbcp.jar:DBCP實現要導入的jar

commons-pool.jar: 連接池實現的依賴類

commons-collections.jar :連接池實現的集合類

 

參考:MySql數據庫連接池

參考:數據庫連接池的理解

參考:數據庫連接池原理

參考:Java數據庫連接池實現原理

參考:Java數據庫連接池--DBCP淺析.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM