羅金濤 李躍新
[摘要]數(shù)據(jù)庫(kù)連接池是一種非常高效實(shí)用的技術(shù)。對(duì)數(shù)據(jù)庫(kù)連接池的基本實(shí)現(xiàn)原理進(jìn)行分析,并給出初步的算法實(shí)現(xiàn)。
[關(guān)鍵詞]數(shù)據(jù)源 連接池 連接 JAVA JDBC
中圖分類號(hào):TP3文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào):1671-7597(2009)0310052-02
一、引言
在任何的項(xiàng)目開發(fā)過程中,都離不開對(duì)數(shù)據(jù)的操作,這一系列的動(dòng)作最終都體現(xiàn)在底層和數(shù)據(jù)庫(kù)的交互,在常規(guī)的jdbc數(shù)據(jù)庫(kù)操作到時(shí)候,總是要反復(fù)的打開和關(guān)閉對(duì)數(shù)據(jù)庫(kù)的連接,但是這個(gè)過程是一個(gè)相當(dāng)消耗系統(tǒng)資源的過程,對(duì)于小的一般程序環(huán)境來說,似乎感覺不到性能的影響,但是對(duì)于一些多層結(jié)構(gòu)大型企業(yè)級(jí)的應(yīng)用程序環(huán)境來說,這種反復(fù)消耗系統(tǒng)資源的弊端就開始體現(xiàn)出來了。于是很多服務(wù)器產(chǎn)品都提供了連接池技術(shù)來提高數(shù)據(jù)庫(kù)操作的性能,通過連接池技術(shù),可以盡可能多對(duì)內(nèi)存資源進(jìn)行重用,大大節(jié)約了內(nèi)存的開銷,同時(shí)能夠支持更多的客戶服務(wù)和提高程序的運(yùn)行效率,最終從整體上提高到了服務(wù)器的運(yùn)行效率。
二、連接池的產(chǎn)生及基本原理
在早期的java項(xiàng)目開發(fā)過程中,當(dāng)項(xiàng)目開發(fā)完成之后,運(yùn)行的時(shí)候發(fā)現(xiàn)隨著訪問量的增長(zhǎng),系統(tǒng)的性能下降得特別的嚴(yán)重,實(shí)踐表面,導(dǎo)致系統(tǒng)性能下降的原因就是發(fā)生在數(shù)據(jù)庫(kù)訪問階段,而在此階段,有一個(gè)反復(fù)執(zhí)行的動(dòng)作,那就是建立和關(guān)閉Connection對(duì)象,由于關(guān)閉操作是在所有的數(shù)據(jù)庫(kù)動(dòng)作執(zhí)行之后才進(jìn)行,它主要目的是釋放資源,而建立Connection對(duì)象的操作是在加載了數(shù)據(jù)庫(kù)驅(qū)動(dòng)之后,數(shù)據(jù)庫(kù)操作之前必須完成的動(dòng)作,而且這個(gè)對(duì)象的生成過程比較耗時(shí),如果每次操作數(shù)據(jù)庫(kù)都臨時(shí)生成一個(gè)Connection對(duì)象,那么隨著并發(fā)訪問量的增加,必然會(huì)影響系統(tǒng)的性能,所以Connection對(duì)象的生成就是影響系統(tǒng)性能的主要原因,為了解決這個(gè)瓶頸,一個(gè)普遍可行的解決方案,就是在應(yīng)用啟動(dòng)的時(shí)候,一次性生成若干個(gè)Connection對(duì)象,而不是在每次操作數(shù)據(jù)庫(kù)的時(shí)候去臨時(shí)生成,這些一次性生成的若干個(gè)Connection對(duì)象就可以常駐內(nèi)存中可以反復(fù)被使用,這樣就避免了Connection對(duì)象的創(chuàng)建過程耗時(shí)的缺陷,從而使得數(shù)據(jù)庫(kù)訪問速度得到了很大的提升,瓶頸有效的得到了緩解,這就是數(shù)據(jù)庫(kù)連接池技術(shù)的產(chǎn)生背景。
圖1顯示了數(shù)據(jù)庫(kù)連接池技術(shù)的基本原理。
當(dāng)客戶端訪問需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行請(qǐng)求,需要先建立數(shù)據(jù)庫(kù)的連接對(duì)象Connection,此時(shí)是向數(shù)據(jù)源對(duì)象(DataSource)進(jìn)行請(qǐng)求,而數(shù)據(jù)源對(duì)象預(yù)先一次性和數(shù)據(jù)庫(kù)(Database)建立好了若干個(gè)連接(Connection),并將這些連接組成一個(gè)連接池(Connection Pool),由應(yīng)用程序動(dòng)態(tài)的對(duì)連接池中的連接進(jìn)行申請(qǐng),使用和釋放。當(dāng)請(qǐng)求的對(duì)象用完以后,不需要進(jìn)行關(guān)閉,直接返回給連接池當(dāng)中,以便其他請(qǐng)求可以重復(fù)使用。當(dāng)并發(fā)的請(qǐng)求的數(shù)量多于連接池的連接數(shù)的時(shí)候,這些請(qǐng)求先在排隊(duì)請(qǐng)求隊(duì)列當(dāng)中排隊(duì)等候,然后由應(yīng)用程序根據(jù)連接池中的使用情況,動(dòng)態(tài)的來增加連接數(shù)。
三、數(shù)據(jù)庫(kù)連接池基本功能的算法實(shí)現(xiàn)
因?yàn)檫B接池的建立已經(jīng)在它基礎(chǔ)上的操作和對(duì)集合類型的操作非常相似,因此我們可以將整個(gè)實(shí)現(xiàn)過程通過對(duì)集合類型的對(duì)象操作來進(jìn)行模擬實(shí)現(xiàn),首先,我們要建立一個(gè)數(shù)據(jù)庫(kù)連接池類ConnectionPool,為了控制在取得連接的過程中只生成一個(gè)類的實(shí)例,應(yīng)該用單態(tài)模式(Singleton)來設(shè)計(jì)這個(gè)連接池類,通過單態(tài)模式可以節(jié)省內(nèi)存的開銷,同時(shí)也降低了Java虛擬機(jī)(JVM)進(jìn)行垃圾回收的開銷。首先需要定義一個(gè)私有的構(gòu)造方法,然后通過保留一個(gè)公開的靜態(tài)方法來取得這個(gè)類的實(shí)例,另外要有一個(gè)容器來保存生成的連接,在JAVA中,我們一般使用集合類型的對(duì)象,例如Vector,ArrayList都可以,但是考慮到多線程的安全性,我們一般使用Vector來進(jìn)行封裝,其中用到的代碼片段如下:
首先定義一個(gè)名為ConnectionPool的類,里面用到的一系列屬性和幾個(gè)主要方法如下:
private Vector pool; //Vector類型的連接池對(duì)象
private String url; //數(shù)據(jù)庫(kù)訪問的url
private String username; //數(shù)據(jù)庫(kù)訪問的用戶名
private String password; //數(shù)據(jù)庫(kù)的密碼
private String driver; //數(shù)據(jù)庫(kù)的驅(qū)動(dòng)類
private int poolSize; //連接池的大小,即連接的數(shù)量
private int initSize; //初始化連接的數(shù)量
private int poolSizeIncrement; //當(dāng)連接數(shù)不夠時(shí)的容量的增量
private static ConnectionPool instance=null; //定義一個(gè)靜態(tài)的連接池類變量
//私有構(gòu)造方法,讀取屬性文件的內(nèi)容,建立指定數(shù)量的連接池中的初始連接。
private ConnectionPool(){
//讀取初始化配置參數(shù),我們可以將數(shù)據(jù)庫(kù)參數(shù)保存在properties的屬性文件中,或者在xml文件中加以配置,推薦使用xml方式,可以提高系統(tǒng)解耦。
… … … …
//實(shí)例化一個(gè)向量,當(dāng)作數(shù)據(jù)庫(kù)連接池對(duì)象的容器。
Vector pool=new Vector(poolSize, poolSizeIncrement);
//在連接池中創(chuàng)建指定數(shù)目的數(shù)據(jù)庫(kù)連接對(duì)象
for(int i=0;i try{ Class.forName(driver); Connection conn=java.sql.DriverManager. getConnection(url,username,password); }catch (SQLException e) { e.printStackTrace(); }catch (ClassNotFoundException e) { e.printStackTrace(); } //將生成的Connection對(duì)象放入Vector中 pool.add(conn); } } //靜態(tài)方法,用于初始化該連接池類的實(shí)例。 public static ConnectionPool getInstance(){ if(instance==null){ instance=new ConnectionPool(); } return instance; } 當(dāng)我們通過上面的方法調(diào)用這個(gè)類的私有構(gòu)造函數(shù)后,它就會(huì)根據(jù)配置文件中的參數(shù),創(chuàng)建指定數(shù)量的數(shù)據(jù)庫(kù)連接,如果有程序需要進(jìn)行數(shù)據(jù)庫(kù)訪問,那么可以給它分配一個(gè)連接,因?yàn)樗械倪B接對(duì)象都保存在一個(gè)Vector中,對(duì)于每個(gè)需要取得連接的請(qǐng)求,首先就讓它取得get(0)位置上連接對(duì)象,并將它從Vector中刪除,從而保證Vector中剩下的都是可用的連接。相反,當(dāng)一個(gè)連接用完以后,就應(yīng)該釋放這個(gè)連接,并且將這個(gè)剛剛釋放的連接重新加入到連接池Vector中,其具體代碼如下:
//返回連接池中的一個(gè)數(shù)據(jù)庫(kù)連接,用戶自己來調(diào)用以取得數(shù)據(jù)庫(kù)的連接對(duì)象。
public synchronized Connection getConnection(){
if(pool.size()>0){
Connection conn=(Connection)pool.get(0);
//取得連接后,從連接池中刪除
pool.remove(conn);
return conn;
}else{
return null;
}
}
//釋放連接方法,需要用戶自己來調(diào)用這個(gè)方法。
public synchronized void closeConnection(Connection conn){
//返回到連接池中,即將這個(gè)Conncetion對(duì)象加入到Vector中最前面。
pool.add(0,conn);
}
如果最后我們的要關(guān)閉所有的連接,那么就要直接關(guān)閉數(shù)據(jù)庫(kù)連接池,那么可以通過遍歷,依次連接池對(duì)象中的每個(gè)連接,同時(shí)可以從vector對(duì)象中移除,代碼如下所示:
publicsynchronized void closePool(){
//通過遍歷的方式依次關(guān)閉Conncetion對(duì)象
for(int i=0;i try{ ((Connection)pool.get(i)).close(); }catch(SQLException e){ e.printStackTrace(); } //從Vector中移除 pool.remove(i); } } 以上就是基于數(shù)據(jù)庫(kù)連接池技術(shù)的基本原理及用java代碼的算法實(shí)現(xiàn)過程,當(dāng)我們需要使用連接池技術(shù)時(shí),首先我們通過ConnectionPool.getI nstance()方法取得連接池實(shí)例,然后會(huì)自動(dòng)調(diào)用私有的構(gòu)造方法ConnectionPool()取得相應(yīng)數(shù)據(jù)庫(kù)連接參數(shù),并在Vector中生成指定數(shù)目的Conncetion對(duì)象,在程序中我們就可以通過調(diào)用getConnection()方法來取得連接了,因?yàn)榇藭r(shí)的連接不是臨時(shí)生成的,而是在連接池初始化時(shí)就生成了,所以這個(gè)取得Conncetion對(duì)象的過程的效率非常高,最后我們可以調(diào)用closeConnection(Connection conn)和closePool()方法依次關(guān)閉Conncetion和連接池。測(cè)試表明,通過連接池技術(shù)的來訪問數(shù)據(jù)庫(kù)比不使用連接池技術(shù)時(shí)效率要高很多,當(dāng)數(shù)據(jù)庫(kù)訪問量大時(shí),采用連接池技術(shù)的優(yōu)勢(shì)更加明顯。 目前可用的數(shù)據(jù)庫(kù)連接池組件也很多,例如C3PO,DBCP,PROXOOL等都是一些優(yōu)秀的連接池組件,我們只需要在程序中引入相關(guān)的類庫(kù),然后通過相關(guān)的參數(shù)配置或者實(shí)現(xiàn)相應(yīng)的接口和方法,都可以很方便地在實(shí)際的項(xiàng)目中使用它們提供的數(shù)據(jù)庫(kù)連接池技術(shù)了,另外在很多服務(wù)器中,如Tomcat,Jboss,WebLogic,WebSphere都內(nèi)置提供了對(duì)數(shù)據(jù)庫(kù)連接池的支持,例如在Tomcat中本身也帶有連接池的功能,它是通過配置數(shù)據(jù)源(DataSource)參數(shù)來實(shí)現(xiàn)的連接池功能,通過在配置文件的相應(yīng)位置加入如下代碼 DataSource ds = (DataSource)context.lookup(""); con=ds.getConnection(); 值得特別注意的是,有了這個(gè)連接,我們可以像平時(shí)一樣操作數(shù)據(jù)庫(kù)了,而且我們可以執(zhí)行con.close();讓連接池收回這個(gè)連接,但和普通連接不同的是,此時(shí)并沒有關(guān)閉到數(shù)據(jù)庫(kù)的物理連接,所以下次請(qǐng)求的時(shí)候不會(huì)重新生成新的連接,這也是使用連接池技術(shù)的好處,以上數(shù)據(jù)源的配置也是建立在連接池技術(shù)的基本原理之上的,例如,name,username,passw Ord,driverClassName,url等參數(shù)實(shí)際上就是和數(shù)據(jù)庫(kù)訪問相關(guān)的參數(shù),另外,maxActive代表最大連接數(shù),maxIdle表示最大空閑數(shù),maxWait代表最大等待數(shù),這些額外的參數(shù)只是各個(gè)服務(wù)器廠商提供的連接池的額外屬性。 四、結(jié)束語(yǔ) 本文是從數(shù)據(jù)庫(kù)連接池最基本的原理著手,從最基本的層面分析了數(shù)據(jù)庫(kù)連接池技術(shù)的核心原理,并用Java語(yǔ)言實(shí)現(xiàn)了核心代碼,目前的數(shù)據(jù)庫(kù)連接池組件很豐富,功能也各不相同,但是其核心原理都差不多,都是在數(shù)據(jù)庫(kù)連接池技術(shù)的最基本的功能上的不斷完善與創(chuàng)新。 參考文獻(xiàn): [1]閻宏,Java與模式[M].北京:電子工業(yè)出版社,2002年10月.209-227. [2]李剛,輕量級(jí)J2EE企業(yè)應(yīng)用實(shí)戰(zhàn)[M].北京:電子工業(yè)出版社,2007年04月.390-399. [3]劉曉華、張健、周慧貞,JSP應(yīng)用開發(fā)詳解[M].北京:電子工業(yè)出版社,2007年07月.323-327. 作者簡(jiǎn)介: 羅金濤,男,漢族,湖北咸寧人,碩士,研究方向:人工智能,并行計(jì)算;李躍新,男,漢族,湖北武漢人,副教授,研究方向:人工智能,并行計(jì)算。