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是一个属性
* 文件,它包含定义连接池的键-值对。可供定义的公用属性如下:
* 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()
* 以获得该管理器的引用,此调用将增加客户程序计数。客户程序在关闭时调用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);
cre