數據庫連接池


1、數據庫連接池的基本介紹

數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是再重新建立一個;釋放空閑時間超過最大空閑時間的數據庫連接來避免因為沒有釋放數據庫連接而引起的數據庫連接遺漏。這項技術能明顯提高對數據庫操作的性能。數據庫連接池其實就是一個容器(集合),存放着數據庫連接。

連接池基本的思想是在系統初始化的時候,將數據庫連接作為對象存儲在內存中,當用戶需要訪問數據庫時,並非建立一個新的連接,而是從連接池中取出一個已建立的空閑連接對象。使用完畢后,用戶也並非將連接關閉,而是將連接放回連接池中,以供下一個請求訪問使用。而連接的建立、斷開都由連接池自身來管理。同時,還可以通過設置連接池的參數來控制連接池中的初始連接數、連接的上下限數以及每個連接的最大使用次數、最大空閑時間等等。也可以通過其自身的管理機制來監視數據庫連接的數量、使用情況等。

 

 

1.1、為什么要使用連接池

 一個數據庫連接對象均對應一個物理數據庫連接,每次操作都打開一個物理連接,使用完都關閉連接,這樣造成系統的性能低下。使用數據庫連接池可以節約資源,程序更加高效。

數據庫連接池的解決方案是在應用程序啟動時建立足夠的數據庫連接,並講這些連接組成一個連接池(簡單說:在一個“池”里放了好多半成品的數據庫連接對象),由應用程序動態地對池中的連接進行申請、使用和釋放。對於多於連接池中連接數的並發請求,應該在請求隊列中排隊等待。並且應用程序可以根據池中連接的使用率,動態增加或減少池中的連接數。連接池技術盡可能多地重用了消耗內存地資源,大大節省了內存,提高了服務器地服務效率,能夠支持更多的客戶服務。通過使用連接池,將大大提高程序運行效率,同時,我們可以通過其自身的管理機制來監視數據庫連接的數量、使用情況等。

 

1.2、Java中常見的數據連接池

在Java中開源的數據庫連接池有以下幾種 :

  1. C3P0:是一個開放源代碼的JDBC連接池,它在lib目錄中與Hibernate一起發布,包括了實現jdbc3和jdbc2擴展規范說明的Connection 和Statement 池的DataSources 對象。
  2. Druid:Druid不僅是一個數據庫連接池,還包含一個ProxyDriver、一系列內置的JDBC組件庫、一個SQL Parser。支持所有JDBC兼容的數據庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
  3. Proxool:是一個Java SQL Driver驅動程序,提供了對選擇的其它類型的驅動程序的連接池封裝。可以非常簡單的移植到現存的代碼中,完全可配置,快速、成熟、健壯。可以透明地為現存的JDBC驅動程序增加連接池功能。
  4. DBCP:DBCP是一個依賴Jakarta commons-pool對象池機制的數據庫連接池,DBCP可以直接的在應用程序中使用,Tomcat的數據源使用的就是DBCP。
我們在使用連接池時,連接池給我們提供了獲取數據庫連接的方法,但使用完成后還是要調用 close 方法來關閉連接。但是實際上使用連接池時調用 close() 方法實際上並沒有關閉連接,只是將該連接歸還給連接池。
 

2、C3P0連接池

C3P0是一個開源的JDBC連接池,它實現了數據源與JNDI綁定,支持JDBC3規范和實現了JDBC2的標准擴展說明的Connection和Statement池的DataSources對象。即將用於連接數據庫的連接整合在一起形成一個隨取隨用的數據庫連接池(Connection pool)。

 

2.1、基於maven項目使用C3P0

首先添加依賴:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.12</version>
</dependency>
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.5.2</version>
</dependency>

因為使用的是 mysql,所以我們要添加 mysql 的驅動包依賴。

 

如果新建的 maven 項目是 java SE 的項目,我們需要手動建一個 resource 包,並且標記為 resource folder,因為我們需要將 cp30 的配置文件放在該包下。

新建 cp30 的配置文件 c3p0-config.xml,並將該配置文件放在 resource 包下。只有放在 resource 包下,編譯過后你才會發現 cp30 的配置文件生成在 target 的classes 文件夾下。最終目錄如下:

 

 

c3p0-config.xml 文件內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--使用默認的配置讀取連接池對象-->
    <default-config>
        <!--連接參數-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/db_test</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!--連接池參數-->
        <!--初始化申請的連接數量。在初始化時就申請的連接數-->
        <property name="initialPoolSize">5</property>
        <!--超時時間-->
        <property name="checkoutTimeout">3000</property>
        <property name="maxIdleTime">30</property>
        <!--最大的連接數量。超過該數目不會再申請多的連接-->
        <property name="maxPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxStatements">200</property>
    </default-config>

    <!--配置連接池mysql-->
    <named-config name="mysql">
        <property name="acquireIncrement">50</property>
        <property name="initialPoolSize">100</property>
        <property name="minPoolSize">50</property>
        <property name="maxPoolSize">1000</property><!-- intergalactoApp adopts a different approach to configuring statement caching -->
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>

    <!--配置連接池2-->
    <!--......-->
</c3p0-config>

在配置文件中可以配置多個連接池,default-config 是默認的連接池配置,可以通過 name-config 標簽來配置其他的連接池配置,通過 name 屬性為該配置指定名稱。在使用額外的連接池配置時,最終的配置信息是默認配置和該額外的配置的並集,並且以額外的配置優先。

在獲取連接池對象時,如果不傳參則使用的是默認配置,也可以通過使用 name 參數來使用指定配置。代碼如下,我們測試一下連接池是否配置成功:

public class CP30_test {
    public void test01() throws SQLException {
        //1.創建數據庫連接池對象。不指定名稱使用的是默認配置,即default-config
        DataSource ds = new ComboPooledDataSource();
//可以通過參數來使用指定的配置 //DataSource ds = new ComboPooledDataSource("mysql-config"); //2.獲取連接對象 Connection conn = ds.getConnection(); //3.打印 System.out.println(conn); } }

打印結果如下:

說明配置已經成功。我們可以通過該連接池來獲取連接對象,JDBC 的其他步驟跟不使用連接池時一樣。

 

2.1.1、新建C3P0Util工具類

一般在使用數據庫連接池時,我們會新建一個工具類來方便我們使用連接池。

工具類代碼示例:

package com.c3p0.utils;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
import javax.sql.DataSource;
 
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
public class C3P0Util {
    //使用ComboPooledDataSource來生成DataSource的實例
    private static DataSource dataSource =new ComboPooledDataSource();
 
    //從連接池中獲取連接
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        }catch (SQLException e) {
            // TODO Auto-generated catch block
            throw new RuntimeException();
        }
    }
 
    //釋放連接回連接池
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs !=null) {
            try {
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs =null;
        }
        if (stmt !=null) {
            try {
                stmt.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            stmt =null;
        }
        if (conn !=null) {
            try {
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            conn =null;
        }
    }
}

使用工具類:

public class TestCRUD {
    public void testInsert() {
        Connection conn =null;
        PreparedStatement ps =null;
        conn = C3P0Util.getConnection();
        try {
            ps = conn.prepareStatement("INSERT INTO users (username,PASSWORD,email,birthday)VALUES('SUN99','123','123456@qq.com','2020-01-01')");
            ps.executeUpdate();
            System.out.println("添加操作執行成功!");
        }catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("添加操作執行失敗!");
        }finally {
            C3P0Util.release(conn, ps,null);
        }
    }
 }

 

2.2、c3p0配置參數詳解

常見配置參數:

  • initialPoolSize:初始化連接數。在容器初始化時就申請的連接數量

  • maxPoolSize:最大連接數。如果需要的連接超過該數目容器也不會再申請多的連接,此時數據庫連接可能就會超時,無法查詢到數據。最大連接數應該根據服務器的性能來靈活配置

 

<c3p0-config>   
    <default-config>   
    <!--當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default:3 -->   
    <property name="acquireIncrement">3</property>   
 
    <!--定義在從數據庫獲取新連接失敗后重復嘗試的次數。Default:30 -->   
    <property name="acquireRetryAttempts">30</property>   
       
    <!--兩次連接中間隔時間,單位毫秒。Default:1000 -->   
    <property name="acquireRetryDelay">1000</property>   
       
    <!--連接關閉時默認將所有未提交的操作回滾。Default:false -->   
    <property name="autoCommitOnClose">false</property>   
       
    <!--c3p0將建一張名為Test的空表,並使用其自帶的查詢語句進行測試。如果定義了這個參數那么   
    屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操作,它將只供c3p0測試   
    使用。Default:null-->   
    <property name="automaticTestTable">Test</property>   
       
    <!--獲取連接失敗將會引起所有等待連接池來獲取連接的線程拋出異常。但是數據源仍有效   
    保留,並在下次調用getConnection()的時候繼續嘗試獲取連接。如果設為true,那么在嘗試   
    獲取連接失敗后該數據源將申明已斷開並永久關閉。Default:false-->   
    <property name="breakAfterAcquireFailure">false</property>   
       
    <!--當連接池用完時客戶端調用getConnection()后等待獲取新連接的時間,超時后將拋出   
    SQLException,如設為0則無限期等待。單位毫秒。Default:0 -->   
    <property name="checkoutTimeout">100</property>   
       
    <!--通過實現ConnectionTester或QueryConnectionTester的類來測試連接。類名需制定全路徑。   
    Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->   
    <property name="connectionTesterClassName"></property>   
       
    <!--指定c3p0 libraries的路徑,如果(通常都是這樣)在本地即可獲得那么無需設置,默認null即可   
    Default:null-->   
    <property name="factoryClassLocation">null</property>   
       
    <!--強烈不建議使用該方法,將這個設置為true可能會導致一些微妙而奇怪的bug-->   
    <property name="forceIgnoreUnresolvedTransactions">false</property>   
       
    <!--每60秒檢查所有連接池中的空閑連接。Default:0 -->   
    <property name="idleConnectionTestPeriod">60</property>   
       
    <!--初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。Default:3 -->   
    <property name="initialPoolSize">3</property>   
       
    <!--最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。Default:0 -->   
    <property name="maxIdleTime">60</property>   
       
    <!--連接池中保留的最大連接數。Default:15 -->   
    <property name="maxPoolSize">15</property>   
       
    <!--JDBC的標准參數,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements   
    屬於單個connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素。   
    如果maxStatements與maxStatementsPerConnection均為0,則緩存被關閉。Default:0-->   
    <property name="maxStatements">100</property>   
       
    <!--maxStatementsPerConnection定義了連接池內單個連接所擁有的最大緩存statements數。Default:0 -->   
    <property name="maxStatementsPerConnection"></property>   
       
    <!--c3p0是異步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能   
    通過多線程實現多個操作同時被執行。Default:3-->   
    <property name="numHelperThreads">3</property>   
       
    <!--當用戶調用getConnection()時使root用戶成為去獲取連接的用戶。主要用於連接池連接非c3p0   
    的數據源時。Default:null-->   
    <property name="overrideDefaultUser">root</property>   
       
    <!--與overrideDefaultUser參數對應使用的一個參數。Default:null-->   
    <property name="overrideDefaultPassword">password</property>   
       
    <!--密碼。Default:null-->   
    <property name="password"></property>   
       
    <!--定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個一顯著提高測試速度。注意:   
    測試的表必須在初始數據源的時候就存在。Default:null-->   
    <property name="preferredTestQuery">select id from test where id=1</property>   
       
    <!--用戶修改系統配置參數執行前最多等待300秒。Default:300 -->   
    <property name="propertyCycle">300</property>   
       
    <!--因性能消耗大請只在需要的時候使用它。如果設為true那么在每個connection提交的   
    時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable   
    等方法來提升連接測試的性能。Default:false -->   
    <property name="testConnectionOnCheckout">false</property>   
       
    <!--如果設為true那么在取得連接的同時將校驗連接的有效性。Default:false -->   
    <property name="testConnectionOnCheckin">true</property>   
       
    <!--用戶名。Default:null-->   
    <property name="user">root</property>   
       
    <!--早期的c3p0版本對JDBC接口采用動態反射代理。在早期版本用途廣泛的情況下這個參數   
    允許用戶恢復到動態反射代理以解決不穩定的故障。最新的非反射代理更快並且已經開始   
    廣泛的被使用,所以這個參數未必有用。現在原先的動態反射與新的非反射代理同時受到   
    支持,但今后可能的版本可能不支持動態反射代理。Default:false-->   
    <property name="usesTraditionalReflectiveProxies">false</property> 
    </default-config>      
</c3p0-config>

 

3、druid連接池

3.1、druid連接池的基本介紹

Druid是阿里開源的數據庫連接池,作為后起之秀,性能比dbcp、c3p0更高,使用也越來越廣泛。Druid不僅是一個數據庫連接池,還包含一個ProxyDriver、一系列內置的JDBC組件庫、一個SQL Parser。支持所有JDBC兼容的數據庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。

druid的優點:

  1. 高性能。性能比dbcp、c3p0高很多。
  2. 只要是jdbc支持的數據庫,druid都支持,對數據庫的支持性好。並且Druid針對oracle、mysql做了特別優化。
  3. 提供監控功能。可以監控sql語句的執行時間、ResultSet持有時間、返回行數、更新行數、錯誤次數、錯誤堆棧等信息,來了解連接池、sql語句的工作情況,方便統計、分析SQL的執行性能

 

3.2、基於maven項目使用druid連接池

首先添加依賴,因為使用的是 mysql,所以也要添加 mysql 的驅動依賴:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.8</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.12</version>
</dependency>

依賴添加完成,我們需要添加 druid 的配置文件。如果新建的 maven 項目是 java SE 的項目,我們需要手動建一個 resource 包,並且該文件應該被標記為 resource folder,因為我們需要將 cp30 的配置文件放在該包下。

新建 druid 的配置文件 druid.properties(可自定義文件名稱),並將該配置文件放在 resource 包下(只有放在 resource 包下,編譯過后你才會發現 cp30 的配置文件生成在 target 的classes 文件夾下)。最終目錄如下:

druid.properties 配置文件的內容如下:

url=jdbc:mysql://localhost:3306/test
#這個可以缺省的,會根據url自動識別
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456

##初始連接數,默認0
initialSize=10
#最大連接數,默認8
maxActive=30
#最小閑置數
minIdle=10
#獲取連接的最大等待時間,單位毫秒
maxWait=2000
#緩存PreparedStatement,默認false
poolPreparedStatements=true
#緩存PreparedStatement的最大數量,默認-1(不緩存)。大於0時會自動開啟緩存PreparedStatement,所以可以省略上一句設置
maxOpenPreparedStatements=20

此時我們就可以通過連接池來獲取數據庫連接了:

package org.example;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

public class DruidTest {
    
    @Test
    public void test01() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //數據源配置
            Properties properties=new Properties();
            //通過當前類的class對象獲取資源文件
            InputStream is = DruidTest.class.getResourceAsStream("/druid.properties");
            properties.load(is);

            //返回的是DataSource,不是DruidDataSource
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

            //獲取連接
            connection = dataSource.getConnection();

            //PreparedStatement接口
            String sql = "update user set name = 'newName' where id = 2";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

           //關閉連接。實際上是將連接歸還給連接池
            try {
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

 

3.2.1、建立druid工具類

一般在使用數據庫連接池時,我們會新建一個工具類來方便我們使用連接池。下面我們建立一個 druid 工具類,在該類中實現多個方法封裝以我們方便使用,包括:獲取數據庫連接池、獲取數據庫的連接、關閉數據庫連接資源。

代碼如下:

package org.example;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class DruidUtil {
    private static DataSource ds;

    static {
        //加載配置文件和建立連接池
        try {
            Properties pro = new Properties();
            InputStream resourceAsStream = DruidUtil.class.getClassLoader().getResourceAsStream("Druid.properties");
            pro.load(resourceAsStream);
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取數據庫連接池
     * @return
     */
    public static DataSource getDataSource(){
        return ds;
    }


    /**
     * 獲取連接池中的一個連接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }


    /**
     * 關閉數據庫的資源  三個對象都存在時
     * @param conn
     * @param res
     * @param pstmt
     */
    public static void close(Connection conn, ResultSet res, PreparedStatement pstmt){
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (res!=null){
            try {
                res.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 關閉數據庫的連接(只存在Connection和PreparedStatement對象時)
     * @param conn
     * @param pstmt
     */
    public void close(Connection conn,PreparedStatement pstmt){
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

我們就可以使用 druid 工具類來獲取數據庫連接、關閉資源。

實例代碼如下:

public class DruidTest {
    public void utilTest() {
        Connection connection = null;
        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;
        try {
            connection = DruidUtil.getConnection();   //獲取數據庫連接

            String sql = "select * from user";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()){
                Integer id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String password = resultSet.getString("password");
                System.out.println(id+" "+name+" "+password);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DruidUtil.close(connection,resultSet,preparedStatement);   //釋放資源
        }
    }
}

 


免責聲明!

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



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