1. 描述
連接池技術的核心思想是:連接複用,通過建立一個數據庫連接池以及一套連接使用、分配、管理策略,使得該連接池中的連接可以得到高效、安全的複用,避免了數據庫連接頻繁建立、關閉的開銷。另外,由於對JDBC中的原始連接進行了封裝,從而方便了數據庫應用對於連接的使用(特别是對於事務處理),提高了獲取數據庫連接效率,也正是因爲這個封裝層的存在,隔離了應用的本身的處理邏輯和具體數據庫訪問邏輯,使應用本身的複用成爲可能。
連接池主要由三部分組成:連接池的建立、連接池中連接使用的治理、連接池的關閉。
2. 連接池原理
2.1 連接池的建立
應用程序中建立的連接池其實是一個靜态的。所謂靜态連接池是指連接池中的連接在系統初始化時就已分配好,且不能随意關閉連接。Java中提供了很多容器類可以方便的構建連接池,如:Vector、Stack、Servlet、Bean 等,通過讀取連接屬性文件 Connections.properties 與數據庫實例建立連接。在系統初始化時,根據相應的配置創建連接并放置在連接池中,以便需要使用時能從連接池中獲取,這樣就可以避免連接随意的建立、關閉造成的開銷。
2.2 連接池的管理
連接池管理策略是連接池機制的核心。當連接池建立後,如何對連接池中的連接進行管理,解決好連接池内連接的分配和釋放,對系統的性能有很大的影響。連接的合理分配、釋放可提高連接的複用,降低了系統建立新連接的開銷,同時也加速了用戶的訪問速度。下面介紹連接池中連接的分配、釋放策略。
連接池的分配、釋放策略對於有效複用連接非常重要,我們采用的方法是一個很有名的設計模式:Reference Counting(引用記數)。該模式在複用資源方面應用的非常廣泛,把該方法運用到對於連接的分配釋放上,爲每一個數據庫連接,保留一個引用記數,用來記錄該連接的使用者的個數。
具體實現方法如下:
當客戶請求數據庫連接時,首先查看連接池中是否有空閑連接(指當前沒有分配出去的連接)。如果存在空閑連接,則把連接分配給客戶并作相應處理(即标記該連接爲正在使用,引用計數加 1)。如果沒有空閑連接,則查看當前所開的連接數是不是已經達到 maxConn(最大連接數),如果沒達到就重新創建一個連接給請求的客戶;如果達到就按設定的maxWaitTime(最大等待時間)進行等待,如果等待 maxWaitTime 後仍沒有空閑連接,就抛出無空閑連接的異常給用戶。
當客戶釋放數據庫連接時,先判斷該連接的引用次數是否超過了規定值,如果超過就删除該連接,并判斷當前連接池内總的連接數是否小於 minConn(最小連接數),若小於就将連接池充滿;如果沒超過就将該連接标記爲開放狀态,可供再次複用。可以看出正是這套策略保證了數據庫連接的有效複用,避免頻繁地建立、釋放連接所帶來的系統資源開銷。
2.3 連接池的關閉
當應用程序退出時,應關閉連接池,此時應把在連接池建立時向數據庫申請的連接對象統一歸還給數據庫(即關閉所有數據庫連接),這與連接池的建立正好是一個相反過程。
連接池分配一個連接後如定義一個數據集,點擊預覽,執行完對應的sql語句會将所占用的連接歸還連接池。
3. 連接池的配置
數據庫連接池中到底要放置多少個連接,才能使系統的性能更佳,用 minConn 和 maxConn 來限制。
minConn 是當應用啓動的時候連接池所創建的連接數,假如過大啓動将變慢,但是啓動後響應更快;假如過小啓動加快,但是最初使用的用戶将因爲連接池中沒有足夠的連接不可避免的延緩了執行速度。因此應該在開發的過程中設定較小 minConn,而在實際應用的中設定較大 minConn。maxConn 是連接池中的最大連接數,可以通過反複試驗來确定此飽和點。
爲此在連接池類 ConnectionPool 中加入兩個方法 getActiveSize() 和 getOpenSize(),ActiveSize 表示某一時間有多少連接正被使用,OpenSize 表示連接池中有多少連接被打開,反映了連接池使用的峰值。将這兩個值在日志信息中反應出來, minConn 的值應該小於平均 ActiveSize,而 maxConn 的值應該在 activeSize 和 OpenSize 之間。
4. 連接池的關鍵技術
4.1 事務處理
前面讨論的是關於使用數據庫連接進行普通的數據庫訪問。對於事務處理,情況就變得比較複雜。因爲事務本身要求原則性的保證,此時就要求對於數據庫的操作符合"All-or-Nothing"原則,即要麽全部完成,要麽什麽都不做。如果簡單的采用上述的連接複用的策略,就會發生問題,因爲沒有辦法控制屬於同一個事務的多個數據庫操作方法的動作,可能這些數據庫操作是在多個連接上進行的,并且這些連接可能被其他非事務方法複用。Connection本身具有提供了對於事務的支持,可以通過設置Connection的AutoCommit屬性爲false,顯式的調用 commit或rollback方法來實現。但是要安全、高效的進行連接複用,就必須提供相應的事務支持機制。方法是:采用顯式的事務支撐方法,每一個事務獨占一個連接。這種方法可以大大降低對於事務處理的複雜性,并且又不會妨礙連接的複用。連接管理服務提供了顯式的事務開始、結束(commit或rollback)聲明,以及一個事務注冊表,用於登記事務發起者和事務使用的連接的對應關系,通過該表,使用事務的部分和連接管理部分就隔離開,因爲該表是在運行時根據實際的調用情況動态生成的。事務使用的連接在該事務運行中不能被複用。在實現中,用戶标識是通過使用者所在的線程來标識的。後面的所有對於數據庫的訪問都是通過查找該注冊表,使用已經分配的連接來完成的。當事務結束時,從注冊表中删除相應表項。
4.2 并發
爲了使連接管理服務有更大的通用性,我們必須要考慮到多線程環境,即并發問題。在一個多線程的環境下,必須要保證連接管理自身數據的一緻性和連接内部數據的一緻性,在這方面Java提供很好的支持(synchronized關鍵字),這樣就很容易使連接管理成爲線程安全的。
4.3 多數據庫服務器
在實際應用中,應用程序常常需要訪問多個不同的數據庫。如何通過同一個連接池訪問不同的數據庫,是應用程序需要解決的一個核心問題。下面介紹一種解決的途徑:首先,定義一個數據庫連接池參數的類,定義了數據庫的JDBC驅動程序類名,連接的URL以及用戶名口令等等一些信息,該類是用於初始化連接池的參數:public class ConnectionParam implements Serializable{//各初始化參數的定義}其次是連接池的工廠類ConnectionFactory,通過該類将一個連接池對象與一個名稱對應起來,使用者通過該名稱就可以獲取指定的連接池對象,實現的主要代碼如下:
public class ConnectionParam implements Serializable{
//各初始化參數的定義
}
其次是連接池的工廠類ConnectionFactory,通過該類将一個連接池對象與一個名稱對應起來,使用者通過該名稱就可以獲取指定的連接池對象,實現的主要代碼如下:
public class ConnectionFactory{
static Hashtable connectionPools = //用來保存數據源名和連接池對象的關系
public static DataSource lookup(String dataSourceName) throws NameNotFoundException{
//查找名字爲dataSourceName的數據源
}
public static DataSource bind(Stringname, ConnectionParam param) throws Exception{
//将名字name與使用param初始化的連接池對象綁定
}
}
public static void unbind(String name) throws NameNotFound Exception{
//将與名字name綁定的連接池對象删除
}
5. 連接池應用的實現
一個完整的連接池應用包括三個部分:DBConnectionPool類,負責從連接池獲取(或創建)連接、将連接返回給連接池、系統關閉時關閉所有連接釋放所有資源;DBConnectionManager類,負責裝載和注冊JDBC驅動、根據屬性文件中定義的屬性創建DBConnectionPool、跟蹤應用程序對連接池的引用等;應用程序對連接池的使用。
本文實現的數據庫連接池包括一個管理類DBConnectionManager,負責提供與多個連接池對象(DBConnectionPool類)之間的接口。每一個連接池對象管理一組封裝過的JDBC連接對象Conn,封裝過的JDBC連接對象Conn可以被任意數量的Model層的組件共享。
類Conn 的設計很簡單,如下所示:
Class Conn {
Private java. sgl .Connection con; //數據庫連接對象
Public Boolean inUse ; //是否被使用
Public long lastAccess; //最近一次釋放該連接的時間
Public int useCount; // 被使用次數
}
下面是實現連接池的主要代碼:
// 初始化數據庫連接池
public static synchronized void FastInitPool()
throws Exception {
try { Class.forName(driver);
for (int i=0; i<size; i++) {
Connection con = createConnection();
if (con!=null) addConnection(con);
} } }
// 向連接池對象中添加數據庫連接
private static void addConnection(Connection con) {
if (pool==null||pool1==null) {
pool=new Vector(size);
pool1=new Vector(size); }
pool.addElement(con);
pool1.addElement("false"); }
// 獲取數據庫連接
public static synchronized Connection getConn()
throws Exception {
Connection conn = null;
try { if (driver = null)
FastInitPool();
// 獲得一個可用的(空閑的)連接
for (int i = 0; i < pool.size(); i++) {
conn = (Connection)pool.elementAt(i);
if (pool1.elementAt(i)=="false") {
pool1.set(i,"true");
//System.out.println("從連接池中獲取第"+(i+1)+"個空閑連接");
return conn;
}
}
//如果沒有可用連接,且已有連接數小於最大連接數限制,則創建并增加一個新連接到連接池
conn = createConnection();
pool.addElement(conn);
pool1.addElement("true");
// System.out.println(" 所有連接都在使用,在連接池中再創建一個新連接");
}
catch (Exception e) {
System.err.println(e.getMessage());
throw new Exception(e.getMessage());
}
return conn; //返回一個有效的新連接
}
public Connection getConnection(String strDriver, String strUrl, String strUserName, String strPassWord)
throws SQLException{
try{ Class.forName(strDriver);
conn = DriverManager.getConnection(strUrl, strUserName, strPassWord); }
return conn; }