連線池的建立
應用程式中建立的連線池其實是一個靜態的。所謂靜態連線池是指連線池中的連線在
系統初始化時就已分配好,且不能隨意關閉連線。
Java中提供了很多容器類可以方便的構建
連線池,如:Vector、Stack、
Servlet、Bean等,通過讀取連線屬性檔案Connections.properties與資料庫實例建立連線。在系統初始化時,根據相應的配置創建連線並放置在連線池中,以便需要使用時能從連線池中獲取,這樣就可以避免連線隨意的建立、關閉造成的開銷。
連線池的管理
連線池管理策略是連線池機制的核心。當連線池建立後,如何對連線池中的連線進行管理,解決好連線池
內連線的分配和釋放,對系統的性能有很大的影響。連線的合理分配、釋放可提高連線的復用,降低了系統建立新連線的開銷,同時也加速了用戶的訪問速度。下面介紹連線池中連線的分配、釋放策略。
連線池的分配、釋放策略對於有效復用連線非常重要,我們採用的方法是一個很有名的設計模式:Reference Counting(引用記數)。該模式在復用資源方面套用的非常廣泛,把該方法運用到對於連線的分配釋放上,為每一個資料庫連線,保留一個引用記數,用來記錄該連線的使用者的個數。具體的實現方法是:
當客戶請求資料庫連線時,首先查看連線池中是否有空閒連線(指當前沒有分配出去的連線)。如果存在空閒連線,則把連線分配給客戶並作相應處理(即標記該連線為正在使用,
引用計數加1)。如果沒有空閒連線,則查看當前所開的連線數是不是已經達到maxConn(
最大連線數),如果沒達到就重新創建一個連線給請求的客戶;如果達到就按設定的maxWaitTime(最大等待時間)進行等待,如果等待maxWaitTime後仍沒有空閒連線,就拋出無空閒連線的異常給用戶。
當客戶釋放資料庫連線時,先判斷該連線的引用次數是否超過了規定值,如果超過就刪除該連線,並判斷當前連線池內總的連線數是否小於minConn(最小連線數),若小於就將連線池充滿;如果沒超過就將該連線標記為開放狀態,可供再次復用。可以看出正是這套策略保證了資料庫連線的有效復用,避免頻繁地建立、釋放連線所帶來的系統資源開銷。
連線池的關閉
當應用程式退出時,應關閉
連線池,此時應把在連線池建立時向資料庫申請的連線對象統一歸還給資料庫(即關閉所有資料庫連線),這與連線池的建立正好是一個相反過程。
連線池的配置
資料庫連線池中到底要放置多少個連線,才能使系統的性能更佳,用minConn和maxConn來限制。minConn是當套用啟動的時候連線池所創建的連線數,如果過大啟動將變慢,但是啟動後回響更快;如果過小啟動加快,但是最初使用的用戶將因為連線池中沒有足夠的連線不可避免的延緩了執行速度。因此應該在開發的過程中設定較小minConn,而在實際套用的中設定較大minConn。maxConn是連線池中的
最大連線數,可以通過反覆試驗來確定此飽和點。為此在連線池類ConnectionPool中加入兩個方法getActiveSize()和getOpenSize(),ActiveSize 表示某一時間有多少連線正被使用,OpenSize表示連線池中有多少連線被打開,反映了
連線池使用的峰值。將這兩個值在日誌信息中反應出來, minConn的值應該小於平均ActiveSize,而maxConn的值應該在activeSize和OpenSize之間。
連線池的關鍵技術
事務處理
前面討論的是關於使用資料庫連線進行普通的資料庫訪問。對於
事務處理,情況就變得比較複雜。因為事務本身要求原則性的保證,此時就要求對於資料庫的操作符合"All-or-Nothing"原則,即要么全部完成,要么什麼都不做。如果簡單的採用上述的連線復用的策略,就會發生問題,因為沒有辦法控制屬於同一個事務的多個資料庫操作方法的動作,可能這些資料庫操作是在多個連線上進行的,並且這些連線可能被其他非事務方法復用。Connection本身具有提供了對於事務的支持,可以通過設定Connection的AutoCommit屬性為false,顯式的調用 commit或rollback方法來實現。但是要安全、高效的進行連線復用,就必須提供相應的事務支持機制。方法是:採用顯式的事務支撐方法,每一個事務獨占一個連線。這種方法可以大大降低對於
事務處理的複雜性,並且又不會妨礙連線的復用。
連線管理服務提供了顯式的
事務開始、結束(
commit或rollback)聲明,以及一個事務
註冊表,用於登記事務發起者和事務使用的連線的對應關係,通過該表,使用事務的部分和連線管理部分就隔離開,因為該表是在運行時根據實際的調用情況動態生成的。事務使用的連線在該事務運行中不能被復用。在實現中,
用戶標識是通過使用者所在的執行緒來標識的。後面的所有對於資料庫的訪問都是通過查找該註冊表,使用已經分配的連線來完成的。當
事務結束時,從註冊表中刪除相應表項。
並發
為了使連線管理服務有更大的通用性,我們必須要考慮到多執行緒環境,即並發問題。在一個多執行緒的環境下,必須要保證連線管理自身數據的一致性和連線內部數據的一致性,在這方面
Java提供很好的支持(
synchronized關鍵字),這樣就很容易使連線管理成為
執行緒安全的。
多資料庫伺服器
在實際套用中,應用程式常常需要訪問多個不同的資料庫。如何通過同一個
連線池訪問不同的資料庫,是應用程式需要解決的一個核心問題。下面介紹一種解決的途徑:
首先,定義一個
資料庫連線池參數的類,定義了資料庫的
JDBC驅動程式類名,連線的URL以及用戶名口令等等一些信息,該類是用於初始化連線池的參數:
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綁定的連線池對象刪除
}
連線池套用的實現
一個完整的
連線池套用包括三個部分: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) {
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; }