資料庫連線池技術詳解

Roobtyan發表於2018-08-19

前言

今天來講一下資料庫連線池技術.其實這個名詞也就是聽起來高大上一點,實際上並不是很複雜的內容,相信在我的講解下,並且自己實際的將程式碼寫一遍之後,能夠對這項技術有較為深刻的理解.廢話不多說,開始講解.

資料庫連線池技術概述

所謂的資料庫連線池技術,就是用來分配,管理,釋放資料庫連線的.你也許會問,好像我直接用JDBC也能夠實現這些功能吧. 嗯,你說的沒錯,JDBC確實也可以,但是,你記不記得,我們使用JDBC技術的時候,每次用完了,是不是都會將連線關閉;等到下一次再用的時候,是不是都得將資料庫連線再開啟? 實際上,資料庫連結資源是十分寶貴的,我們在小型的專案中還看不出來,在高併發的專案中,你會發現,這樣頻繁的開啟和關閉資料庫連結是對伺服器的一種摧殘,十分影響效率. 那麼,資料庫連線池是如何做的呢? 實現思路是這樣的:在每次有訪問的時候,資料庫連線池會給使用者分配一個資料庫連線,當使用者用完了連線之後,連線池再將連線回收,放回一個連線集合中. 原理就是這樣的,我們來看一下這張圖加深印象

這裡寫圖片描述
這樣你可能還是不太清楚,而且,資料庫連線池要考慮的東西要比上面說的更復雜,不過不要害怕,我通過實際的程式碼來幫你理解一下. 備註:下面的程式碼是自己實現一個簡單的資料庫連線池,著重瞭解資料庫連線池的原理.

自己實現一個資料庫連線池

注意:下面的程式碼我是分塊講解的,你一個個貼上下來,最後肯定也是能執行的,為了方便,我會在講完之後,給出完整的實現程式碼.

  • 定義初始化連線數目,最大連線數以及當前已經連線的數目 一開始,當資料庫連線池啟動的時候,為了實現上面的需求,我們肯定是要先給出幾個已經完成的連線的,這樣使用者訪問的時候就能直接拿到了;此外,當某一段時間的訪問使用者超過我定義的連線池中的連線個數,肯定是要額外新建連線給使用者使用;當然,這個新建的連線肯定是不能無限制的,否則還是會很影響效率的,所以,我們還要定義最大的連線數.
private final int init_count = 3; //初始化連結數目
private final int max_count = 6; //最大連線數
private int current_count = 0; //到當前連線數
複製程式碼
  • 那麼,我們一開始新建的連線池要放在哪裡供使用者使用呢?肯定是要建立一個連線集合的,這樣的操作比較方便.至於為什麼要使用LinkedList這樣一個集合,一會就會介紹的.
private LinkedList<Connection> pool = new LinkedList<Connection>();
複製程式碼
  • 剛才說了,一開始我們肯定是要初始化連線給使用者使用的,那麼,就需要在連線池啟動的時候就新建一定數量的連結.分析後發現,放在建構函式中初始化連結是最好不過的了,最後再將連線放在連結集合中.
 //建構函式,初始化連結放入連線池
 public MyPool() {
     for (int i=0;i<init_count;i++){
         //記錄當前連線數
         current_count++;
         //createConnection是自定義的建立連結函式.
         Connection connection = createConnection();
         pool.addLast(connection);
     }
 }
複製程式碼
  • 建立一個新的連線,這個就沒啥好說的了,畢竟你要的連結都是從這來的.
public Connection createConnection() {
	Class.forName("com.mysql.jdbc.Driver");
	Connection connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/keyan","root","root");
	return connection;
}
複製程式碼
  • 獲取連結 當使用者來訪問的時候,我們肯定是要給使用者一個連線的,如果池中沒有連線了(所有連線均被佔用),那麼就要建立新的連線,使用createConnection()函式,當然,這個連線的個數肯定是不能超過最大連線數的.如果不滿足這兩個條件,那麼直接丟擲異常.
public Connection getConnection() {
     if (pool.size() > 0){
         //removeFirst刪除第一個並且返回
         //現在你一定看懂了我說的為什要用LinkedList了吧,因為下面的這個
         //removeFirst()方法會將集合中的第一個元素刪除,但是還會返回第一個元素
         //這樣就省去了我們很多不必要的麻煩
         return pool.removeFirst();
     }
     if (current_count < max_count){
         //記錄當前使用的連線數
         current_count++;
         //建立連結
         return createConnection();
     }
     throw new RuntimeException("當前連結已經達到最大連線數");
}
複製程式碼
  • 釋放資源 當使用者使用完了連線之後,我們要做的並不是關閉連線,而是將連線重新放入資源池LinkedList之中,這樣就省去了一遍又一遍的連線關閉.這個就是連線池的核心內容.是不是很簡單?
 public void releaseConnection(Connection connection){
    if (pool.size() < init_count){
        pool.addLast(connection);
        current_count--;
    }else {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

整個的實現過程就是這樣的,下面我把全部的程式碼貼出來,方便大家學習.

//單元測試
@Test
public class MyPool {
    private final int init_count = 3; //初始化連結數目
    private final int max_count = 6; //最大
    private int current_count = 0; //到當前連線數
    //連線池,用來存放初始化連結
    private LinkedList<Connection> pool = new LinkedList<Connection>();

    //建構函式,初始化連結放入連線池
    public MyPool() {
        for (int i=0;i<init_count;i++){
            //記錄當前連線數
            current_count++;
            Connection connection = createConnection();
            pool.addLast(connection);
        }
    }

    //建立新的連線
    public Connection createConnection() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/keyan","root","root");
            return connection;
        }catch (Exception e){
            System.out.println("資料庫連結異常");
            throw new RuntimeException();
        }
    }

    //獲取連結
    public Connection getConnection() {
        if (pool.size() > 0){
            //removeFirst刪除第一個並且返回
            return pool.removeFirst();
        }
        if (current_count < max_count){
            //記錄當前使用的連線數
            current_count++;
            //建立連結
            return createConnection();
        }
        throw new RuntimeException("當前連結已經達到最大連線數");
    }

    //釋放連結
    public void releaseConnection(Connection connection){
        if (pool.size() < init_count){
            pool.addLast(connection);
            current_count--;
        }else {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
複製程式碼

自己跑了一遍程式碼之後,你是不是發現原來看起來很複雜的技術,並不像我們想得那樣? 好了,介紹完了基本的資料庫連線池技術原理之後,我們就要介紹兩個開源的優秀的資料連線池技術. 其實,真正的資料庫連線池要考慮的東西比我們剛才寫的這玩意複雜,現階段不需要我們寫這樣複雜的東西,不過如果你感興趣的話,可以看看資料庫連線池的原始碼--沒錯,這兩個連線池都是開源的.OK,接下來就開始吧!

優秀的資料庫連線池

首先,sun公司規定,連線池技術需實現javax.sql.DataSource介面,也就是說,如果你要自己實現資料庫連線池,那麼就必須實現這個介面.是不是很牛比的樣子,實際上,作為標準制定方,sun公司還是有很多要求的,這是作為標準制定者的權利,所以,企業或者國家往往會對一些關鍵技術標準的制定打得頭破血流.額,扯遠了...

DBPC

其實,你可能不知道,如果你的tomcat經過特殊的配置,也是可以作為資料庫連線池使用的,因為tomcat內建的就是DBPC.. 想要使用DBPC,你必須匯入三個jar檔案:commons-dbcp2-2.2.0.jar,commons-pool2-2.5.0.jar,commons-logging-1.2.jar.我想,以你的聰明才智,想要獲取這三個jar檔案,一定是小菜一疊,這是一個軟體開發者的必備技能--學會如何搜尋. 使用起來就非常簡單了.使用的方式主要有兩種,一種是硬編碼的方式,就是自己手動設定各種引數,另外一種就是配置相應的配置檔案,然後載入就行了.

硬編碼實現DBPC

BasicDataSource dataSource = new BasicDataSource();
//引數配置:初始化連線數,最大連線數,連線字串,驅動,使用者,密碼
dataSource.setInitialSize(3);   //最大初始化連結
dataSource.setMaxTotal(6);      //最大連結
dataSource.setMaxIdle(3000);    //最大空閒時間
dataSource.setUrl("jdbc:mysql:///keyan");   //url
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");

//獲取連結
Connection connection = dataSource.getConnection();
connection.prepareStatement("SELECT * FROM e_person").execute();
connection.close();
複製程式碼

使用查詢的時候,只需要按照原來JDBC的操作方式就行了,這個沒啥好說的,按照我上面的配置方式實現就行了,一定不要忘記匯入包.

配置檔案方式實現

//建立properties配置檔案
Properties properties = new Properties();
//獲取檔案流
InputStream in = DBCPTest.class.getResourceAsStream("db.properties");
//載入配置檔案
properties.load(in);
//建立資料來源物件
BasicDataSource dataSource = BasicDataSourceFactory.createDataSource(properties);

//獲取連結
Connection connection = dataSource.getConnection();
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_person").executeQuery();
while (resultSet.next()){
   System.out.println(resultSet.getString("work_name"));
}
connection.close();
複製程式碼

上面是基本的操作流程,下面我們看一下這個xml檔案的配置 檔案db.properties

url=jdbc:mysql:///keyan
driverClassName=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=6
maxIdle=3000
複製程式碼

特別要注意的一點是,這個配置檔案一定要放在和你的這DBPC類放在同一個包下. 都完成了之後,直接用就可以了.

C3P0

c3p0同樣是非常優秀的連線池技術,這個需要匯入的檔案比較少,只有一個c3p0-0.9.1.2.jar.自己去網站上找一下就好了. 使用c3p0同樣是有兩種方式,分別也是硬編碼方式和配置檔案方式.

硬編碼方式

//配置相關的引數
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql:///keyan");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setInitialPoolSize(3);
dataSource.setMaxPoolSize(6);
dataSource.setMaxIdleTime(1000);

Connection connection = dataSource.getConnection();
//sql語句
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_project").executeQuery();
while (resultSet.next()){
   System.out.println(resultSet.getString("project_name"));
}
connection.close();
複製程式碼

和上面一樣,簡單的使用一下就行了,知道如何使用就行.至於更加深層次的東西,有興趣的話慢慢研究吧.

配置檔案方式

ComboPooledDataSource dataSource = new ComboPooledDataSource();
Connection connection = dataSource.getConnection();
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_project").executeQuery();
while (resultSet.next()){
    System.out.println(resultSet.getString("project_name"));
}
connection.close();
複製程式碼

乍一看,你可能會問,不是配置檔案方式實現嗎?沒錯,只是這個是隱式的呼叫,不需要你實現,只需要新建一個ComboPooledDataSource物件就行了,預設呼叫xml檔案,檔案路徑是src根目錄.也就是說,你只需要將配置檔案寫在src目錄下就行了.重點是如何寫這個xml檔案. 注意,檔名c3p0-config.xml,這個檔名不能改,必須是這個,檔案路徑必須是src根目錄,否則讀取不到.

<c3p0-config>
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="initialPoolSize">3</property>
        <property name="maxPoolSize">6</property>
        <property name="maxIdleTime">1000</property>
    </default-config>
</c3p0-config>
複製程式碼

配置檔案的引數大體上用到的就這麼多,當然,你也可以去找一下相關的資料,瞭解一下更加詳細的引數意義,這些引數,我麼日常使用足夠了.

補充內容:資料庫連線池的優點

資源重用

由於資料庫連線得以重用,避免了頻繁建立,釋放連線引起的大量效能開銷。在減少系統消耗的基礎上,另一方面也增加了系統執行環境的平穩性。

更快的系統反應速度

資料庫連線池在初始化過程中,往往已經建立了若干資料庫連線置於連線池中備用。此時連線的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連線,避免了資料庫連線初始化和釋放過程的時間開銷,從而減少了系統的響應時間

新的資源分配手段

對於多應用共享同一資料庫的系統而言,可在應用層通過資料庫連線池的配置,實現某一應用最大可用資料庫連線數的限制,避免某一應用獨佔所有的資料庫資源

統一的連線管理,避免資料庫連線洩露:

在較為完善的資料庫連線池實現中,可根據預先的佔用超時設定,強制回收被佔用連線,從而避免了常規資料庫連線操作中可能出現的資源洩露

結語

感謝您的閱讀,歡迎指正部落格中存在的問題,也可以跟我聯絡,一起進步,一起交流!

微信公眾號:進擊的程式狗 郵箱:roobtyan@outlook.com 個人部落格:roobtyan.cn 掃描下面的二維碼關注我吧,你將收穫到意想不到的東西喲…… 給大家準備了一份非常棒的JAVA的視訊教程,從JAVA基礎一直到JAVAWEB,還有非常強大的專案實戰。 就在我的微信公眾號裡,回覆java就可檢視,免費的呦!

這裡寫圖片描述

相關文章