连结池详解
到目前為目,JDBC2的連結池只是一個接口,沒有真正的實現,JDBC3正在開發中,
據報已經支持連結池,但..........
JDBC3用了JNDI技術,連結池的配置可以讓一個高手都煩死.
目前第三方已經實現的連結池當然是poolman,1.0版對一般用戶來說已經足夠用了.配置也簡單,2.0版雖然增加了一些功能,但配置也是采用JNDI,對RMI和EJB不懂的朋友可能很煩.建議用1.0的了.
如果有興趣,自己也可以實現連結池,最關鍵的技術也就是把連結作為參數傳給一個BEAN,用完后返回這個參數連結而不是關閉.
下面是一個簡單的實現:
DBConnectionManager.java程序清單如下:
????
????001?import?java.io.*;
????002?import?java.sql.*;
????003?import?java.util.*;
????004?import?java.util.Date;
????005
????006?/**
????007?*?管理類DBConnectionManager支持對一個或多個由屬性文件定義的數據庫連接
????008?*?池的訪問.客戶程序可以調用getInstance()方法訪問本類的唯一實例.
????009?*/
????010?public?class?DBConnectionManager?{
????011?static?private?DBConnectionManager?instance;?//?唯一實例
????012?static?private?int?clients;
????013
????014?private?Vector?drivers?=?new?Vector();
????015?private?PrintWriter?log;
????016?private?Hashtable?pools?=?new?Hashtable();
????017
????018?/**
????019?*?返回唯一實例.如果是第一次調用此方法,則創建實例
????020?*
????021?*?@return?DBConnectionManager?唯一實例
????022?*/
????023?static?synchronized?public?DBConnectionManager?getInstance()?{
????024?if?(instance?==?null)?{
????025?instance?=?new?DBConnectionManager();
????026?}
????027?clients++;
????028?return?instance;
????029?}
????030
????031?/**
????032?*?建構函數私有以防止其它對象創建本類實例
????033?*/
????034?private?DBConnectionManager()?{
????035?init();
????036?}
????037
????038?/**
????039?*?將連接對象返回給由名字指定的連接池
????040?*
????041?*?@param?name?在屬性文件中定義的連接池名字
????042?*?@param?con?連接對象
????043?*/
????044?public?void?freeConnection(String?name,?Connection?con)?{
????045?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????046?if?(pool?!=?null)?{
????047?pool.freeConnection(con);
????048?}
????049?}
????050
????051?/**
????052?*?獲得一個可用的(空閑的)連接.如果沒有可用連接,且已有連接數小于最大連接數
????053?*?限制,則創建并返回新連接
????054?*
????055?*?@param?name?在屬性文件中定義的連接池名字
????056?*?@return?Connection?可用連接或null
????057?*/
????058?public?Connection?getConnection(String?name)?{
????059?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????060?if?(pool?!=?null)?{
????061?return?pool.getConnection();
????062?}
????063?return?null;
????064?}
????065
????066?/**
????067?*?獲得一個可用連接.若沒有可用連接,且已有連接數小于最大連接數限制,
????068?*?則創建并返回新連接.否則,在指定的時間內等待其它線程釋放連接.
????069?*
????070?*?@param?name?連接池名字
????071?*?@param?time?以毫秒計的等待時間
????072?*?@return?Connection?可用連接或null
????073?*/
????074?public?Connection?getConnection(String?name,?long?time)?{
????075?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????076?if?(pool?!=?null)?{
????077?return?pool.getConnection(time);
????078?}
????079?return?null;
????080?}
????081
????082?/**
????083?*?關閉所有連接,撤銷驅動程序的注冊
????084?*/
????085?public?synchronized?void?release()?{
????086?//?等待直到最后一個客戶程序調用
????087?if?(--clients?!=?0)?{
????088?return;
????089?}
????090
????091?Enumeration?allPools?=?pools.elements();
????092?while?(allPools.hasMoreElements())?{
????093?DBConnectionPool?pool?=?(DBConnectionPool)?allPools.nextElement();
????094?pool.release();
????095?}
????096?Enumeration?allDrivers?=?drivers.elements();
????097?while?(allDrivers.hasMoreElements())?{
????098?Driver?driver?=?(Driver)?allDrivers.nextElement();
????099?try?{
????100?DriverManager.deregisterDriver(driver);
????101?log("撤銷JDBC驅動程序?"?+?driver.getClass().getName()+"的注冊\\");
????102?}
????103?catch?(SQLException?e)?{
????104?log(e,?"無法撤銷下列JDBC驅動程序的注冊:?"?+?driver.getClass().getName());
????105?}
????106?}
????107?}
????108
????109?/**
????110?*?根據指定屬性創建連接池實例.
????111?*
????112?*?@param?props?連接池屬性
????113?*/
????114?private?void?createPools(Properties?props)?{
????115?Enumeration?propNames?=?props.propertyNames();
????116?while?(propNames.hasMoreElements())?{
????117?String?name?=?(String)?propNames.nextElement();
????118?if?(name.endsWith(".url"))?{
????119?String?poolName?=?name.substring(0,?name.lastIndexOf("."));
????120?String?url?=?props.getProperty(poolName?+?".url");
????121?if?(url?==?null)?{
????122?log("沒有為連接池"?+?poolName?+?"指定URL");
????123?continue;
????124?}
????125?String?user?=?props.getProperty(poolName?+?".user");
????126?String?password?=?props.getProperty(poolName?+?".password");
????127?String?maxconn?=?props.getProperty(poolName?+?".maxconn",?"0");
????128?int?max;
????129?try?{
????130?max?=?Integer.valueOf(maxconn).intValue();
????131?}
????132?catch?(NumberFormatException?e)?{
????133?log("錯誤的最大連接數限制:?"?+?maxconn?+?"?.連接池:?"?+?poolName);
????134?max?=?0;
????135?}
????136?DBConnectionPool?pool?=
????137?new?DBConnectionPool(poolName,?url,?user,?password,?max);
????138?pools.put(poolName,?pool);
????139?log("成功創建連接池"?+?poolName);
????140?}
????141?}
????142?}
????143
????144?/**
????145?*?讀取屬性完成初始化
????146?*/
????147?private?void?init()?{
????148?InputStream?is?=?getClass().getResourceAsStream("/db.properties");
????149?Properties?dbProps?=?new?Properties();
????150?try?{
????151?dbProps.load(is);
????152?}
????153?catch?(Exception?e)?{
????154?System.err.println("不能讀取屬性文件.?"?+
????155?"請確保db.properties在CLASSPATH指定的路徑中");
????156?return;
????157?}
????158?String?logFile?=?dbProps.getProperty("logfile",?"DBConnectionManager.log");
????159?try?{
????160?log?=?new?PrintWriter(new?FileWriter(logFile,?true),?true);
????161?}
????162?catch?(IOException?e)?{
????163?System.err.println("無法打開日志文件:?"?+?logFile);
????164?log?=?new?PrintWriter(System.err);
????165?}
????166?loadDrivers(dbProps);
????167?createPools(dbProps);
????168?}
????169
????170?/**
????171?*?裝載和注冊所有JDBC驅動程序
????172?*
????173?*?@param?props?屬性
????174?*/
????175?private?void?loadDrivers(Properties?props)?{
????176?String?driverClasses?=?props.getProperty("drivers");
????177?StringTokenizer?st?=?new?StringTokenizer(driverClasses);
????178?while?(st.hasMoreElements())?{
????179?String?driverClassName?=?st.nextToken().trim();
????180?try?{
????181?Driver?driver?=?(Driver)
????182?Class.forName(driverClassName).newInstance();
????183?DriverManager.registerDriver(driver);
????184?drivers.addElement(driver);
????185?log("成功注冊JDBC驅動程序\\"?+?driverClassName);
????186?}
????187?catch?(Exception?e)?{
????188?log("無法注冊JDBC驅動程序:?"?+
????189?driverClassName?+?",?錯誤:?"?+?e);
????190?}
????191?}
????192?}
????193
????194?/**
????195?*?將文本信息寫入日志文件
????196?*/
????197?private?void?log(String?msg)?{
????198?log.println(new?Date()?+?":?"?+?msg);
????199?}
????200
????201?/**
????202?*?將文本信息與異常寫入日志文件
????203?*/
????204?private?void?log(Throwable?e,?String?msg)?{
????205?log.println(new?Date()?+?":?"?+?msg);
????206?e.printStackTrace(log);
????207?}
????208
????209?/**
????210?*?此內部類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最
????211?*?大連接數為止.在返回連接給客戶程序之前,它能夠驗證連接的有效性.
????212?*/
????213?class?DBConnectionPool?{
????214?private?int?checkedOut;
????215?private?Vector?freeConnections?=?new?Vector();
????216?private?int?maxConn;
????217?private?String?name;
????218?private?String?password;
????219?private?String?URL;
????220?private?String?user;
????221
????222?/**
????223?*?創建新的連接池
????224?*
????225?*?@param?name?連接池名字
????226?*?@param?URL?數據庫的JDBC?URL
????227?*?@param?user?數據庫帳號,或?null
????228?*?@param?password?密碼,或?null
????229?*?@param?maxConn?此連接池允許建立的最大連接數
????230?*/
????231?public?DBConnectionPool(String?name,?String?URL,?String?user,?String?password,
????232?int?maxConn)?{
????233?this.name?=?name;
????234?this.URL?=?URL;
????235?this.user?=?user;
????236?this.password?=?password;
????237?this.maxConn?=?maxConn;
????238?}
????239
????240?/**
????241?*?將不再使用的連接返回給連接池
????242?*
????243?*?@param?con?客戶程序釋放的連接
????244?*/
????245?public?synchronized?void?freeConnection(Connection?con)?{
????246?//?將指定連接加入到向量末尾
????247?freeConnections.addElement(con);
????248?checkedOut--;
????249?notifyAll();
????250?}
????251
????252?/**
????253?*?從連接池獲得一個可用連接.如沒有空閑的連接且當前連接數小于最大連接
????254?*?數限制,則創建新連接.如原來登記為可用的連接不再有效,則從向量刪除之,
????255?*?然后遞歸調用自己以嘗試新的可用連接.
????256?*/
????257?public?synchronized?Connection?getConnection()?{
????258?Connection?con?=?null;
????259?if?(freeConnections.size()?>?0)?{
????260?//?獲取向量中第一個可用連接
????261?con?=?(Connection)?freeConnections.firstElement();
????262?freeConnections.removeElementAt(0);
????263?try?{
????264?if?(con.isClosed())?{
????265?log("從連接池"?+?name+"刪除一個無效連接");
????266?//?遞歸調用自己,嘗試再次獲取可用連接
????267?con?=?getConnection();
????268?}
????269?}
????270?catch?(SQLException?e)?{
????271?log("從連接池"?+?name+"刪除一個無效連接");
????272?//?遞歸調用自己,嘗試再次獲取可用連接
????273?con?=?getConnection();
????274?}
????275?}
????276?else?if?(maxConn?==?0?||?checkedOut?<?maxConn)?{
????277?con?=?newConnection();
????278?}
????279?if?(con?!=?null)?{
????280?checkedOut++;
????281?}
????282?return?con;
????283?}
????284
????285?/**
????286?*?從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間
????287?*?參見前一個getConnection()方法.
????288?*
????289?*?@param?timeout?以毫秒計的等待時間限制
????290?*/
????291?public?synchronized?Connection?getConnection(long?timeout)?{
????292?long?startTime?=?new?Date().getTime();
????293?Connection?con;
????294?while?((con?=?getConnection())?==?null)?{
????295?try?{
????296?wait(timeout);
????297?}
????298?catch?(InterruptedException?e)?{}
????299?if?((new?Date().getTime()?-?startTime)?>=?timeout)?{
????300?//?wait()返回的原因是超時
????301?return?null;
????302?}
????303?}
????304?return?con;
????305?}
????306
????307?/**
????308?*?關閉所有連接
????309?*/
????310?public?synchronized?void?release()?{
????311?Enumeration?allConnections?=?freeConnections.elements();
????312?while?(allConnections.hasMoreElements())?{
????313?Connection?con?=?(Connection)?allConnections.nextElement();
????314?try?{
????315?con.close();
????316?log("關閉連接池"?+?name+"中的一個連接");
????317?}
????318?catch?(SQLException?e)?{
????319?log(e,?"無法關閉連接池"?+?name+"中的連接");
????320?}
????321?}
????322?freeConnections.removeAllElements();
????323?}
????324
????325?/**
????326?*?創建新的連接
????327?*/
????328?private?Connection?newConnection()?{
????329?Connection?con?=?null;
????330?try?{
????331?if?(user?==?null)?{
????332?con?=?DriverManager.getConnection(URL);
????333?}
????334?else?{
????335?con?=?DriverManager.getConnection(URL,?user,?password);
????336?}
????337?log("連接池"?+?name+"創建一個新的連接");
????338?}
????339?catch?(SQLException?e)?{
????340?log(e,?"無法創建下列URL的連接:?"?+?URL);
????341?return?null;
????342?}
????343?return?con;
????344?}
????345?}
????346?}
????
????
????三、類DBConnectionPool說明
????
????該類在209至345行實現,它表示指向某個數據庫的連接池。數據庫由JDBC?URL標識。一個JDBC?URL由三部分組成:協議標識(總是jdbc),驅動程序標識(如?odbc、idb、oracle等),數據庫標識(其格式依賴于驅動程序)。例如,jdbc:odbc:demo,即是一個指向demo數據庫的JDBC?URL,而且訪問該數據庫要使用JDBC-ODBC驅動程序。每個連接池都有一個供客戶程序使用的名字以及可選的用戶帳號、密碼、最大連接數限制。如果Web應用程序所支持的某些數據庫操作可以被所有用戶執行,而其它一些操作應由特別許可的用戶執行,則可以為兩類操作分別定義連接池,兩個連接池使用相同的JDBC?URL,但使用不同的帳號和密碼。
????類DBConnectionPool的建構函數需要上述所有數據作為其參數。如222至238行所示,這些數據被保存為它的實例變量:
????如252至283行、285至305行所示,?客戶程序可以使用DBConnectionPool類提供的兩個方法獲取可用連接。兩者的共同之處在于:如連接池中存在可用連接,則直接返回,否則創建新的連接并返回。如果沒有可用連接且已有連接總數等于最大限制數,第一個方法將直接返回null,而第二個方法將等待直到有可用連接為止。
????所有的可用連接對象均登記在名為freeConnections的向量(Vector)中。如果向量中有多于一個的連接,getConnection()總是選取第一個。同時,由于新的可用連接總是從尾部加入向量,從而使得數據庫連接由于長時間閑置而被關閉的風險減低到最小程度。
????第一個getConnection()在返回可用連接給客戶程序之前,調用了isClosed()方法驗證連接仍舊有效。如果該連接被關閉或觸發異常,getConnection()遞歸地調用自己以嘗試獲取另外的可用連接。如果在向量freeConnections中不存在任何可用連接,getConnection()方法檢查是否已經指定最大連接數限制。如已經指定,則檢查當前連接數是否已經到達極限。此處maxConn為0表示沒有限制。如果沒有指定最大連接數限制或當前連接數小于該值,該方法嘗試創建新的連接。如創建成功,則增加已使用連接的計數并返回,否則返回空值。
????如325至345行所示,創建新連接由newConnection()方法實現。創建過程與是否已經指定數據庫帳號、密碼有關。
????JDBC的DriverManager類提供多個getConnection()方法,這些方法要用到JDBC?URL與其它一些參數,如用戶帳號和密碼等。DriverManager將使用指定的JDBC?URL確定適合于目標數據庫的驅動程序及建立連接。
????在285至305行實現的第二個getConnection()方法需要一個以毫秒為單位的時間參數,該參數表示客戶程序能夠等待的最長時間。建立連接的具體操作仍舊由第一個getConnection()方法實現。
????該方法執行時先將startTime初始化為當前時間。在while循環中嘗試獲得一個連接。如果失敗,則以給定的時間值為參數調用wait()。wait()的返回可能是由于其它線程調用notify()或notifyAll(),也可能是由于預定時間已到。為找出wait()返回的真正原因,程序用當前時間減開始時間(startTime),如差值大于預定時間則返回空值,否則再次調用getConnection()。
????把空閑的連接登記到連接池由240至250行的freeConnection()方法實現,它的參數為返回給連接池的連接對象。該對象被加入到freeConnections向量的末尾,然后減少已使用連接計數。調用notifyAll()是為了通知其它正在等待可用連接的線程。
????許多Servlet引擎為實現安全關閉提供多種方法。數據庫連接池需要知道該事件以保證所有連接能夠正常關閉。DBConnectionManager類負協調整個關閉過程,但關閉連接池中所有連接的任務則由DBConnectionPool類負責。在307至323行實現的release()方法供DBConnectionManager調用。該方法遍歷freeConnections向量并關閉所有連接,然后從向量中刪除這些連接。
????
????
????四、類DBConnectionManager?說明
????
????該類只能創建一個實例,其它對象能夠調用其靜態方法(也稱為類方法)獲得該唯一實例的引用。如031至036行所示,DBConnectionManager類的建構函數是私有的,這是為了避免其它對象創建該類的實例。
????DBConnectionManager類的客戶程序可以調用getInstance()方法獲得對該類唯一實例的引用。如018至029行所示,類的唯一實例在getInstance()方法第一次被調用期間創建,此后其引用就一直保存在靜態變量instance中。每次調用getInstance()都增加一個DBConnectionManager的客戶程序計數。即,該計數代表引用DBConnectionManager唯一實例的客戶程序總數,它將被用于控制連接池的關閉操作。
????該類實例的初始化工作由146至168行之間的私有方法init()完成。其中?getResourceAsStream()方法用于定位并打開外部文件。外部文件的定位方法依賴于類裝載器的實現。標準的本地類裝載器查找操作總是開始于類文件所在路徑,也能夠搜索CLASSPATH中聲明的路徑。db.properties是一個屬性文件,它包含定義連接池的鍵-值對。可供定義的公用屬性如下:
??
????drivers?以空格分隔的JDBC驅動程序類列表
????logfile?日志文件的絕對路徑
????
????其它的屬性和特定連接池相關,其屬性名字前應加上連接池名字:
????
????<?poolname>.url?數據庫的?JDBC?URL?
????<?poolname>.maxconn?允許建立的最大連接數,0表示沒有限制?
????<?poolname>.user?用于該連接池的數據庫帳號
????<?poolname>.password?相應的密碼
????
????其中url屬性是必需的,而其它屬性則是可選的。數據庫帳號和密碼必須合法。用于Windows平臺的db.properties文件示例如下:
????
????drivers=sun.jdbc.odbc.JdbcOdbcDriver?jdbc.idbDriver
????logfile=D:\\user\\src\\java\\DBConnectionManager\\log.txt
????
????idb.url=jdbc:idb:c:\\local\\javawebserver1.1\\db\\db.prp
????idb.maxconn=2
????
????access.url=jdbc:odbc:demo
????access.user=demo
????access.password=demopw
????
????注意在Windows路徑中的反斜杠必須輸入2個,這是由于屬性文件中的反斜杠同時也是一個轉義字符。
????init()方法在創建屬性對象并讀取db.properties文件之后,就開始檢查logfile屬性。如果屬性文件中沒有指定日志文件,則默認為當前目錄下的DBConnectionManager.log文件。如日志文件無法使用,則向System.err輸出日志記錄。
????裝載和注冊所有在drivers屬性中指定的JDBC驅動程序由170至192行之間的loadDrivers()方法實現。該方法先用StringTokenizer將drivers屬性值分割為對應于驅動程序名稱的字符串,然后依次裝載這些類并創建其實例,最后在?DriverManager中注冊該實例并把它加入到一個私有的向量drivers。向量drivers將用于關閉服務時從DriverManager取消所有JDBC?驅動程序的注冊。
????init()方法的最后一個任務是調用私有方法createPools()創建連接池對象。如109至142行所示,createPools()方法先創建所有屬性名字的枚舉對象(即Enumeration對象,該對象可以想象為一個元素系列,逐次調用其nextElement()方法將順序返回各元素),然后在其中搜索名字以“.url”結尾的屬性。對于每一個符合條件的屬性,先提取其連接池名字部分,進而讀取所有屬于該連接池的屬性,最后創建連接池對象并把它保存在實例變量pools中。散列表(Hashtable類?)pools實現連接池名字到連接池對象之間的映射,此處以連接池名字為鍵,連接池對象為值。
????為便于客戶程序從指定連接池獲得可用連接或將連接返回給連接池,類DBConnectionManager提供了方法getConnection()和freeConnection()。所有這些方法都要求在參數中指定連接池名字,具體的連接獲取或返回操作則調用對應的連接池對象完成。它們的實現分別在051至064行、066至080行、038至049行。
????如082至107行所示,為實現連接池的安全關閉,DBConnectionManager提供了方法release()。在上面我們已經提到,所有DBConnectionManager的客戶程序都應該調用靜態方法getInstance()以獲得該管理器的引用,此調用將增加客戶程序計數。客戶程序在關閉時調用release()可以遞減該計數。當最后一個客戶程序調用release(),遞減后的引用計數為0,就可以調用各個連接池的release()方法關閉所有連接了。管理類release()方法最后的任務是撤銷所有JDBC驅動程序的注冊。
????
????
????五、Servlet使用連接池示例
????
????Servlet?API所定義的Servlet生命周期類如:
????
????1)?創建并初始化Servlet(init()方法)。
????2)?響應客戶程序的服務請求(service()方法)。
????3)?Servlet終止運行,釋放所有資源(destroy()方法)。
????
????本例演示連接池應用,上述關鍵步驟中的相關操作為:
????
????1)?在init(),用實例變量connMgr?保存調用DBConnectionManager.getInstance()所返回的引用。
????2)?在service(),調用getConnection(),執行數據庫操作,用freeConnection()將連接返回給連接池。
????3)?在destroy(),調用release()關閉所有連接,釋放所有資源。
????
????示例程序清單如下:
????
????import?java.io.*;
????import?java.sql.*;
????import?javax.servlet.*;
????import?javax.servlet.http.*;
????public?class?TestServlet?extends?HttpServlet?{
????private?DBConnectionManager?connMgr;
????
????public?void?init(ServletConfig?conf)?throws?ServletException?{
????super.init(conf);
????connMgr?=?DBConnectionManager.getInstance();
????}
????
????public?void?service(HttpServletRequest?req,?HttpServletResponse?res)?
????throws?IOException?{
????
????res.setContentType("text/html");
????PrintWriter?out?=?res.getWriter();
????Connection?con?=?connMgr.getConnection("idb");
????if?(con?==?null)?{
????out.println("不能獲取數據庫連接.");
????return;
????}
????ResultSet?rs?=?null;
????ResultSetMetaData?md?=?null;
????Statement?stmt?=?null;
????try?{
????stmt?=?con.createStatement();
????rs?=?stmt.executeQuery("SELECT?*?FROM?EMPLOYEE");
????md?=?rs.getMetaData();
????out.println("<?H1>職工數據<?/H1>");
????while?(rs.next())?{
????out.println("<?BR>");
????for?(int?i?=?1;?i?<?md.getColumnCount();?i++)?{
????out.print(rs.getString(i)?+?",?");
????}
????}
????stmt.close();
????rs.close();
????}
????catch?(SQLException?e)?{
????e.printStackTrace(out);
????}
????connMgr.freeConnection("idb",?con);
????}
????
????public?void?destroy()?{
????connMgr.release();
????super.destroy();
????}
????}?
據報已經支持連結池,但..........
JDBC3用了JNDI技術,連結池的配置可以讓一個高手都煩死.
目前第三方已經實現的連結池當然是poolman,1.0版對一般用戶來說已經足夠用了.配置也簡單,2.0版雖然增加了一些功能,但配置也是采用JNDI,對RMI和EJB不懂的朋友可能很煩.建議用1.0的了.
如果有興趣,自己也可以實現連結池,最關鍵的技術也就是把連結作為參數傳給一個BEAN,用完后返回這個參數連結而不是關閉.
下面是一個簡單的實現:
DBConnectionManager.java程序清單如下:
????
????001?import?java.io.*;
????002?import?java.sql.*;
????003?import?java.util.*;
????004?import?java.util.Date;
????005
????006?/**
????007?*?管理類DBConnectionManager支持對一個或多個由屬性文件定義的數據庫連接
????008?*?池的訪問.客戶程序可以調用getInstance()方法訪問本類的唯一實例.
????009?*/
????010?public?class?DBConnectionManager?{
????011?static?private?DBConnectionManager?instance;?//?唯一實例
????012?static?private?int?clients;
????013
????014?private?Vector?drivers?=?new?Vector();
????015?private?PrintWriter?log;
????016?private?Hashtable?pools?=?new?Hashtable();
????017
????018?/**
????019?*?返回唯一實例.如果是第一次調用此方法,則創建實例
????020?*
????021?*?@return?DBConnectionManager?唯一實例
????022?*/
????023?static?synchronized?public?DBConnectionManager?getInstance()?{
????024?if?(instance?==?null)?{
????025?instance?=?new?DBConnectionManager();
????026?}
????027?clients++;
????028?return?instance;
????029?}
????030
????031?/**
????032?*?建構函數私有以防止其它對象創建本類實例
????033?*/
????034?private?DBConnectionManager()?{
????035?init();
????036?}
????037
????038?/**
????039?*?將連接對象返回給由名字指定的連接池
????040?*
????041?*?@param?name?在屬性文件中定義的連接池名字
????042?*?@param?con?連接對象
????043?*/
????044?public?void?freeConnection(String?name,?Connection?con)?{
????045?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????046?if?(pool?!=?null)?{
????047?pool.freeConnection(con);
????048?}
????049?}
????050
????051?/**
????052?*?獲得一個可用的(空閑的)連接.如果沒有可用連接,且已有連接數小于最大連接數
????053?*?限制,則創建并返回新連接
????054?*
????055?*?@param?name?在屬性文件中定義的連接池名字
????056?*?@return?Connection?可用連接或null
????057?*/
????058?public?Connection?getConnection(String?name)?{
????059?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????060?if?(pool?!=?null)?{
????061?return?pool.getConnection();
????062?}
????063?return?null;
????064?}
????065
????066?/**
????067?*?獲得一個可用連接.若沒有可用連接,且已有連接數小于最大連接數限制,
????068?*?則創建并返回新連接.否則,在指定的時間內等待其它線程釋放連接.
????069?*
????070?*?@param?name?連接池名字
????071?*?@param?time?以毫秒計的等待時間
????072?*?@return?Connection?可用連接或null
????073?*/
????074?public?Connection?getConnection(String?name,?long?time)?{
????075?DBConnectionPool?pool?=?(DBConnectionPool)?pools.get(name);
????076?if?(pool?!=?null)?{
????077?return?pool.getConnection(time);
????078?}
????079?return?null;
????080?}
????081
????082?/**
????083?*?關閉所有連接,撤銷驅動程序的注冊
????084?*/
????085?public?synchronized?void?release()?{
????086?//?等待直到最后一個客戶程序調用
????087?if?(--clients?!=?0)?{
????088?return;
????089?}
????090
????091?Enumeration?allPools?=?pools.elements();
????092?while?(allPools.hasMoreElements())?{
????093?DBConnectionPool?pool?=?(DBConnectionPool)?allPools.nextElement();
????094?pool.release();
????095?}
????096?Enumeration?allDrivers?=?drivers.elements();
????097?while?(allDrivers.hasMoreElements())?{
????098?Driver?driver?=?(Driver)?allDrivers.nextElement();
????099?try?{
????100?DriverManager.deregisterDriver(driver);
????101?log("撤銷JDBC驅動程序?"?+?driver.getClass().getName()+"的注冊\\");
????102?}
????103?catch?(SQLException?e)?{
????104?log(e,?"無法撤銷下列JDBC驅動程序的注冊:?"?+?driver.getClass().getName());
????105?}
????106?}
????107?}
????108
????109?/**
????110?*?根據指定屬性創建連接池實例.
????111?*
????112?*?@param?props?連接池屬性
????113?*/
????114?private?void?createPools(Properties?props)?{
????115?Enumeration?propNames?=?props.propertyNames();
????116?while?(propNames.hasMoreElements())?{
????117?String?name?=?(String)?propNames.nextElement();
????118?if?(name.endsWith(".url"))?{
????119?String?poolName?=?name.substring(0,?name.lastIndexOf("."));
????120?String?url?=?props.getProperty(poolName?+?".url");
????121?if?(url?==?null)?{
????122?log("沒有為連接池"?+?poolName?+?"指定URL");
????123?continue;
????124?}
????125?String?user?=?props.getProperty(poolName?+?".user");
????126?String?password?=?props.getProperty(poolName?+?".password");
????127?String?maxconn?=?props.getProperty(poolName?+?".maxconn",?"0");
????128?int?max;
????129?try?{
????130?max?=?Integer.valueOf(maxconn).intValue();
????131?}
????132?catch?(NumberFormatException?e)?{
????133?log("錯誤的最大連接數限制:?"?+?maxconn?+?"?.連接池:?"?+?poolName);
????134?max?=?0;
????135?}
????136?DBConnectionPool?pool?=
????137?new?DBConnectionPool(poolName,?url,?user,?password,?max);
????138?pools.put(poolName,?pool);
????139?log("成功創建連接池"?+?poolName);
????140?}
????141?}
????142?}
????143
????144?/**
????145?*?讀取屬性完成初始化
????146?*/
????147?private?void?init()?{
????148?InputStream?is?=?getClass().getResourceAsStream("/db.properties");
????149?Properties?dbProps?=?new?Properties();
????150?try?{
????151?dbProps.load(is);
????152?}
????153?catch?(Exception?e)?{
????154?System.err.println("不能讀取屬性文件.?"?+
????155?"請確保db.properties在CLASSPATH指定的路徑中");
????156?return;
????157?}
????158?String?logFile?=?dbProps.getProperty("logfile",?"DBConnectionManager.log");
????159?try?{
????160?log?=?new?PrintWriter(new?FileWriter(logFile,?true),?true);
????161?}
????162?catch?(IOException?e)?{
????163?System.err.println("無法打開日志文件:?"?+?logFile);
????164?log?=?new?PrintWriter(System.err);
????165?}
????166?loadDrivers(dbProps);
????167?createPools(dbProps);
????168?}
????169
????170?/**
????171?*?裝載和注冊所有JDBC驅動程序
????172?*
????173?*?@param?props?屬性
????174?*/
????175?private?void?loadDrivers(Properties?props)?{
????176?String?driverClasses?=?props.getProperty("drivers");
????177?StringTokenizer?st?=?new?StringTokenizer(driverClasses);
????178?while?(st.hasMoreElements())?{
????179?String?driverClassName?=?st.nextToken().trim();
????180?try?{
????181?Driver?driver?=?(Driver)
????182?Class.forName(driverClassName).newInstance();
????183?DriverManager.registerDriver(driver);
????184?drivers.addElement(driver);
????185?log("成功注冊JDBC驅動程序\\"?+?driverClassName);
????186?}
????187?catch?(Exception?e)?{
????188?log("無法注冊JDBC驅動程序:?"?+
????189?driverClassName?+?",?錯誤:?"?+?e);
????190?}
????191?}
????192?}
????193
????194?/**
????195?*?將文本信息寫入日志文件
????196?*/
????197?private?void?log(String?msg)?{
????198?log.println(new?Date()?+?":?"?+?msg);
????199?}
????200
????201?/**
????202?*?將文本信息與異常寫入日志文件
????203?*/
????204?private?void?log(Throwable?e,?String?msg)?{
????205?log.println(new?Date()?+?":?"?+?msg);
????206?e.printStackTrace(log);
????207?}
????208
????209?/**
????210?*?此內部類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最
????211?*?大連接數為止.在返回連接給客戶程序之前,它能夠驗證連接的有效性.
????212?*/
????213?class?DBConnectionPool?{
????214?private?int?checkedOut;
????215?private?Vector?freeConnections?=?new?Vector();
????216?private?int?maxConn;
????217?private?String?name;
????218?private?String?password;
????219?private?String?URL;
????220?private?String?user;
????221
????222?/**
????223?*?創建新的連接池
????224?*
????225?*?@param?name?連接池名字
????226?*?@param?URL?數據庫的JDBC?URL
????227?*?@param?user?數據庫帳號,或?null
????228?*?@param?password?密碼,或?null
????229?*?@param?maxConn?此連接池允許建立的最大連接數
????230?*/
????231?public?DBConnectionPool(String?name,?String?URL,?String?user,?String?password,
????232?int?maxConn)?{
????233?this.name?=?name;
????234?this.URL?=?URL;
????235?this.user?=?user;
????236?this.password?=?password;
????237?this.maxConn?=?maxConn;
????238?}
????239
????240?/**
????241?*?將不再使用的連接返回給連接池
????242?*
????243?*?@param?con?客戶程序釋放的連接
????244?*/
????245?public?synchronized?void?freeConnection(Connection?con)?{
????246?//?將指定連接加入到向量末尾
????247?freeConnections.addElement(con);
????248?checkedOut--;
????249?notifyAll();
????250?}
????251
????252?/**
????253?*?從連接池獲得一個可用連接.如沒有空閑的連接且當前連接數小于最大連接
????254?*?數限制,則創建新連接.如原來登記為可用的連接不再有效,則從向量刪除之,
????255?*?然后遞歸調用自己以嘗試新的可用連接.
????256?*/
????257?public?synchronized?Connection?getConnection()?{
????258?Connection?con?=?null;
????259?if?(freeConnections.size()?>?0)?{
????260?//?獲取向量中第一個可用連接
????261?con?=?(Connection)?freeConnections.firstElement();
????262?freeConnections.removeElementAt(0);
????263?try?{
????264?if?(con.isClosed())?{
????265?log("從連接池"?+?name+"刪除一個無效連接");
????266?//?遞歸調用自己,嘗試再次獲取可用連接
????267?con?=?getConnection();
????268?}
????269?}
????270?catch?(SQLException?e)?{
????271?log("從連接池"?+?name+"刪除一個無效連接");
????272?//?遞歸調用自己,嘗試再次獲取可用連接
????273?con?=?getConnection();
????274?}
????275?}
????276?else?if?(maxConn?==?0?||?checkedOut?<?maxConn)?{
????277?con?=?newConnection();
????278?}
????279?if?(con?!=?null)?{
????280?checkedOut++;
????281?}
????282?return?con;
????283?}
????284
????285?/**
????286?*?從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間
????287?*?參見前一個getConnection()方法.
????288?*
????289?*?@param?timeout?以毫秒計的等待時間限制
????290?*/
????291?public?synchronized?Connection?getConnection(long?timeout)?{
????292?long?startTime?=?new?Date().getTime();
????293?Connection?con;
????294?while?((con?=?getConnection())?==?null)?{
????295?try?{
????296?wait(timeout);
????297?}
????298?catch?(InterruptedException?e)?{}
????299?if?((new?Date().getTime()?-?startTime)?>=?timeout)?{
????300?//?wait()返回的原因是超時
????301?return?null;
????302?}
????303?}
????304?return?con;
????305?}
????306
????307?/**
????308?*?關閉所有連接
????309?*/
????310?public?synchronized?void?release()?{
????311?Enumeration?allConnections?=?freeConnections.elements();
????312?while?(allConnections.hasMoreElements())?{
????313?Connection?con?=?(Connection)?allConnections.nextElement();
????314?try?{
????315?con.close();
????316?log("關閉連接池"?+?name+"中的一個連接");
????317?}
????318?catch?(SQLException?e)?{
????319?log(e,?"無法關閉連接池"?+?name+"中的連接");
????320?}
????321?}
????322?freeConnections.removeAllElements();
????323?}
????324
????325?/**
????326?*?創建新的連接
????327?*/
????328?private?Connection?newConnection()?{
????329?Connection?con?=?null;
????330?try?{
????331?if?(user?==?null)?{
????332?con?=?DriverManager.getConnection(URL);
????333?}
????334?else?{
????335?con?=?DriverManager.getConnection(URL,?user,?password);
????336?}
????337?log("連接池"?+?name+"創建一個新的連接");
????338?}
????339?catch?(SQLException?e)?{
????340?log(e,?"無法創建下列URL的連接:?"?+?URL);
????341?return?null;
????342?}
????343?return?con;
????344?}
????345?}
????346?}
????
????
????三、類DBConnectionPool說明
????
????該類在209至345行實現,它表示指向某個數據庫的連接池。數據庫由JDBC?URL標識。一個JDBC?URL由三部分組成:協議標識(總是jdbc),驅動程序標識(如?odbc、idb、oracle等),數據庫標識(其格式依賴于驅動程序)。例如,jdbc:odbc:demo,即是一個指向demo數據庫的JDBC?URL,而且訪問該數據庫要使用JDBC-ODBC驅動程序。每個連接池都有一個供客戶程序使用的名字以及可選的用戶帳號、密碼、最大連接數限制。如果Web應用程序所支持的某些數據庫操作可以被所有用戶執行,而其它一些操作應由特別許可的用戶執行,則可以為兩類操作分別定義連接池,兩個連接池使用相同的JDBC?URL,但使用不同的帳號和密碼。
????類DBConnectionPool的建構函數需要上述所有數據作為其參數。如222至238行所示,這些數據被保存為它的實例變量:
????如252至283行、285至305行所示,?客戶程序可以使用DBConnectionPool類提供的兩個方法獲取可用連接。兩者的共同之處在于:如連接池中存在可用連接,則直接返回,否則創建新的連接并返回。如果沒有可用連接且已有連接總數等于最大限制數,第一個方法將直接返回null,而第二個方法將等待直到有可用連接為止。
????所有的可用連接對象均登記在名為freeConnections的向量(Vector)中。如果向量中有多于一個的連接,getConnection()總是選取第一個。同時,由于新的可用連接總是從尾部加入向量,從而使得數據庫連接由于長時間閑置而被關閉的風險減低到最小程度。
????第一個getConnection()在返回可用連接給客戶程序之前,調用了isClosed()方法驗證連接仍舊有效。如果該連接被關閉或觸發異常,getConnection()遞歸地調用自己以嘗試獲取另外的可用連接。如果在向量freeConnections中不存在任何可用連接,getConnection()方法檢查是否已經指定最大連接數限制。如已經指定,則檢查當前連接數是否已經到達極限。此處maxConn為0表示沒有限制。如果沒有指定最大連接數限制或當前連接數小于該值,該方法嘗試創建新的連接。如創建成功,則增加已使用連接的計數并返回,否則返回空值。
????如325至345行所示,創建新連接由newConnection()方法實現。創建過程與是否已經指定數據庫帳號、密碼有關。
????JDBC的DriverManager類提供多個getConnection()方法,這些方法要用到JDBC?URL與其它一些參數,如用戶帳號和密碼等。DriverManager將使用指定的JDBC?URL確定適合于目標數據庫的驅動程序及建立連接。
????在285至305行實現的第二個getConnection()方法需要一個以毫秒為單位的時間參數,該參數表示客戶程序能夠等待的最長時間。建立連接的具體操作仍舊由第一個getConnection()方法實現。
????該方法執行時先將startTime初始化為當前時間。在while循環中嘗試獲得一個連接。如果失敗,則以給定的時間值為參數調用wait()。wait()的返回可能是由于其它線程調用notify()或notifyAll(),也可能是由于預定時間已到。為找出wait()返回的真正原因,程序用當前時間減開始時間(startTime),如差值大于預定時間則返回空值,否則再次調用getConnection()。
????把空閑的連接登記到連接池由240至250行的freeConnection()方法實現,它的參數為返回給連接池的連接對象。該對象被加入到freeConnections向量的末尾,然后減少已使用連接計數。調用notifyAll()是為了通知其它正在等待可用連接的線程。
????許多Servlet引擎為實現安全關閉提供多種方法。數據庫連接池需要知道該事件以保證所有連接能夠正常關閉。DBConnectionManager類負協調整個關閉過程,但關閉連接池中所有連接的任務則由DBConnectionPool類負責。在307至323行實現的release()方法供DBConnectionManager調用。該方法遍歷freeConnections向量并關閉所有連接,然后從向量中刪除這些連接。
????
????
????四、類DBConnectionManager?說明
????
????該類只能創建一個實例,其它對象能夠調用其靜態方法(也稱為類方法)獲得該唯一實例的引用。如031至036行所示,DBConnectionManager類的建構函數是私有的,這是為了避免其它對象創建該類的實例。
????DBConnectionManager類的客戶程序可以調用getInstance()方法獲得對該類唯一實例的引用。如018至029行所示,類的唯一實例在getInstance()方法第一次被調用期間創建,此后其引用就一直保存在靜態變量instance中。每次調用getInstance()都增加一個DBConnectionManager的客戶程序計數。即,該計數代表引用DBConnectionManager唯一實例的客戶程序總數,它將被用于控制連接池的關閉操作。
????該類實例的初始化工作由146至168行之間的私有方法init()完成。其中?getResourceAsStream()方法用于定位并打開外部文件。外部文件的定位方法依賴于類裝載器的實現。標準的本地類裝載器查找操作總是開始于類文件所在路徑,也能夠搜索CLASSPATH中聲明的路徑。db.properties是一個屬性文件,它包含定義連接池的鍵-值對。可供定義的公用屬性如下:
??
????drivers?以空格分隔的JDBC驅動程序類列表
????logfile?日志文件的絕對路徑
????
????其它的屬性和特定連接池相關,其屬性名字前應加上連接池名字:
????
????<?poolname>.url?數據庫的?JDBC?URL?
????<?poolname>.maxconn?允許建立的最大連接數,0表示沒有限制?
????<?poolname>.user?用于該連接池的數據庫帳號
????<?poolname>.password?相應的密碼
????
????其中url屬性是必需的,而其它屬性則是可選的。數據庫帳號和密碼必須合法。用于Windows平臺的db.properties文件示例如下:
????
????drivers=sun.jdbc.odbc.JdbcOdbcDriver?jdbc.idbDriver
????logfile=D:\\user\\src\\java\\DBConnectionManager\\log.txt
????
????idb.url=jdbc:idb:c:\\local\\javawebserver1.1\\db\\db.prp
????idb.maxconn=2
????
????access.url=jdbc:odbc:demo
????access.user=demo
????access.password=demopw
????
????注意在Windows路徑中的反斜杠必須輸入2個,這是由于屬性文件中的反斜杠同時也是一個轉義字符。
????init()方法在創建屬性對象并讀取db.properties文件之后,就開始檢查logfile屬性。如果屬性文件中沒有指定日志文件,則默認為當前目錄下的DBConnectionManager.log文件。如日志文件無法使用,則向System.err輸出日志記錄。
????裝載和注冊所有在drivers屬性中指定的JDBC驅動程序由170至192行之間的loadDrivers()方法實現。該方法先用StringTokenizer將drivers屬性值分割為對應于驅動程序名稱的字符串,然后依次裝載這些類并創建其實例,最后在?DriverManager中注冊該實例并把它加入到一個私有的向量drivers。向量drivers將用于關閉服務時從DriverManager取消所有JDBC?驅動程序的注冊。
????init()方法的最后一個任務是調用私有方法createPools()創建連接池對象。如109至142行所示,createPools()方法先創建所有屬性名字的枚舉對象(即Enumeration對象,該對象可以想象為一個元素系列,逐次調用其nextElement()方法將順序返回各元素),然后在其中搜索名字以“.url”結尾的屬性。對于每一個符合條件的屬性,先提取其連接池名字部分,進而讀取所有屬于該連接池的屬性,最后創建連接池對象并把它保存在實例變量pools中。散列表(Hashtable類?)pools實現連接池名字到連接池對象之間的映射,此處以連接池名字為鍵,連接池對象為值。
????為便于客戶程序從指定連接池獲得可用連接或將連接返回給連接池,類DBConnectionManager提供了方法getConnection()和freeConnection()。所有這些方法都要求在參數中指定連接池名字,具體的連接獲取或返回操作則調用對應的連接池對象完成。它們的實現分別在051至064行、066至080行、038至049行。
????如082至107行所示,為實現連接池的安全關閉,DBConnectionManager提供了方法release()。在上面我們已經提到,所有DBConnectionManager的客戶程序都應該調用靜態方法getInstance()以獲得該管理器的引用,此調用將增加客戶程序計數。客戶程序在關閉時調用release()可以遞減該計數。當最后一個客戶程序調用release(),遞減后的引用計數為0,就可以調用各個連接池的release()方法關閉所有連接了。管理類release()方法最后的任務是撤銷所有JDBC驅動程序的注冊。
????
????
????五、Servlet使用連接池示例
????
????Servlet?API所定義的Servlet生命周期類如:
????
????1)?創建并初始化Servlet(init()方法)。
????2)?響應客戶程序的服務請求(service()方法)。
????3)?Servlet終止運行,釋放所有資源(destroy()方法)。
????
????本例演示連接池應用,上述關鍵步驟中的相關操作為:
????
????1)?在init(),用實例變量connMgr?保存調用DBConnectionManager.getInstance()所返回的引用。
????2)?在service(),調用getConnection(),執行數據庫操作,用freeConnection()將連接返回給連接池。
????3)?在destroy(),調用release()關閉所有連接,釋放所有資源。
????
????示例程序清單如下:
????
????import?java.io.*;
????import?java.sql.*;
????import?javax.servlet.*;
????import?javax.servlet.http.*;
????public?class?TestServlet?extends?HttpServlet?{
????private?DBConnectionManager?connMgr;
????
????public?void?init(ServletConfig?conf)?throws?ServletException?{
????super.init(conf);
????connMgr?=?DBConnectionManager.getInstance();
????}
????
????public?void?service(HttpServletRequest?req,?HttpServletResponse?res)?
????throws?IOException?{
????
????res.setContentType("text/html");
????PrintWriter?out?=?res.getWriter();
????Connection?con?=?connMgr.getConnection("idb");
????if?(con?==?null)?{
????out.println("不能獲取數據庫連接.");
????return;
????}
????ResultSet?rs?=?null;
????ResultSetMetaData?md?=?null;
????Statement?stmt?=?null;
????try?{
????stmt?=?con.createStatement();
????rs?=?stmt.executeQuery("SELECT?*?FROM?EMPLOYEE");
????md?=?rs.getMetaData();
????out.println("<?H1>職工數據<?/H1>");
????while?(rs.next())?{
????out.println("<?BR>");
????for?(int?i?=?1;?i?<?md.getColumnCount();?i++)?{
????out.print(rs.getString(i)?+?",?");
????}
????}
????stmt.close();
????rs.close();
????}
????catch?(SQLException?e)?{
????e.printStackTrace(out);
????}
????connMgr.freeConnection("idb",?con);
????}
????
????public?void?destroy()?{
????connMgr.release();
????super.destroy();
????}
????}?
轉載于:https://www.cnblogs.com/zhuor/archive/2005/11/07/270751.html
總結
- 上一篇: ASP.NET程序中常用代码汇总(四)
- 下一篇: WEB BUTTON IN DOTNE