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

人生没有如果,只有后果和结果。对于过去,不可忘记,但要放下。因为有明天,今天永远只是起跑线。
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;005006 /**007 * 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接008 * 池的访问.客户程序可以调用getInstance()方法访问本类的唯一实例.009 */010 public class DBConnectionManager {011 static private DBConnectionManager instance; // 唯一实例012 static private int clients;013014 private Vector drivers = new Vector();015 private PrintWriter log;016 private Hashtable pools = new Hashtable();017018 /**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 }030031 /**032 * 建构函数私有以防止其它对象创建本类实例033 */034 private DBConnectionManager() {035 init();036 }037038 /**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 }050051 /**052 * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数053 * 限制,则创建并返回新连接054 *055 * @param name 在属性文件中定义的连接池名字056 * @return Connection 可用连接或null057 */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 }065066 /**067 * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,068 * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.069 *070 * @param name 连接池名字071 * @param time 以毫秒计的等待时间072 * @return Connection 可用连接或null073 */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 }081082 /**083 * 关闭所有连接,撤销驱动程序的注册084 */085 public synchronized void release() {086 // 等待直到最后一个客户程序调用087 if (--clients != 0) {088 return;089 }090091 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 }108109 /**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 }143144 /**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 }169170 /**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 }193194 /**195 * 将文本信息写入日志文件196 */197 private void log(String msg) {198 log.println(new Date() + ": " + msg);199 }200201 /**202 * 将文本信息与异常写入日志文件203 */204 private void log(Throwable e, String msg) {205 log.println(new Date() + ": " + msg);206 e.printStackTrace(log);207 }208209 /**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;221222 /**223 * 创建新的连接池224 *225 * @param name 连接池名字226 * @param URL 数据库的JDBC URL227 * @param user 数据库帐号,或 null228 * @param password 密码,或 null229 * @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 }239240 /**241 * 将不再使用的连接返回给连接池242 *243 * @param con 客户程序释放的连接244 */245 public synchronized void freeConnection(Connection con) {246 // 将指定连接加入到向量末尾247 freeConnections.addElement(con);248 checkedOut--;249 notifyAll();250 }251252 /**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 }284285 /**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 }306307 /**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 }324325 /**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 }

到此这篇关于用连接池提高Servlet访问数据库的效率(1)就介绍到这了。爱给予的只是它自己,取走的也只从它自己,爱不占有,也不能被占有。爱就在爱中满足。更多相关用连接池提高Servlet访问数据库的效率(1)内容请查看相关栏目,小编编辑不易,再次感谢大家的支持!

标签: 连接池 Servlet