一个未成熟的数据库连接池(part 1)

一个未成熟的数据库连接池(part 1)<修正后>
shegg 原创  (参与分:32,专家分:100)   发表:2004-6-7 下午10:35   更新:2004-6-11 下午3:50   版本:2.0   阅读:2325

最近,本人着手开发要有一个有强大后台的网站,在使用连接池时,觉得使用服务器自带的连接池总有些受限制。同时,为了加深对Java的学习和研究。写下了下面的连接池类。
该连接池主要有一下功能;
1)初始化一次,到处使用。
2)强大的日志功能,记录每一个sql动作,包括Connection、ResultSet 和Statement
3)根据连接的数量,定时自动回收已经释放或超时的连接。
4)配置灵活,可以使用各种JDBC驱动程序,支持多驱动程序。

源代码:


/*
*  @Title  连接池
*  @Author: zxg
*  @Version 1.0
*  @Memo:定义数据库连接及其数据库连接池等
*/

package com.drsl.db;



import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;
import java.lang.reflect.*;
import com.mysql.jdbc.Driver;

import com.drsl.db.*;

public class ConnectionManager {
    
    static private ConnectionManager instance; // 唯一实例
    static private int clients;
    static private int maxonlinetime=30*60*1000;
    
    private Vector drivers = new Vector();
    private Hashtable pools = new Hashtable();
    private Timer checkConnTimer=new Timer();
    
    static private PrintWriter log;
    /**
    * 返回唯一实例.如果是第一次调用此方法,则创建实例
    *
    * @return ConnectionManager 唯一实例
    */

    static synchronized public ConnectionManager getInstance() {
        if (instance == null) {
            instance = new ConnectionManager();
        }
//        clients++;
        return instance;
    }
    
    /**
    * 建构函数私有以防止其它对象创建本类实例
    */

    private ConnectionManager() {
        init();
    }
    
    /**
    * 读取属性完成初始化
    */

    private void init() {
        
        try {
            
            InputStream    is =  getClass().getResourceAsStream("db.properties");
            
            Properties    dbProps = new Properties();
        
            dbProps.load(is);
        }
        catch (Exception e) {
            e.printStackTrace();
            System.err.println("不能读取属性文件= " +
            "请确保db.properties在CLASSPATH指定的路径中");
            return;
        }
        String logFile = dbProps.getProperty("logfile""log.txt");
        try {
            log = new PrintWriter(new FileWriter(logFile, true), true);
        }
        catch (IOException e) {
            System.err.println("无法打开日志文件: " + logFile);
            log = new PrintWriter(System.err);
        }
        loadDrivers(dbProps);
        createPools(dbProps);
    }
    
    /**
    * 装载和注册所有JDBC驱动程序
    *
    * @param props 属性
    */

    private void loadDrivers(Properties props) {
        String driverClasses = props.getProperty("drivers");
        StringTokenizer st = new StringTokenizer(driverClasses);
        while (st.hasMoreElements()) {
            String driverClassName = st.nextToken().trim();
            try {
                
                Driver driver = (Driver)Class.forName(driverClassName).newInstance();
                if(driver!=null){
                    DriverManager.registerDriver(driver);
                    drivers.addElement(driver);
                    log("Begin");
                    log("成功注册JDBC驱动程序" + driverClassName);
                }
                else{
                    log("Begin");
                    log("注册JDBC驱动程序" + driverClassName+"失败");
                }
                    
            }
            catch (Exception e) {
                log("Begin");
                log("无法注册JDBC驱动程序: " + driverClassName + ", 错误: " + e);
            }
        }
    }
    
    /**
    * 根据指定属性创建连接池实例.
    *
    * @param props 连接池属性
    */

    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");
                String minconn = props.getProperty(poolName + ".minconn""10");
                String option=props.getProperty(poolName+".option","");
                int max,min;
                try {
                    max = Integer.valueOf(maxconn).intValue();
                    
                }
                catch (NumberFormatException e) {
                    log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName);
                    max = 0;
                }
                
                try {
                    min = Integer.valueOf(minconn).intValue();
                    
                }
                catch (NumberFormatException e) {
                    log("错误的最小连接数限制: " + minconn + " .连接池: " + poolName);
                    min = 0;
                }
                
                try{
                    ConnectionPool pool = new ConnectionPool(poolName, url,user,password,min,max,option);
                    
                    pools.put(poolName, pool);
                    
                    //2秒钟后开始每个一分钟检查一次连接池情况
                    checkConnTimer.schedule(pool,2000,60*1000);

                    log("成功创建连接池" + poolName);
                    
                }catch(Exception e){
                    log(e,"创建DBConnectionPool出错");
                }
            }
        }
    }

    /**
    * 将连接对象返回给由名字指定的连接池
    *
    * @param name 在属性文件中定义的连接池名字
    * @param con 连接对象
    */

    public void freeConnection(String name, Connection conn) {
        ConnectionPool pool = (ConnectionPool) pools.get(name);
        if (pool != null) {
            pool.freeConnection(conn);
        }
    }
    
    /**
    * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
    * 限制,则创建并返回新连接
    *
    * @param name 在属性文件中定义的连接池名字
    * @return Connection 可用连接或null
    */

    public Connection getConnection(String name) {
        ConnectionPool pool = (ConnectionPool) pools.get(name);
        if (pool != null) {
            return pool.getConnection();
        }
        return null;
    }
    
    /**
    * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
    * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
    *
    * @param name 连接池名字
    * @param time 以毫秒计的等待时间
    * @return Connection 可用连接或null
    */

    public Connection getConnection(String name, long time) {
        ConnectionPool pool = (ConnectionPool) pools.get(name);
        if (pool != null) {
            return pool.getConnection(time);
        }
        return null;
    }
    
    /**
    * 关闭所有连接,撤销驱动程序的注册
    */

    public synchronized void release() {
    // 等待直到最后一个客户程序调用
//        if (--clients != 0) {
//            return;
//        }
        
        checkConnTimer.cancel();
        
        Enumeration allPools = pools.elements();
        while (allPools.hasMoreElements()) {
            ConnectionPool pool = (ConnectionPool) allPools.nextElement();
            pool.cancel();
            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());
            }
        }

    }
    
    /**
    * 将文本信息写入日志文件
    */

    static public void log(String msg) {
        log.println(new Date() + ": " + msg);
    }
    
    /**
    * 将文本信息与异常写入日志文件
    */

    static public void log(Throwable e, String msg) {
        log.println(new Date() + ": " + msg);
        e.printStackTrace(log);
    }
//测试

        
    static public void main(String[] args) {
        
        ConnectionManager dcm=null;
        
        try{
            dcm=ConnectionManager.getInstance();
            
            Connection conn=dcm.getConnection("mysql");
            Connection conn1=dcm.getConnection("mysql");
            Connection conn2=dcm.getConnection("mysql");
            Connection conn3=dcm.getConnection("mysql");
            Connection conn4=dcm.getConnection("mysql");
            Connection conn5=dcm.getConnection("mysql");
            Connection conn6=dcm.getConnection("mysql");
            Connection conn7=dcm.getConnection("mysql");
            Connection conn8=dcm.getConnection("mysql");
            Connection conn9=dcm.getConnection("mysql");
            Connection conn10=dcm.getConnection("mysql");
            Connection conn11=dcm.getConnection("mysql");
            Connection conn12=dcm.getConnection("mysql");
            Connection conn13=dcm.getConnection("mysql");
            Connection conn14=dcm.getConnection("mysql");
            
            ResultSet rs;
            
            String sql="select * from css";
        
            Statement st=conn.createStatement();
            if(st==null) {
                log("main--error while get /"Statement/"");
                return;
            }
            rs=st.executeQuery(sql);
            if(rs==null){
                log("main--error while get /"ResultSet/"");
                return;
            }
            System.out.println("/r/n");
                        
            while(rs.next()){
                System.out.println(rs.getString(1));
            }
            rs.close();
            st.close();
            rs=null;
            st=null;
            
            
            conn.close();
            conn1.close();
            conn2.close();
            conn3.close();
            conn4.close();
            conn5.close();
            conn6.close();
            conn7.close();
            conn8.close();
            conn9.close();
            conn10.close();
            conn11.close();
            conn12.close();
            conn13.close();
            conn14.close();
            
            conn=null;
            conn1=null;
            conn2=null;
            conn3=null;
            conn4=null;
            conn5=null;
            conn6=null;
            conn7=null;
            conn8=null;
            conn9=null;
            conn10=null;
            conn11=null;
            conn12=null;
            conn13=null;
            conn14=null;
            
        }catch(SQLException e){
            dcm.log(e,"main--error");
        }
    }

}

//



/***************连接池类*************************************************/

/**
* 此类定义了一个连接池.它能够根据要求创建新连接,直到预定的最
* 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.
* 它继承自 TimerTask 被 ConnectionManager 类的timer成员调度
*/

package com.drsl.db;

import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;
import java.lang.reflect.*;

import com.drsl.db.*;

public class ConnectionPool extends TimerTask{
    
    private int countConn;
    
    private Vector freeConns = new Vector();
    private Vector usingConns = new Vector();
    
    private int maxUseTime;//使用中的连接最大空闲时间
    private int maxFreeTime;//空闲的连接最大空闲时间(在连接数未小于最小连接数时,关闭此连接)
    
    private int maxConn;//最大连接数
    private int minConn;//最小连接数
    
    private String name;//pool name
    private String url;
    private String user;
    private String password;
    private String option;
    
//    private PrintWriter log;

    /**
    * 创建新的连接池
    *
    * @param name 连接池名字
    * @param url 数据库的JDBC url
    * @param dbInfo 数据库连接信息
    * @param maxConn 此连接池允许建立的最大连接数
    */

    public ConnectionPool(String name, String url,String user,String password, int minConn,int maxConn,String option) {
        this.name = name;
        this.url = url;
        this.user = user;
        this.password = password;
        this.option=option;
        
        this.maxConn = maxConn;
        this.minConn = minConn;
        
        if(this.minConn<=0) this.minConn=10;
        
        log("End One Part/r/n");
        for(int i=0; i<minConn;i++){
            newConnection(); 
        }
    }
    /**
    * 将新建的连接添加到连接池
    *
    * @param connobj 新建的连接
    */

    public synchronized void freeConnection(ConnectionObject connobj) {
        // 将指定连接加入到向量末尾
        try{
            connobj.setInUse(false);
            freeConns.addElement(connobj);
            log("成功记录一个新建连接或者回收一个已释放连接");
            notifyAll();
        }catch(ArrayIndexOutOfBoundsException e){
            log(e,"freeConnection(ConnectionObject connobj) --失败");
            
        }
    }
    /**
    * 将不再使用的连接返回给连接池
    *
    * @param conn 客户程序主动释放的连接
    */

    public synchronized void freeConnection(Connection conn) {
        // 将指定连接加入到向量末尾
        ConnectionObject connobj=null;
        
        for(int i=0;i<usingConns.size();i++)
        {
            connobj=(ConnectionObject)usingConns.get(i);
            
            if(connobj.getConnection()==conn)
                break;
        }
        if(connobj!=null){
            try{
                connobj.setInUse(false);
                freeConns.addElement(connobj);
                usingConns.removeElement(connobj);
                log("成功回收一个连接");
                notifyAll();
            }catch(Exception e){
                log(e,"回收一个连接--失败");
            }
        }
    }
    
    /**
    * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接
    * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之,
    * 然后递归调用自己以尝试新的可用连接.
    */

    public synchronized Connection getConnection() {
        ConnectionObject connobj = null;
        Connection conn=null;
        // 获取向量中第一个可用连接
        try {
            connobj = (ConnectionObject) freeConns.get(0);
        }
        catch (Exception e) {
            
            log("End One Part/r/n");

            log("从连接池" + name+"获取一个连接失败");
            if( maxConn == 0 || countConn < maxConn) {
                connobj = newConnection();
            }
        }
        //如没有空闲的连接且当前连接数小于最大连接数限制,则创建新连接
        if(connobj==null && ( maxConn == 0 || countConn < maxConn)) {
            log("从连接池" + name+"获取一个连接失败");
            log("End One Part/r/n");
            connobj = newConnection();
        }
        if (connobj != null) {
            
            connobj.setLastAccessTime(new Date().getTime());
            connobj.setInUse(true);
            
            usingConns.addElement(connobj);
            freeConns.removeElementAt(0);
            
            conn=connobj.getConnection();
            return conn;
            
        }else{
            log("获取连接" + name+"失败--连接数量已达最大上限");
            return null;
        }
    }
    
    /**
    * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间
    * 参见前一个getConnection()方法.
    *
    * @param timeout 以毫秒计的等待时间限制
    */

    public synchronized Connection getConnection(long timeout) {
        
        long startTime = new Date().getTime();
        Connection conn=null;
        while ((conn = getConnection()) == null) {
            try {
                wait(timeout);//??????????????
            }
            catch (InterruptedException e){
                
            }
            if ((new Date().getTime() - startTime) >= timeout) {
                // wait()返回的原因是超时?????????
                return null;
            }
        }
        return conn;
    }
    
    /**
    * 关闭所有连接
    */

    public synchronized void release() {
//        cancel();
        Enumeration allConnections = freeConns.elements();
        
        while (allConnections.hasMoreElements()) {
            ConnectionObject connobj = (ConnectionObject) allConnections.nextElement();
            try {
                connobj.close();
                connobj=null;
                log("关闭连接池" + name+"中的一个连接");
            }
            catch (SQLException e) {//SQLException
                log(e, "无法关闭连接池" + name+"中的连接");
            }
        }
        freeConns.removeAllElements();
        //
        allConnections = usingConns.elements();
        
        while (allConnections.hasMoreElements()) {
            ConnectionObject connobj = (ConnectionObject) allConnections.nextElement();
            try {
                connobj.close();
                connobj=null;
                log("关闭连接池" + name+"中的一个连接");
            }
            catch (SQLException e) {//SQLException
                log(e, "无法关闭连接池" + name+"中的连接");
            }
        }
        usingConns.removeAllElements();
    }
    
    /**
    * 创建新的连接
    */

    private ConnectionObject newConnection() {
        ConnectionObject connobj= null; 
        try {
            
            log("连接池" + name+"创建一个新的连接对象");
             
            String URL=url+option;
            
             log("URL=" +URL );
             
             Connection conn = DriverManager.getConnection(URL,user,password);
            
            connobj=new ConnectionObject(conn,false);
            
            freeConnection(connobj);
            countConn++;
            
        } 
        catch (SQLException e) { 
            log(e, "无法创建下列URL的连接: " + url+" for User= " +user+" Password="+password); 
            return null; 
        } 
        return connobj; 
    } 
    //检查各连接状态(每分钟一次)
    public void run (){
        
        ConnectionObject connobj=null;
        //回收 正在使用中的已经"关闭"的连接    
        //和 使用时间已经超时的连接
        int i=0;    
        while(i<usingConns.size()){

             connobj=(ConnectionObject)usingConns.get(i);
            if(connobj.isInUse()==false){
                try{
                        log("run--回收 正在使用中的已经/"关闭/"的连接"); 
                        freeConnection(connobj);
                        usingConns.removeElementAt(i);        
                        i--;
                }catch(ArrayIndexOutOfBoundsException e){
                    log(e,"run--回收 正在使用中的已经/"关闭/"的连接--失败"); 
                }
            }else{
            
                long nowtime=new Date().getTime();
                long t=nowtime-connobj.getLastAccessTime();
                try{
                    if(t>20*60*1000){//超时时间为20分钟
                        log("run--回收 使用时间已经超时的连接"); 
                        freeConnection(connobj);
                        usingConns.removeElementAt(i);
                        i--;
                    }
                }catch(ArrayIndexOutOfBoundsException e ){
                    log(e,"run--回收 使用时间已经超时的连接--失败"); 
                }
            }
            i++;
        }
        //删除 空闲的已经被意外关闭的连接
        i=0;
        while(i<freeConns.size()){
        
            connobj= (ConnectionObject)freeConns.get(i);
            try{
                if(connobj.getConnection().isClosed()){
                    connobj=null;
                    freeConns.removeElementAt(i);
                    countConn--;
                    i--;
                    log("run--删除 空闲的已经被意外关闭的连接"); 
                }
            }catch(Exception e){
                log(e,"run--删除 空闲的已经被意外关闭的连接-失败"); 
            }
            i++;
        }
        
        //删除 从空闲连接中多余的(大于最小连接数的)连接 
        long cc=countConn-minConn;
        i=0;
        while(i<cc && freeConns.size()>1){
            try{
                connobj=(ConnectionObject)freeConns.get(0);
                connobj.close();
                connobj=null;
                freeConns.removeElementAt(0);
                countConn--;
                log("run--删除 从空闲连接中多余的(大于最小连接数的)连接 "); 
            }catch(SQLException e){
                log(e,"run--从空闲连接中多余的(大于最小连接数的)连接--失败"); 
            }
            i++;
        }
        //增加连接 保持要求的最小连接数
        if(cc<0){
            cc=-cc;
            log("End One Part/r/n");
            log("run--增加连接 保持要求的最小连接数"); 
            for(i=0;i<cc;i++){
                newConnection();
            }
        }
        //增加连接 保持至少有一个可用连接
        if(freeConns.size()<1){
            log("End One Part/r/n");
            log("run--增加连接 保持至少有一个可用连接"); 
            newConnection();
        }
        log("run--once");
    //    notifyAll();
    }
    /**
    * 将文本信息写入日志文件
    */

    private void log(String msg) {
        ConnectionManager.log(msg);
    }
    
    /**
    * 将文本信息与异常写入日志文件
    */

    private void log(Throwable e, String msg) {
        ConnectionManager.log(e,msg);
    }
        

}


/

/**
 * 数据连接的自封装,屏蔽了close方法和createStatement,prepareStatement 方法以返回自己的接管类
 * @author zxg
 */

package com.drsl.db;

import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.Date;
import java.lang.reflect.*;

public class ConnectionObject implements InvocationHandler{
    
    private final static String CLOSE_METHOD_NAME = "close";
    private final static String CREATSTATMENT_METHOD_NAME = "createStatement";
    private final static String PREPARESTATEMENT_METHOD_NAME = "prepareStatement";
    
    private Connection conn = null;
    private    Connection conn_proxy=null;
    private PreparedStatementObject pso=null;
    private StatementObject stmo=null;
    
    //数据库的忙状态
    private boolean inUse = false;
    
    //用户最后一次访问该连接方法的时间
    private static long lastAccessTime = new Date().getTime();


    public ConnectionObject(Connection conn, boolean inUse){
        this.conn = conn;
        this.inUse = inUse;
    }
    
    /**
     * Returns the conn.
     * @return Connection
     */

    //返回数据库连接conn的接管类,以便截住Connection 的各个方法
    public Connection getConnection() {
        if(conn_proxy==null){
            ClassLoader classloader=conn.getClass().getClassLoader();
            Class[] interfaces = conn.getClass().getInterfaces(); 
            
            if(interfaces==null||interfaces.length==0){ 
                interfaces = new Class[1]; 
                interfaces[0] = Connection.class
            }
            
            try{
                
                conn_proxy= (Connection)Proxy.newProxyInstance(classloader,interfaces,this);
                
            }catch(NullPointerException e){
                log(e,"ConnectionObject getConnection()--error");
            }
            if(conn_proxy!=null)
                log("ConnectionObject getConnection()--success");
        }
        return conn_proxy;
    }
    
    /**
     * 该方法真正的关闭了数据库的连接
     * @throws SQLException
     */

    synchronized void  close() throws SQLException{
        //由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接
        conn.close();
        conn=null;
    }
    /**
     * Returns the inUse.
     * @return boolean
     */

    public boolean isInUse() {
        return inUse;
    }

    /**
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object)
     */

    public Object invoke(Object proxy, Method m, Object[] args) 
        throws Throwable 
    {
        Object obj = null;
        log("ConnectionObject--invoke:Method: /""+m.getName()+"/"");
        
        //判断是否调用了close的方法,如果调用close方法则把连接置为无用状态
        
        if(CLOSE_METHOD_NAME.equals(m.getName())){
            setInUse(false);
                    
        }else if(CREATSTATMENT_METHOD_NAME.equals(m.getName())){
            
        //如果调用了 createStatment 的方法,封装 Statement 类接受管理
        
            Statement stm=(Statement) m.invoke(conn, args);
            if(stm!=null && stmo==null){
                stmo=new StatementObject(stm);
                obj=stmo.getStatement();
            }else{
                ConnectionManager.log("ConnectionObject--invoke:Method: /""+m.getName()+"/"--失败");
            }
            
        }else if(PREPARESTATEMENT_METHOD_NAME.equals(m.getName())){
            
        //如果调用了 createStatment 的方法,封装 PreparedStatement 类接受管理
        
            PreparedStatement ps=(PreparedStatement) m.invoke(conn, args);
            
            if(ps!=null && pso==null){
                pso=new PreparedStatementObject(ps);
            }
            else if(pso!=null){
                obj=pso.getPreparedStatement();
            }else
                log("ConnectionObject--invoke:Method: /""+m.getName()+"/"--失败");
            
        }else
            obj = m.invoke(conn, args);    
            
        //设置最后一次访问时间,以便及时清除超时的连接
        lastAccessTime = new Date().getTime();
        return obj;
    }
        
    /**
     * Returns the lastAccessTime.
     * @return long
     */

    public static long getLastAccessTime() {
        return lastAccessTime;
    }
    /**
     * set the lastAccessTime.
     * @param latimelong
     */

    
    public static void setLastAccessTime(long latime) {
        lastAccessTime=latime;
    }

    /**
     * Sets the inUse.
     * @param inUse The inUse to set
     */

    public void setInUse(boolean inUse) {
        this.inUse = inUse;
    }
    
    public synchronized void release() {
        try{
            close();
        }catch(SQLException e){
            log(e,"ConnectionObject--release 调用 close 失败");
        }
    }
        /**
    * 将文本信息写入日志文件
    */

    private void log(String msg) {
        ConnectionManager.log(msg);
    }
    
    /**
    * 将文本信息与异常写入日志文件
    */

    private void log(Throwable e, String msg) {
        ConnectionManager.log(e,msg);
    }

}


待续。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值