前言
今天來講一下資料庫連線池技術.其實這個名詞也就是聽起來高大上一點,實際上並不是很複雜的內容,相信在我的講解下,並且自己實際的將程式碼寫一遍之後,能夠對這項技術有較為深刻的理解.廢話不多說,開始講解.
資料庫連線池技術概述
所謂的資料庫連線池技術,就是用來分配,管理,釋放資料庫連線的.你也許會問,好像我直接用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就可檢視,免費的呦!