1. 程式人生 > >八、JDBC-資料庫連線池

八、JDBC-資料庫連線池

JDBC資料庫連線池的必要性

一、在使用開發基於資料庫的web程式時,傳統的模式基本是按一下步驟:

1)在主程式(如servlet/beans)中建立資料庫連線

2)進行sql操作

3)斷開資料庫連線

二、這種模式開發,存在的問題:

1)普通的JDBC資料庫連線使用DriverManager來獲取,每次向資料庫建立連線的時候都要將Connection載入進記憶體中,再驗證使用者名稱和密碼(得花費0.05s~1s的時間).需要資料庫連線的時候,就向資料庫要求一個,執行完成後就斷開連線。這樣的方式將消耗大連的時間和資源。資料庫的連線資源並沒有得到很好的重複利用。若同時有幾百人甚至幾千人同時線上,頻繁的進行資料庫連線將佔用很多的系統資源,嚴重的甚至會造成伺服器的崩潰。

2)對於每一次資料庫連線,使用完成後都要斷開。否則,如果程式出現異常而未能關閉,將導致資料庫系統的記憶體洩露,並最終導致重啟資料庫。

3)這種開發不能控制被建立的連線物件數,系統資源被毫無顧忌的分配出去,如連線過多,也可能導致記憶體洩露,伺服器崩潰。

資料庫連線池(connection pool)

1)為了解決傳統開發中的資料庫連線問題,可以採用資料庫連線池技術。

2)資料庫連線池的基本思想就是為資料庫連線建立一個“緩衝池”,預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需要從“緩衝池”中取出一個,使用完畢之後再放回去。

3)資料庫連線池負責分配、管理和釋放資料庫連線,他允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。

4)資料庫連線池在初始化的時候將建立一定數量的資料庫連線放到連線池中,這些資料庫連線的數量由最小資料庫連線數來設定的。無論這些資料庫連線是否被使用,連線池都將一直保證至少擁有這麼多的連線數量。

連線池的最大資料庫連線數量限定了這個連線池能佔有的最大連線數,當應用程式想連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待序列中.

資料庫連線池(connection pool)的工作原理

資料庫連線池(connection pool)的優點

1) 資源重用 
由於資料庫連線得到重用,避免了頻繁建立、釋放連線引起的大量效能開銷。在減少系統消耗的基礎上,另一方面也增進了系統執行環境的平穩性(減少記憶體碎片以及資料庫臨時程序/執行緒的數量);
2) 更快的系統響應速度 
資料庫連線池在初始化過程中,往往已經建立了若干資料庫連線置於池中備用。此時連線的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連線,避免了資料庫連線初始化和釋放過程的時間開銷,從而縮減了系統整體響應時間;
3) 新的資源分配手段 
對於多應用共享同一資料庫的系統而言,可在應用層通過資料庫連線的配置,實現資料庫連線池技術,幾年前也許還是個新鮮話題,對於目前的業務系統而言,如果設計中還沒有考慮到連線池的應用,那麼…….快在設計文件中加上這部分的內容吧。某一應用最大可用資料庫連線數的限制,避免某一應用獨佔所有資料庫資源;
4) 統一的連線管理,避免資料庫連線洩漏 
在較為完備的資料庫連線池實現中,可根據預先的連線佔用超時設定,強制收回被佔用連線。從而避免了常規資料庫連線操作中可能出現的資源洩漏。一個最小化的資料庫連線池實現;

兩種開源的資料庫連線池

1)JDBC的資料庫連線池使用javax.sql.DataSource來表示,DataSource只是一個介面,該介面通常由伺服器(Weblogic,WebSphere,Tomcat)提供實現

--DBCP資料庫連線池(Tomcat內建的資料庫連線池)

--C3P0資料庫連線池(Hibernate推薦使用的資料庫連線池,效能不錯)

2)DataSource通常被稱為資料來源,它包含連線池和連線池管理兩個部分,習慣上也經常把DataSource稱為連線池。

DBCP資料庫連線池

方式一:

使用DBCP資料庫連線池的步驟

* 1.加入JAR包(2個)
* commons-dbcp2-2.1.1.jar
* Commons-pool.jar
* 2.建立資料庫連線池
* 3.為資料來源例項指定必須的屬性
* 4.從資料來源中獲取資料庫連線

具體程式碼實現:

@Test
    public void testDBCP() throws Exception{
        DataSource dataSource=null;
        //1.建立DBCP資料來源例項
        dataSource=new BasicDataSource();
        //2.為資料來源例項指定必須的屬性
        ((BasicDataSource) dataSource).setUsername("root");
        ((BasicDataSource) dataSource).setPassword("123456");
        ((BasicDataSource) dataSource).setUrl("jdbc:mysql://localhost:3306/atguigu");
        ((BasicDataSource) dataSource).setDriverClassName("com.mysql.jdbc.Driver");
        //3.指定資料來源一些可選的屬性(可以看下載的Jar包中的index.html)API文件 
        /*
         * maxIdle,最大空閒數,資料庫連線的最大空閒時間。超過空閒時間,資料庫連線將被標記為不可用,然後被釋放。設為0表示無限制。
           MaxActive,連線池的最大資料庫連線數。設為0表示無限制。
           maxWait ,最大建立連線等待時間。如果超過此時間將接到異常。設為-1表示無限制
                                【溫馨提示】:pool2中修改如下:
                    maxActive  ==>  maxTotal
                    maxWait ==> maxWaitMillis
         */
        //1).指定資料庫連線池中初始化連線的個數
        ((BasicDataSource) dataSource).setInitialSize(10);
        //2).指定資料庫連線池中最大連線的個數:同一時刻可以同時向資料庫申請的資料庫連線
        ((BasicDataSource) dataSource).setMaxTotal(5);
        //3).指定資料庫連線池中最小連線的個數:在連線池中儲存的最小空閒連線的數量
        ((BasicDataSource) dataSource).setMinIdle(5);
        //4).指定資料庫連線池分配連線的最長時間,單位為毫秒,超出改時間將丟擲異常
        ((BasicDataSource) dataSource).setMaxWaitMillis(1000*5);
        //4.從資料來源中獲取資料庫連線
    
        Connection connection=dataSource.getConnection();
        System.out.println(connection.getClass());
       
    }

方式二:

使用DBCP資料庫連線池的步驟

* 1.載入dbcp的properties配置檔案
* 配置檔案中的鍵值對需要來自BasicDataSource這個類的屬性
* 2.呼叫BasicDataSourceFactory的createDataSource方法
* 建立DataSource例項
* 3.從DataSource中獲取資料庫連線

dbcp.properties檔案內容如下:

driver=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/atguigu
user=root
password=123456
initialSize=10
maxTotal=50
minIdle=5
maxWaitMillis=5000
@Test
    public void testDBCPWithDataSourceFactory() throws Exception{
        Properties properties=new Properties();
        InputStream inputStream=JDBCTest.class.getClassLoader()
                .getResourceAsStream("dbcp.properties");
        properties.load(inputStream);
        DataSource dataSource=
                BasicDataSourceFactory.createDataSource(properties);
        System.out.println(dataSource.getConnection());
        
        BasicDataSource basicDataSource=
                (BasicDataSource) dataSource;
        System.out.println(basicDataSource.getMaxTotal());
        
    }

C3P0資料庫連線池

方式一:

這裡我們需要加入兩個JAR包:c3p0-0.9.5.2.jar和mchange-commons-java-0.2.11.jar

我們下載的c3p0JAR包中的doc資料夾中的index.html檔案,點選該檔案->Contents->Quickstart,我們可以看到c3p0資料庫連線池的建立步驟,如下圖所示(我們使用一些JAR包的時候要善於利用好幫助文件):

import com.mchange.v2.c3p0.*;
    
...
    
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver            
cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
cpds.setUser("dbuser");                                  
cpds.setPassword("dbpassword");

於是我們的程式碼就可以這樣寫了:

@Test
    public void testC3P0() throws Exception{
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver            
        cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/atguigu" );
        cpds.setUser("root");                                  
        cpds.setPassword("123456");
        System.out.println(cpds.getConnection());
    }

執行結果:證明建立成功

五月 09, 2016 4:35:56 下午 com.mchange.v2.log.MLog 
資訊: MLog clients using java 1.4+ standard logging.
五月 09, 2016 4:35:57 下午 com.mchange.v2.c3p0.C3P0Registry 
資訊: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
五月 09, 2016 4:35:57 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
資訊: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hge1d39g158wpld8tx485|1588809, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge1d39g158wpld8tx485|1588809, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/atguigu, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
[email protected] [wrapping: [email protected]]

方式二:使用配置檔案的形式

具體的步驟:

 * 1.建立c3p0-config.xml檔案,參考幫助文件中
     *   Appendix B: Configuation Files, etc.的內容
     * 2.建立ComboPooledDataSource例項:
     *   DataSource dataSource = 
                new ComboPooledDataSource("helloc3p0"); 
     *3.從DataSource例項中獲取資料庫連線

其中c3p0-config.xml中的內容我們進行更改成下面的形式:每一行代表什麼我都給出了詳細的解釋

<c3p0-config>
  
  <named-config name="helloc3p0"> 
  <!-- 指定資料來源的基本屬性 -->
    <property name="user">root</property>
    <property name="password">123456</property>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/atguigu</property>
    <!-- 若資料庫中連線數不足時,一次向資料庫伺服器申請多少個連線    -->
    <property name="acquireIncrement">5</property>
    <!-- 初始化資料庫連線時連線的數量 -->
    <property name="initialPoolSize">10</property>
    <!-- 資料庫連線池中最小的資料庫連線數 -->
    <property name="minPoolSize">5</property>
     <!-- 資料庫連線池中最大的資料庫連線數 -->
    <property name="maxPoolSize">10</property>

    <!-- 資料庫連線池可以維護的Statement的個數 -->
    <property name="maxStatements">10</property> 
    <!-- 每個連線同時可以使用的Statement物件的個數 -->
    <property name="maxStatementsPerConnection">5</property>

  </named-config>
</c3p0-config>

我們可以看到named-config name="helloc3p0",我們要建立的資料庫連線池的名稱是helloc3p0;

第二種方式的具體程式碼實現:

@Test
    public void testC3P0withConfigFile() throws Exception{
        DataSource dataSource = 
                new ComboPooledDataSource("helloc3p0"); 
        System.out.println(dataSource.getConnection());
        ComboPooledDataSource comboPooledDataSource=
                (ComboPooledDataSource) dataSource;
        System.out.println(comboPooledDataSource.getMaxStatements());
    }

測試一些,執行結果:

五月 09, 2016 4:42:15 下午 com.mchange.v2.log.MLog 
資訊: MLog clients using java 1.4+ standard logging.
五月 09, 2016 4:42:15 下午 com.mchange.v2.c3p0.C3P0Registry 
資訊: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
五月 09, 2016 4:42:15 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
資訊: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> helloc3p0, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge1d39g1594tqaufcxlm|1588809, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/atguigu, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 10, maxStatementsPerConnection -> 5, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
[email protected] [wrapping: [email protected]]
10

此時我們JDBCtools中的獲取連線的函式getConnection就可以改寫程式碼了,不是每一次都建立新的資料庫連線,而是從資料庫連線池中獲取連線,release函式也不是真正的關閉連線了,而是把資料庫連線繼續放到連線池中。

private static DataSource dataSource=null;
    //資料庫連線池只被初始化一次
    static {
        dataSource=new ComboPooledDataSource("helloc3p0");
    }
    // 獲取資料庫連線
    public static Connection getConnection() throws  Exception{
        
        /*    ClassNotFoundException, SQLException {
        Properties properties = new Properties();
        InputStream inputStream = JDBCTest.class.getClassLoader()
                .getResourceAsStream("jdbc.properties");
        properties.load(inputStream);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String jdbcUrl = properties.getProperty("jdbcUrl");
        String driverClass = properties.getProperty("driver");
        Class.forName(driverClass);
        Connection connection = DriverManager.getConnection(jdbcUrl, user,
                password);*/
        
        return dataSource.getConnection();
    }

資料庫連線池一般只有一個,所以我們用static修飾,並使用靜態程式碼塊的形式初始化資料庫連線池,getConnection中的被註釋程式碼使我們原來的獲取連線的方式,可以看到程式碼簡潔了不少!