一种实现数据库连接池的方法(3)

发表于:2007-07-04来源:作者:点击数: 标签:
DataSourceImpl是一个实现了接口 java x. sql .DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方
DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection


/**
* @see javax.sql.DataSource#getConnection(String,String)
*/
    public Connection getConnection(String user, String password) throws SQLException
    {
        //首先从连接池中找出空闲的对象
        Connection conn = getFreeConnection(0);
        if(conn == null){
            //判断是否超过最大连接数,如果超过最大连接数
            //则等待一定时间查看是否有空闲连接,否则抛出异常告诉用户无可用连接
            if(getConnectionCount() >= connParam.getMaxConnection())
                conn = getFreeConnection(connParam.getWaitTime());
            else{//没有超过连接数,重新获取一个数据库的连接
                connParam.setUser(user);
                connParam.setPassword(password);
                Connection conn2 = DriverManager.getConnection(connParam.getUrl(),
                user, password);
                //代理将要返回的连接对象
                _Connection _conn = new _Connection(conn2,true);
                synchronized(conns){
                    conns.add(_conn);
                }
                conn = _conn.getConnection();
            }
        }
        return conn;
    }
    /**
     * 从连接池中取一个空闲的连接
     * @param nTimeout    如果该参数值为0则没有连接时只是返回一个null
     * 否则的话等待nTimeout毫秒看是否还有空闲连接,如果没有抛出异常
     * @return Connection
     * @throws SQLException
     */
    protected synchronized Connection getFreeConnection(long nTimeout)
        throws SQLException
    {
        Connection conn = null;
        Iterator iter = conns.iterator();
        while(iter.hasNext()){
            _Connection _conn = (_Connection)iter.next();
            if(!_conn.isInUse()){
                conn = _conn.getConnection();
                _conn.setInUse(true);                
                break;
            }
        }
        if(conn == null && nTimeout > 0){
            //等待nTimeout毫秒以便看是否有空闲连接
            try{
                Thread.sleep(nTimeout);
            }catch(Exception e){}
            conn = getFreeConnection(0);
            if(conn == null)
                throw new SQLException("没有可用的数据库连接");
        }
        return conn;
    }




DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。

终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。


/**
* 数据连接的自封装,屏蔽了close方法
* @author Liudong
*/
class _Connection implements InvocationHandler
{
    private final static String CLOSE_METHOD_NAME = "close";
    private Connection conn = null;
    //数据库的忙状态
    private boolean inUse = false;
    //用户最后一次访问该连接方法的时间
    private long lastAccessTime = System.currentTimeMillis();
    
    _Connection(Connection conn, boolean inUse){
        this.conn = conn;
        this.inUse = inUse;
    }
    /**
     * Returns the conn.
     * @return Connection
     */
    public Connection getConnection() {
        //返回数据库连接conn的接管类,以便截住close方法
        Connection conn2 = (Connection)Proxy.newProxyInstance(
            conn.getClass().getClassLoader(),
            conn.getClass().getInterfaces(),this);
        return conn2;
    }
    /**
     * 该方法真正的关闭了数据库的连接
     * @throws SQLException
     */
    void close() throws SQLException{
        //由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接
        conn.close();
    }
    /**
     * 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;
        //判断是否调用了close的方法,如果调用close方法则把连接置为无用状态
        if(CLOSE_METHOD_NAME.equals(m.getName()))
            setInUse(false);        
        else
            obj = m.invoke(conn, args);    
        //设置最后一次访问时间,以便及时清除超时的连接
        lastAccessTime = System.currentTimeMillis();
        return obj;
    }
        
    /**
     * Returns the lastAccessTime.
     * @return long
     */
    public long getLastAccessTime() {
        return lastAccessTime;
    }

    /**
     * Sets the inUse.
     * @param inUse The inUse to set
     */
    public void setInUse(boolean inUse) {
        this.inUse = inUse;
    }
}




一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:


    /**
     * 关闭该连接池中的所有数据库连接
     * @return int 返回被关闭连接的个数
     * @throws SQLException
     */
    public int close() throws SQLException
    {
        int cc = 0;
        SQLException excp = null;
        Iterator iter = conns.iterator();
        while(iter.hasNext()){
            try{
                ((_Connection)iter.next()).close();
                cc ++;
            }catch(Exception e){
                if(e instanceof SQLException)
                    excp = (SQLException)e;
            }
        }
        if(excp != null)
            throw excp;
        return cc;
    }




该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。

以上文字只是描述了接口方法的接管,具体一个实用的连接池模块还需要对空闲连接的监控并及时释放连接,详细的代码请参照附件。

原文转自:http://www.ltesting.net