package com.xinnuo.jdbc;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;
/*
* 該類只能創建一個實例,其它對象能夠調用其靜態方法(也稱為類方法)獲得該唯一實例的引用。
* DBConnectionManager類的建構函數是私有的,這是為了避免其它對象創建該類的實例.
* DBConnectionManager類的客戶程序可以調用getInstance()方法獲得對該類唯一實例的引用。
* 類的唯一實例在getInstance()方法第一次被調用期間創建,此后其引用就一直保存在靜態變量
* instance中。每次調用getInstance()都增加一個DBConnectionManager的客戶程序計數。
* 即,該計數代表引用DBConnectionManager唯一實例的客戶程序總數,它將被用于控制連接池的
* 關閉操作。 該類實例的初始化工作私有方法init()完成。其中 getResourceAsStream()方法
* 用于定位并打開外部文件。外部文件的定位方法依賴于類裝載器的實現。標準的本地類裝載器查找操
* 作總是開始于類文件所在路徑,也能夠搜索CLASSPATH中聲明的路徑。db.properties是一個屬性
* 文件,它包含定義連接池的鍵-值對??晒┒x的公用屬性如下:
* drivers 以空格分隔的JDBC驅動程序類列表""
* logfile 日志文件的絕對路徑
* 其它的屬性和特定連接池相關,其屬性名字前應加上連接池名字:
* < poolname>.url 數據庫的 JDBC URL
* < poolname>.maxconn 允許建立的最大連接數,0表示沒有限制
* < poolname>.user 用于該連接池的數據庫帳號
* < poolname>.password 相應的密碼""
* 其中url屬性是必需的,而其它屬性則是可選的。數據庫帳號和密碼必須合法。用于Windows平臺的
* db.properties文件示例如下:
* drivers=com.microsoft.jdbc.sqlserver.SQLServerDriver
* logfile=D:""log.txt
* access.maxconn=20
* access.url=jdbc:microsoft:sqlserver://localhost:1433;databasename=web
* access.user=sa
* access.password=sa
* 注意在Windows路徑中的反斜杠必須輸入2個,這是由于屬性文件中的反斜杠同時也是一個轉義字符。
* init()方法在創建屬性對象并讀取db.properties文件之后,就開始檢查logfile屬性。如果屬
* 性文件中沒有指定日志文件,則默認為當前目錄下的DBConnectionManager.log文件。如日志文
* 件無法使用,則向System.err輸出日志記錄。裝載和注冊所有在drivers屬性中指定的JDBC驅動
* 程序loadDrivers()方法實現。該方法先用StringTokenizer將drivers屬性值分割為對應于驅
* 動程序名稱的字符串,然后依次裝載這些類并創建其實例,最后在DriverManager中注冊該實例并把
* 它加入到一個私有的向量drivers。向量drivers將用于關閉服務時從DriverManager取消所有
* JDBC 驅動程序的注冊。init()方法的最后一個任務是調用私有方法createPools()創建連接池對
* 象。createPools()方法先創建所有屬性名字的枚舉對象(即Enumeration對象,該對象可以想象
* 為一個元素系列,逐次調用其nextElement()方法將順序返回各元素),然后在其中搜索名字以“.url”
* 結尾的屬性。對于每一個符合條件的屬性,先提取其連接池名字部分,進而讀取所有屬于該連接池的屬性,
* 最后創建連接池對象并把它保存在實例變量pools中。散列表(Hashtable類 )pools實現連接池名字
* 到連接池對象之間的映射,此處以連接池名字為鍵,連接池對象為值。 為便于客戶程序從指定連接池獲
* 得可用連接或將連接返回給連接池,類DBConnectionManager提供了方法getConnection()和
* freeConnection()。所有這些方法都要求在參數中指定連接池名字,具體的連接獲取或返回操作則調
* 用對應的連接池對象完成。為實現連接池的安全關閉,DBConnectionManager提供了方法release()。
* 在上面我們已經提到,所有DBConnectionManager的客戶程序都應該調用靜態方法getInstance()
* 以獲得該管理器的引用,此調用將增加客戶程序計數??蛻舫绦蛟陉P閉時調用release()可以遞減該計數。
* 當最后一個客戶程序調用release(),遞減后的引用計數為0,就可以調用各個連接池的release()方法
* 關閉所有連接了。管理類release()方法最后的任務是撤銷所有JDBC驅動程序的注冊。
*/
/**
* 管理類DBConnectionManager支持對一個或多個由屬性文件定義的數據庫連接
* 池的訪問.客戶程序可以調用getInstance()方法訪問本類的唯一實例.
*/
public class DBConnectionManager {
static private DBConnectionManager instance; // 唯一實例
static private int clients;
private Vector drivers = new Vector();
private PrintWriter log;
private Hashtable pools = new Hashtable();
/**
* 返回唯一實例.如果是第一次調用此方法,則創建實例
* @return DBConnectionManager 唯一實例
*/
static synchronized public DBConnectionManager getInstance() {
if (instance == null) {
instance = new DBConnectionManager();
}
clients++;
return instance;
}
/**
* 建構函數私有以防止其它對象創建本類實例
*/
private DBConnectionManager() {
init();
}
/**
* * 將連接對象返回給由名字指定的連接池
* @param name在屬性文件中定義的連接池名字
* @param con連接對象""""r
*/
public void freeConnection(String name, Connection con) {
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null) {
pool.freeConnection(con);
}
}
/**
* 獲得一個可用的(空閑的)連接.如果沒有可用連接,且已有連接數小于最大連接數 053 * 限制,則創建并返回新連
* @param name在屬性文件中定義的連接池名字 056 *
* @return Connection 可用連接或null 057
*/
public Connection getConnection(String name) {
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null) {
return pool.getConnection();
}
return null;
}
/**
* 獲得一個可用連接.若沒有可用連接,且已有連接數小于最大連接數限制,
* 則創建并返回新連接.否則,在指定的時間內等待其它線程釋放連接.
* @param name 連接池名字 071 *
* @param time以毫秒計的等待時間""""r
* @return Connection 可用連接或null
*/
public Connection getConnection(String name, long time) {
DBConnectionPool pool = (DBConnectionPool) pools.get(name);
if (pool != null) {
return pool.getConnection(time);
}
return null;
}
/**
* 關閉所有連接,撤銷驅動程序的注冊""""r
*/
public synchronized void release() {
// 等待直到最后一個客戶程序調用
if (--clients != 0) {
return;
}
Enumeration allPools = pools.elements();
while (allPools.hasMoreElements()) {
DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
pool.release();
}
Enumeration allDrivers = drivers.elements();
while (allDrivers.hasMoreElements()) {
Driver driver = (Driver) allDrivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
log("撤銷JDBC驅動程序 " + driver.getClass().getName() + "的注冊");
} catch (SQLException e) {
log(e, "無法撤銷下列JDBC驅動程序的注冊: " + driver.getClass().getName());
}
}
}
/**
* 根據指定屬性創建連接池實例.
* @param props 連接池屬性 113
*/
private void createPools(Properties props) {
Enumeration propNames = props.propertyNames();
while (propNames.hasMoreElements()) {
String name = (String) propNames.nextElement();
if (name.endsWith(".url")) {
String poolName = name.substring(0, name.lastIndexOf("."));
String url = props.getProperty(poolName + ".url");
if (url == null) {
log("沒有為連接池" + poolName + "指定URL");
continue;
}
String user = props.getProperty(poolName + ".user");
String password = props.getProperty(poolName + ".password");
String maxconn = props.getProperty(poolName + ".maxconn", "0");
int max;
try {
max = Integer.valueOf(maxconn).intValue();
} catch (NumberFormatException e) {
log("錯誤的最大連接數限制: " + maxconn + " .連接池: " + poolName);
max = 0;
}
DBConnectionPool pool = new DBConnectionPool(poolName, url,
user, password, max);
pools.put(poolName, pool);
log("成功創建連接池" + poolName);
}
}
}
/**
* 讀取屬性完成初始化
*/
private void init() {
InputStream is = getClass().getResourceAsStream("/db.properties");
Properties dbProps = new Properties();
try {
dbProps.load(is);
} catch (Exception e) {
System.err.println("不能讀取屬性文件. "+"請確保db.properties在CLASSPATH指定的路徑中");
return;
}
String logFile = dbProps.getProperty("logfile","DBConnectionManager.log");
try {
log = new PrintWriter(new FileWriter(logFile, true), true);
} catch (IOException e) {
System.err.println("無法打開日志文件: " + logFile);
log = new PrintWriter(System.err);
}
loadDrivers(dbProps);
createPools(dbProps);
}
/**
* 裝載和注冊所有JDBC驅動程序
* @param props屬性
*/
private void loadDrivers(Properties props) {
String driverClasses = props.getProperty("drivers");
StringTokenizer st = new StringTokenizer(driverClasses);
while (st.hasMoreElements()) {
String driverClassName = st.nextToken().trim();
try {
Driver driver = (Driver) Class.forName(driverClassName).newInstance();
DriverManager.registerDriver(driver);
drivers.addElement(driver);
log("成功注冊JDBC驅動程序" + driverClassName);
} catch (Exception e) {
e.printStackTrace();
log("無法注冊JDBC驅動程序: " + driverClassName + ", 錯誤: " + e);
}
}
}
/**
* 將文本信息寫入日志文件
*/
private void log(String msg) {
log.println(new Date() + ": " + msg);
}
/**
* 將文本信息與異常寫入日志文件
*/
private void log(Throwable e, String msg) {
log.println(new Date() + ": " + msg);
e.printStackTrace(log);
}
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* DBConnectionPool實現,它表示指向某個數據庫的連接池。數據庫由JDBC URL標識。一個JDBCURL由三部分組成:協議標識(總是jdbc),
* 驅動程序標識(如odbc、idb、oracle等),數據庫標識(其格式依賴于驅動程序)。例如,jdbc:odbc:demo,即是一個指向demo數據
* 庫的JDBCURL,而且訪問該數據庫要使用JDBC-ODBC驅動程序。每個連接池都有一個供客戶程序使用的名字以及可選的用戶帳號、密碼、最
* 大連接數限制。如果Web應用程序所支持的某些數據庫操作可以被所有用戶執行,而其它一些操作應由特別許可的用戶執行,則可以為兩類操作
* 分別定義連接池,兩個連接池使用相同的JDBC URL,但使用不同的帳號和密碼。類DBConnectionPool的建構函數需要上述所有數據作為其
* 參數。客戶程序可以使用DBConnectionPool
* 類提供的兩個方法獲取可用連接。兩者的共同之處在于:如連接池中存在可用連接,則直接返回,否則創建新的連接并返回。如果沒有可用連接
* 且已有連接總數等于最大限制數,第一個方法將直接返回null,而第二個方法將等待直到有可用連接為止。所有的可用連接對象均登記在名為
* freeConnections的向量(Vector)中。如果向量中有多于一個的連接,getConnection()總是選取第一個。同時,由于新的可用連接總
* 是從尾部加入向量,從而使得數據庫連接由于長時間閑置而被關閉的風險減低到最小程度。 第一個getConnection()在返回可用連接給客戶
* 程序之前,調用了isClosed()方法驗證連接仍舊有效。如果該連接被關閉或觸發異常,getConnection()遞歸地調用自己以嘗試獲取另外的
* 可用連接。如果在向量freeConnections中不存在任何可用連接,getConnection()方法檢查是否已經指定最大連接數限制。如已經指定,
* 則檢查當前連接數是否已經到達極限。此處maxConn為0表示沒有限制。如果沒有指定最大連接數限制或當前連接數小于該值,該方法嘗試創建
* 新的連接。如創建成功,則增加已使用連接的計數并返回,否則返回空值。創建新連接由newConnection()方法實現。
* 創建過程與是否已經指定數據庫帳號、密碼有關。JDBC的DriverManager類提供多個getConnection()方法,這些方法要用到JDBC URL
* 與其它一些參數,如用戶帳號和密碼等。DriverManager將使用指定的JDBC URL確定適合于目標數據庫的驅動程序及建立連接。
* 第二個getConnection()方法需要一個以毫秒為單位的時間參數,該參數表示客戶程序能夠等待的最長時間。建立連接的具體操
* 作仍舊由第一個getConnection()方法實現。該方法執行時先將startTime初始化為當前時間。在while循環中嘗試獲得一個連接。如果失
* 敗,則以給定的時間值為參數調用wait()。wait()的返回可能是由于其它線程調用notify()或notifyAll(),也可能是由于預定時間已到。
* 為找出wait()返回的真正原因,程序用當前時間減開始時間(startTime),如差值大于預定時間則返回空值,否則再次調用getConnection()。
* 把空閑的連接登記到連接池由freeConnection()方法實現,它的參數為返回給連接池的連接對象。該對象被加入到freeConnections
* 向量的末尾,然后減少已使用連接計數。調用notifyAll()是為了通知其它正在等待可用連接的線程。 許多Servlet引擎為實現安全關閉提供
* 多種方法。數據庫連接池需要知道該事件以保證所有連接能夠正常關閉。DBConnectionManager類負協調整個關閉過程,但關閉連接池中所有連
* 接的任務則由DBConnectionPool類負責。release()方法供DBConnectionManager調用。該方法遍歷freeConnections向量并關閉所有連接,
* 然后從向量中刪除這些連接。
*/
/**
* 此內部類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最""""r
*/
class DBConnectionPool {
private int checkedOut;
private Vector freeConnections = new Vector();
private int maxConn;
private String name;
private String password;
private String URL;
private String user;
/**
* 創建新的連接池
* @param name連接池名字
* @param URL數據庫的JDBC URL
* @param user數據庫帳號,或 null
* @param password密碼,或 null
* @param maxConn此連接池允許建立的最大連接數
*/
public DBConnectionPool(String name, String URL, String user,
String password, int maxConn) {
this.name = name;
this.URL = URL;
this.user = user;
this.password = password;
this.maxConn = maxConn;
}
/**
* 將不再使用的連接返回給連接池
* @param con客戶程序釋放的連接
*/
public synchronized void freeConnection(Connection con) {
// 將指定連接加入到向量末尾
freeConnections.addElement(con);
checkedOut--;
notifyAll();
}
/**
* 從連接池獲得一個可用連接.如沒有空閑的連接且當前連接數小于最大連接 數限制,則創建新連接.
* 如原來登記為可用的連接不再有效,則從向量刪除之,
* 然后遞歸調用自己以嘗試新的可用連接.
*/
public synchronized Connection getConnection() {
Connection con = null;
if (freeConnections.size() > 0) {// 獲取向量中第一個可用連接
con = (Connection) freeConnections.firstElement();
freeConnections.removeElementAt(0);
try {
if (con.isClosed()) {
log("從連接池" + name + "刪除一個無效連接");
// 遞歸調用自己,嘗試再次獲取可用連接
con = getConnection();
}
} catch (SQLException e) {
log("從連接池" + name + "刪除一個無效連接");
// 遞歸調用自己,嘗試再次獲取可用連接
con = getConnection();
}
} else if (maxConn == 0 || checkedOut < maxConn) {
con = newConnection();
}
if (con != null) {
checkedOut++;
}
return con;
}
/**
* 從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間 參見前一個getConnection()方法.
* @param timeout以毫秒計的等待時間限制
*/
public synchronized Connection getConnection(long timeout) {
long startTime = new Date().getTime();
Connection con;
while ((con = getConnection()) == null) {
try {
wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((new Date().getTime() - startTime) >= timeout) {// wait()返回的原因是超時
return null;
}
}
return con;
}
/**
* 關閉所有連接
*/
public synchronized void release() {
Enumeration allConnections = freeConnections.elements();
while (allConnections.hasMoreElements()) {
Connection con = (Connection) allConnections.nextElement();
try {
con.close();
log("關閉連接池" + name + "中的一個連接");
} catch (SQLException e) {
log(e, "無法關閉連接池" + name + "中的連接");
e.printStackTrace();
}
}
freeConnections.removeAllElements();
}
/**
* 創建新的連接
*/
private Connection newConnection() {
Connection con = null;
try {
if (user==null||"".equals(user)) {
con = DriverManager.getConnection(URL);
} else {
con = DriverManager.getConnection(URL, user, password);
}
log("連接池" + name + "創建一個新的連接");
} catch (SQLException e) {
log(e, "無法創建下列URL的連接: " + URL);
return null;
}
return con;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////
}
在sqlserver2000,tomcat5.5驗證通過
方法調用!
package com.xinnuo.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBConn {
private DBConnectionManager connMgr = null;
private Connection conn = null;
private Statement stat = null;
private ResultSet rs = null;
public DBConn() {
connMgr = DBConnectionManager.getInstance();
}
public ResultSet executeQuery(String strSQL) throws SQLException {
this.conn = connMgr.getConnection("access");
this.stat = this.conn.createStatement();
this.rs = this.stat.executeQuery(strSQL);
return this.rs;
}
public void execute(String strSQL) throws SQLException {
this.conn = connMgr.getConnection("access");
this.stat = this.conn.createStatement();
this.stat.execute(strSQL);
}
public void executeUpdate(String strSQL) throws SQLException {
this.conn = connMgr.getConnection("access");
this.stat = this.conn.createStatement();
this.stat.executeUpdate(strSQL);
}
public Connection getConnection(){
this.conn = connMgr.getConnection("access");
return this.conn;
}
public void free(){
try {
if (this.rs != null) {
this.rs.close();
}
if (this.stat != null) {
this.stat.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
connMgr.freeConnection("access", this.conn);
}
}