关于连接池

来源:互联网 发布:中上社会地位 知乎 编辑:程序博客网 时间:2024/05/01 16:29
到目前为目,JDBC2的连结池只是一个接口,没有真正的实现,JDBC3正在开发中,据报已经支持连结池,但..........
JDBC3用了JNDI技术,连结池的配置可以让一个高手都烦死.

目前第三方已经实现的连结池当然是poolman,1.0版对一般用户来说已经足够用了.配置也简单,2.0版虽然增加了一些功能,但配置也是采用JNDI,对RMI和EJB不懂的朋友可能很烦.建议用1.0的了.

如果有兴趣,自己也可以实现连结池,最关键的技术也就是把连结作为参数传给一个BEAN,用完后返回这个参数连结而不是关闭.
下面是一个简单的实现:
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?连接对象//r

????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?以毫秒计的等待时间//r

????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?*?关闭所有连接,撤销驱动程序的注册//r

????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驱动程序//r

????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?*?此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最//r

????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?????277?con?=?newConnection();
????278?}
????279?if?(con?!=?null)?{
????280?checkedOut++;
????281?}
????282?return?con;
????283?}
????284
????285?/**
????286?*?从连接池获取可用连接.可以指定客户程序能够等待的最长时间//r

????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?}

????三、类DBConnectionPool说明/
????
????该类在209至345行实现,它表示指向某个数据库的连接池。数据库由JDBC?URL标识。一个JDBC?URL由三部分组成:协议标识(总是jdbc),驱动程序标识(如?odbc、idb、oracle等),数据库标识(其格式依赖于驱动程序)。例如,jdbc:odbc:demo,即是一个指向demo数据库的JDBC?URL,而且访问该数据库要使用JDBC-ODBC驱动程序。每个连接池都有一个供客户程序使用的名字以及可选的用户帐号、密码、最大连接数限制。如果Web应用程序所支持的某些数据库操作可以被所有用户执行,而其它一些操作应由特别许可的用户执行,则可以为两类操作分别定义连接池,两个连接池使用相同的JDBC?URL,但使用不同的帐号和密码。
????类DBConnectionPool的建构函数需要上述所有数据作为其参数。如222至238行所示,这些数据被保存为它的实例变量:
????如252至283行、285至305行所示,?客户程序可以使用DBConnectionPool类提供的两个方法获取可用连接。两者的共同之处在于:如连接池中存在可用连接,则直接返回,否则创建新的连接并返回。如果没有可用连接且已有连接总数等于最大限制数,第一个方法将直接返回null,而第二个方法将等待直到有可用连接为止。
????所有的可用连接对象均登记在名为freeConnections的向量(Vector)中。如果向量中有多于一个的连接,getConnection()总是选取第一个。同时,由于新的可用连接总是从尾部加入向量,从而使得数据库连接由于长时间闲置而被关闭的风险减低到最小程度。
????第一个getConnection()在返回可用连接给客户程序之前,调用了isClosed()方法验证连接仍旧有效。如果该连接被关闭或触发异常,getConnection()递归地调用自己以尝试获取另外的可用连接。如果在向量freeConnections中不存在任何可用连接,getConnection()方法检查是否已经指定最大连接数限制。如已经指定,则检查当前连接数是否已经到达极限。此处maxConn为0表示没有限制。如果没有指定最大连接数限制或当前连接数小于该值,该方法尝试创建新的连接。如创建成功,则增加已使用连接的计数并返回,否则返回空值。
????如325至345行所示,创建新连接由newConnection()方法实现。创建过程与是否已经指定数据库帐号、密码有关。
????JDBC的DriverManager类提供多个getConnection()方法,这些方法要用到JDBC?URL与其它一些参数,如用户帐号和密码等。DriverManager将使用指定的JDBC?URL确定适合于目标数据库的驱动程序及建立连接。
????在285至305行实现的第二个getConnection()方法需要一个以毫秒为单位的时间参数,该参数表示客户程序能够等待的最长时间。建立连接的具体操作仍旧由第一个getConnection()方法实现。
????该方法执行时先将startTime初始化为当前时间。在while循环中尝试获得一个连接。如果失败,则以给定的时间值为参数调用wait()。wait()的返回可能是由于其它线程调用notify()或notifyAll(),也可能是由于预定时间已到。为找出wait()返回的真正原因,程序用当前时间减开始时间(startTime),如差值大于预定时间则返回空值,否则再次调用getConnection()。
????把空闲的连接登记到连接池由240至250行的freeConnection()方法实现,它的参数为返回给连接池的连接对象。该对象被加入到freeConnections向量的末尾,然后减少已使用连接计数。调用notifyAll()是为了通知其它正在等待可用连接的线程。
????许多Servlet引擎为实现安全关闭提供多种方法。数据库连接池需要知道该事件以保证所有连接能够正常关闭。DBConnectionManager类负协调整个关闭过程,但关闭连接池中所有连接的任务则由DBConnectionPool类负责。在307至323行实现的release()方法供DBConnectionManager调用。该方法遍历freeConnections向量并关闭所有连接,然后从向量中删除这些连接。
????
????
????四、类DBConnectionManager?说明/
????
????该类只能创建一个实例,其它对象能够调用其静态方法(也称为类方法)获得该唯一实例的引用。如031至036行所示,DBConnectionManager类的建构函数是私有的,这是为了避免其它对象创建该类的实例。
????DBConnectionManager类的客户程序可以调用getInstance()方法获得对该类唯一实例的引用。如018至029行所示,类的唯一实例在getInstance()方法第一次被调用期间创建,此后其引用就一直保存在静态变量instance中。每次调用getInstance()都增加一个DBConnectionManager的客户程序计数。即,该计数代表引用DBConnectionManager唯一实例的客户程序总数,它将被用于控制连接池的关闭操作。
????该类实例的初始化工作由146至168行之间的私有方法init()完成。其中?getResourceAsStream()方法用于定位并打开外部文件。外部文件的定位方法依赖于类装载器的实现。标准的本地类装载器查找操作总是开始于类文件所在路径,也能够搜索CLASSPATH中声明的路径。db.properties是一个属性文件,它包含定义连接池的键-值对。可供定义的公用属性如下:
????
????drivers?以空格分隔的JDBC驱动程序类列表/
????logfile?日志文件的绝对路径
????
????其它的属性和特定连接池相关,其属性名字前应加上连接池名字:
????
????.url?数据库的?JDBC?URL?
????.maxconn?允许建立的最大连接数,0表示没有限制?
????.user?用于该连接池的数据库帐号
????.password?相应的密码/
????
????其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。用于Windows平台的db.properties文件示例如下:
????
????drivers=sun.jdbc.odbc.JdbcOdbcDriver?jdbc.idbDriver
????logfile=D://user//src//java//DBConnectionManager//log.txt
????
????idb.url=jdbc:idb:c://local//javawebserver1.1//db//db.prp
????idb.maxconn=2
????
????access.url=jdbc:odbc:demo
????access.user=demo
????access.password=demopw
????
????注意在Windows路径中的反斜杠必须输入2个,这是由于属性文件中的反斜杠同时也是一个转义字符。
????init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。
????装载和注册所有在drivers属性中指定的JDBC驱动程序由170至192行之间的loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在?DriverManager中注册该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC?驱动程序的注册。
????init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。如109至142行所示,createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类?)pools实现连接池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。
????为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。它们的实现分别在051至064行、066至080行、038至049行。
????如082至107行所示,为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。
????
????
????五、Servlet使用连接池示例
????
????Servlet?API所定义的Servlet生命周期类如:
????
????1)?创建并初始化Servlet(init()方法)。
????2)?响应客户程序的服务请求(service()方法)。
????3)?Servlet终止运行,释放所有资源(destroy()方法)。
????
????本例演示连接池应用,上述关键步骤中的相关操作为:
????
????1)?在init(),用实例变量connMgr?保存调用DBConnectionManager.getInstance()所返回的引用。
????2)?在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。
????3)?在destroy(),调用release()关闭所有连接,释放所有资源。
????
????示例程序清单如下:
????

import?java.io.*;
import?java.sql.*;
import?javax.servlet.*;
import?javax.servlet.http.*;
public?class?TestServlet?extends?HttpServlet?{
????private?DBConnectionManager?connMgr;
????public?void?init(ServletConfig?conf)?throws?ServletException?{
????????super.init(conf);
????????connMgr?=?DBConnectionManager.getInstance();
????}
????public?void?service(HttpServletRequest?req,?HttpServletResponse?res)
????????throws?IOException?{
????????res.setContentType("text/html");
????????PrintWriter?out?=?res.getWriter();
????????Connection?con?=?connMgr.getConnection("idb");
????????if?(con?==?null)?{
????????????out.println("不能获取数据库连接.");
????????????return;
????????}
????????ResultSet?rs?=?null;
????????ResultSetMetaData?md?=?null;
????????Statement?stmt?=?null;
????????try?{
????????????stmt?=?con.createStatement();
????????????rs?=?stmt.executeQuery("SELECT?*?FROM?EMPLOYEE");
????????????md?=?rs.getMetaData();
????????????out.println("职工数据");
????????????while?(rs.next())?{
????????????????out.println("");
????????????????for?(int?i?=?1;?i?????????????????????out.print(rs.getString(i)?+?",?");
????????????????}
????????????}
????????????stmt.close();
????????????rs.close();
????????}?catch?(SQLException?e)?{
????????????e.printStackTrace(out);
????????}
????????connMgr.freeConnection("idb",?con);
????}
????public?void?destroy()?{
????????connMgr.release();
????????super.destroy();
????}
}
原创粉丝点击