* 大连接数为止在返回连接给客户程序之前它能够验证连接的有效性
*/
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) {
thisname = name;
thisURL = URL;
thisuser = user;
thispassword = password;
thismaxConn = maxConn;
}
/**
* 将不再使用的连接返回给连接池
*
* @param con 客户程序释放的连接
*/
public synchronized void freeConnection(Connection con) {
// 将指定连接加入到向量末尾
freeConnectionsaddElement(con);
checkedOut;
notifyAll();
}
/**
* 从连接池获得一个可用连接如没有空闲的连接且当前连接数小于最大连接
* 数限制则创建新连接如原来登记为可用的连接不再有效则从向量删除之
* 然后递归调用自己以尝试新的可用连接
*/
public synchronized Connection getConnection() {
Connection con = null;
if (freeConnectionssize() > ) {
// 获取向量中第一个可用连接
con = (Connection) freeConnectionsfirstElement();
freeConnectionsremoveElementAt();
try {
if (conisClosed()) {
log(从连接池 + name+删除一个无效连接);
// 递归调用自己尝试再次获取可用连接
con = getConnection();
}
}
catch (SQLException e) {
log(从连接池 + name+删除一个无效连接);
// 递归调用自己尝试再次获取可用连接
con = getConnection();
}
}
else if (maxConn == || checkedOut < maxConn) {
con = newConnection();
}
if (con != null) {
checkedOut++;
}
return con;
}
/**
* 从连接池获取可用连接可以指定客户程序能够等待的最长时间\\r
* 参见前一个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) {}
if ((new Date()getTime() startTime) >= timeout) {
// wait()返回的原因是超时
return null;
}
}
return con;
}
/**
* 关闭所有连接
*/
public synchronized void release() {
Enumeration allConnections = freeConnectionselements();
while (allConnectionshasMoreElements()) {
Connection con = (Connection) allConnectionsnextElement();
try {
conclose();
log(关闭连接池 + name+中的一个连接);
}
catch (SQLException e) {
log(e 无法关闭连接池 + name+中的连接);
}
}
freeConnectionsremoveAllElements();
}
/**
* 创建新的连接
*/
private Connection newConnection() {
Connection con = null;
try {
if (user == null) {
con = DriverManagergetConnection(URL);
}
else {
con = DriverManagergetConnection(URL user password);
}
log(连接池 + name+创建一个新的连接);
}
catch (SQLException e) {
log(e 无法创建下列URL的连接: + URL);
return null;
}
return con;
}
}
}
三类DBConnectionPool说明
该类在至行实现它表示指向某个数据库的连接池数据库由JDBC URL标识一个JDBC URL由三部分组成协议标识(总是jdbc)驱动程序标识(如 odbcidboracle等)数据库标识(其格式依赖于驱动程序)例如jdbc:odbc:demo即是一个指向demo数据库的JDBC URL而且访问该数据库要使用JDBCODBC驱动程序每个连接池都有一个供客户程序使用的名字以及可选的用户帐号密码最大连接数限制如果Web应用程序所支持的某些数据库操作可以被所有用户执行而其它一些操作应由特别许可的用户执行则可以为两类操作分别定义连接池两个连接池使用相同的JDBC URL但使用不同的帐号和密码
类DBConnectionPool的建构函数需要上述所有数据作为其参数如至行所示这些数据被保存为它的实例变量
如至行至行所示 客户程序可以使用DBConnectionPool类提供的两个方法获取可用连接两者的共同之处在于如连接池中存在可用连接则直接返回否则创建新的连接并返回如果没有可用连接且已有连接总数等于最大限制数第一个方法将直接返回null而第二个方法将等待直到有可用连接为止
所有的可用连接对象均登记在名为freeConnections的向量(Vector)中如果向量中有多于一个的连接getConnection()总是选取第一个同时由于新的可用连接总是从尾部加入向量从而使得数据库连接由于长时间闲置而被关闭的风险减低到最小程度
第一个getConnection()在返回可用连接给客户程序之前调用了isClosed()方法验证连接仍旧有效如果该连接被关闭或触发异常getConnection()递归地调用自己以尝试获取另外的可用连接如果在向量freeConnections中不存在任何可用连接getConnection()方法检查是否已经指定最大连接数限制如已经指定则检查当前连接数是否已经到达极限此处maxConn为表示没有限制如果没有指定最大连接数限制或当前连接数小于该值该方法尝试创建新的连接如创建成功则增加已使用连接的计数并返回否则返回空值
如至行所示创建新连接由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向量并关闭所有连接然后从向量中删除这些连接
四类DBConnectionManager 说明\
该类只能创建一个实例其它对象能够调用其静态方法(也称为类方法)获得该唯一实例的引用如至行所示DBConnectionManager类的建构函数是私有的这是为了避免其它对象创建该类的实例
DBConnectionManager类的客户程序可以调用getInstance()方法获得对该类唯一实例的引用如至行所示类的唯一实例在getInstance()方法第一次被调用期间创建此后其引用就一直保存在静态变量instance中每次调用getInstance()都增加一个DBConnectionManager的客户程序计数即该计数代表引用DBConnectionManager唯一实例的客户程序总数它将被用于控制连接池的关闭操作
该类实例的初始化工作由至行之间的私有方法init()完成其中 getResourceAsStream()方法用于定位并打开外部文件外部文件的定位方法依赖于类装载器的实现标准的本地类装载器查找操作总是开始于类文件所在路径也能够搜索CLASSPATH中声明的路径dbproperties是一个属性文件它包含定义连接池的键值对可供定义的公用属性如下
drivers 以空格分隔的JDBC驱动程序类列表
logfile 日志文件的绝对路径
其它的属性和特定连接池相关其属性名字前应加上连接池名字
< poolname>url 数据库的 JDBC URL
< poolname>maxconn 允许建立的最大连接数表示没有限制
< poolname>user 用于该连接池的数据库帐号
< poolname>password 相应的密码
其中url属性是必需的而其它属性则是可选的数据库帐号和密码必须合法用于Windows平台的dbproperties文件示例如下
drivers=sunjdbcodbcJdbcOdbcDriver jdbcidbDriver
logfile=D:\\user\\src\\java\\DBConnectionManager\\logtxt
idburl=jdbc:idb:c:\\local\\javawebserver\\db\\dbprp
idbmaxconn=
accessurl=jdbc:odbc:demo
accessuser=demo
accesspassword=demopw
注意在Windows路径中的反斜槓必须输入个这是由于属性文件中的反斜槓同时也是一个转义字符
init()方法在创建属性对象并读取dbproperties文件之后就开始检查logfile属性如果属性文件中没有指定日志文件则默认为当前目录下的DBConnectionManagerlog文件如日志文件无法使用则向Systemerr输出日志记录
装载和注册所有在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()递减后的引用计数为就可以调用各个连接池的release()方法关闭所有连接了管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册
五Servlet使用连接池示例
Servlet API所定义的Servlet生命周期类如
) 创建并初始化Servlet(init()方法)
) 响应客户程序的服务请求(service()方法)
) Servlet终止运行释放所有资源(destroy()方法)
本例演示连接池应用上述关键步骤中的相关操作为
) 在init()用实例变量connMgr 保存调用DBConnectionManagergetInstance()所返回的引用
) 在service()调用getConnection()执行数据库操作用freeConnection()将连接返回给连接池
) 在destroy()调用release()关闭所有连接释放所有资源
示例程序清单如下
import javaio*;import javasql*;import javaxservlet*;import javaxservlethttp*;public class TestServlet extends HttpServlet { private DBConnectionManager connMgr; public void init(ServletConfig conf) throws ServletException { superinit(conf); connMgr = DBConnectionManagergetInstance(); } public void service(HttpServletRequest req HttpServletResponse res) throws IOException { ressetContentType(text/html); PrintWriter out = resgetWriter(); Connection con = connMgrgetConnection(idb); if (con == null) { outprintln(不能获取数据库连接); return; } ResultSet rs = null; ResultSetMetaData md = null; Statement stmt = null; try { stmt = concreateStatement(); rs = stmtexecuteQuery(SELECT * FROM EMPLOYEE); md = rsgetMetaData(); outprintln(< H>职工数据< /H>); while (rsnext()) { outprintln(< BR>); for (int i = ; i < mdgetColumnCount(); i++) { outprint(rsgetString(i) + ); } } stmtclose(); rsclose(); } catch (SQLException e) { eprintStackTrace(out); } connMgrfreeConnection(idb con); } public void destroy() { connMgrrelease(); superdestroy(); }}
con); } public void destroy() { connMgrrelease(); superdestroy(); }}