1. 程式人生 > >MySql資料庫連線池專題

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 連線池

       2.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;  
        }  
    }  
  
}  

複製程式碼

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 

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 :連線池實現的集合類