zhyiwww
用平实的笔,记录编程路上的点点滴滴………
posts - 536,comments - 394,trackbacks - 0

用连接池提高 Servlet 访问数据库的效率(转载)

 

Java Servlet 作为首选的服务器端数据处理技术,正在迅速取代 CGI 脚本。 Servlet 超越 CGI 的优势之一在于,不仅多个请求可以共享公用资源,而且还可以在不同用户请求之间保留持续数据。本文介绍一种充分发挥该特色的实用技术,即数据库连接池。

 

 

一、实现连接池的意义

 

动态 Web 站点往往用数据库存储的信息生成 Web 页面,每一个页面请求导致一次数据库访问。连接数据库不仅要开销一定的通讯和内存资源,还必须完成用户验证、安全上下文配置这类任务,因而往往成为最为耗时的操作。当然,实际的连接时间开销千变万化,但 1 2 秒延迟并非不常见。如果某个基于数据库的 Web 应用只需建立一次初始连

接,不同页面请求能够共享同一连接,就能获得显著的性能改善。

Servlet 是一个 Java 类。 Servlet 引擎(它可能是 Web 服务软件的一部分,也可能是一个独立的附加模块)在系统启动或 Servlet 第一次被请求时将该类装入 Java 虚拟机并创建它的一个实例。不同用户请求由同一 Servlet 实例的多个独立线程处理。那些要

求在不同请求之间持续有效的数据既可以用 Servlet 的实例变量来保存,也可以保存在独立的辅助对象中。

JDBC 访问数据库首先要创建与数据库之间的连接,获得一个连接对象( Connection ),由连接对象提供执行 SQL 语句的方法。

本文介绍的数据库连接池包括一个管理类 DBConnectionManager ,负责提供与多个连接池对象( DBConnectionPool 类)之间的接口。每一个连接池对象管理一组 JDBC 连接对象,每一个连接对象可以被任意数量的 Servlet 共享。

DBConnectionPool 提供以下功能:

 

1) 从连接池获取(或创建)可用连接。

2) 把连接返回给连接池。

3) 在系统关闭时释放所有资源,关闭所有连接。

 

此外, DBConnectionPool 类还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题)

,并能够限制连接池中的连接总数不超过某个预定值。

管理类 DBConnectionManager 用于管理多个连接池对象,它提供以下功能:

 

1) 装载和注册 JDBC 驱动程序。

2) 根据在属性文件中定义的属性创建连接池对象。

3) 实现连接池名字与其实例之间的映射。

4) 跟踪客户程序对连接池的引用,保证在最后一个客户程序结束时安全地关闭所有连接池。

 

本文余下部分将详细说明这两个类,最后给出一个示例演示 Servlet 使用连接池的一般过程。

 

 

二、具体实现

 

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 }

转自:动态网制作指南 www.knowsky.com



|----------------------------------------------------------------------------------------|
                           版权声明  版权所有 @zhyiwww
            引用请注明来源 http://www.blogjava.net/zhyiwww   
|----------------------------------------------------------------------------------------|
posted on 2006-06-02 19:08 zhyiwww 阅读(350) 评论(0)  编辑  收藏 所属分类: j2ee

只有注册用户登录后才能发表评论。


网站导航: