MySql數據庫鏈接池專題

1、什麼是數據庫鏈接池?

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

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

 傳通通連接:     通常來講,Java應用程序訪問數據庫的過程是:java

  ①裝載數據庫驅動程序;mysql

  ②經過JDBC創建數據庫鏈接;web

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

  ④斷開數據庫鏈接。sql

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

 

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

數據庫

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

      數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:編程

  1, 最小鏈接數:是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費.
  2, 最大鏈接數:是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過次數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響之後的數據庫操做
  3, 若是最小鏈接數與最大鏈接數相差很大:那麼最早鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接.不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,他將被           放到鏈接池中等待重複使用或是空間超時後被釋放.c#

2、使用數據庫鏈接池的關鍵點

一、併發問題

  爲了使鏈接管理服務具備最大的通用性,必須考慮多線程環境,即併發問題。這個問題相對比較好解決,由於各個語言自身提供了對併發管理的支持像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)等參數來控制鏈接池中的鏈接。比方說,最小鏈接數是系統啓動時鏈接池所建立的鏈接數。若是建立過多,則系統啓動就慢,但建立後系統的響應速度會很快;若是建立過少,則系統啓動的很快,響應起來卻慢。這樣,能夠在開發時,設置較小的最小鏈接數,開發起來會快,而在系統實際使用時設置較大的,由於這樣對訪問客戶來講速度會快些。最大鏈接數是鏈接池中容許鏈接的最大數目,具體設置多少,要看系統的訪問量,可經過軟件需求上獲得。 
  如何確保鏈接池中的最小鏈接數呢?有動態和靜態兩種策略。動態即每隔必定時間就對鏈接池進行檢測,若是發現鏈接數量小於最小鏈接數,則補充相應數量的新鏈接,以保證鏈接池的正常運轉。靜態是發現空閒鏈接不夠時再去檢查。

 

3、使用數據庫鏈接池的優點和其工做原理

一、鏈接池的優點

鏈接池用於建立和管理數據庫鏈接的緩衝池技術,緩衝池中的鏈接能夠被任何須要他們的線程使用。當一個線程須要用JDBC對一個數據庫操做時,將從池中請求一個鏈接。當這個鏈接使用完畢後,將返回到鏈接池中,等待爲其餘的線程服務。

鏈接池的主要優勢有如下三個方面。

第1、減小鏈接建立時間。鏈接池中的鏈接是已準備好的、可重複使用的,獲取後能夠直接訪問數據庫,所以減小了鏈接建立的次數和時間。

第2、簡化的編程模式。當使用鏈接池時,每個單獨的線程可以像建立一個本身的JDBC鏈接同樣操做,容許用戶直接使用JDBC編程技術。

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

二、鏈接池的工做原理

下面,簡單的闡述下鏈接池的工做原理。

鏈接池技術的核心思想是鏈接複用,經過創建一個數據庫鏈接池以及一套鏈接使用、分配和管理策略,使得該鏈接池中的鏈接能夠獲得高效、安全的複用,避免了數據庫鏈接頻繁創建、關閉的開銷。

鏈接池的工做原理主要由三部分組成,分別爲鏈接池的創建、鏈接池中鏈接的使用管理、鏈接池的關閉。

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

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

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

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

第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) 提供將鏈接放回鏈接池中方法

  鏈接池的實現

  一、鏈接池模型

  本文討論的鏈接池包括一個鏈接池類(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淺析.

相關文章
相關標籤/搜索