一個未成熟的數據庫連接池(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);
    }

}


待續。。。
發佈了92 篇原創文章 · 獲贊 1 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章