在开发中跟数据库不停地打交道,总是在用连接池,大多数人也都知道连接池的好处:统一管理数据库连接,避免频繁创建和销毁连接带来的开销。但是连接池究竟是如何工作的很值得探究,了解其中的原理也有利于加深对连接池配置的理解,从而更好地进行数据库连接上的优化。

仅就我自己了解的连接池有dbcp,c3p0,以及随Tomcat 7发布的tomcat jdbc pool。这次我选择了dbcp的源码来读,网上有人说dbcp写得太复杂,好几十个类,据说tomcat jdbc pool很不错,性能强,而且简单。

首先配置一个数据源,Spring有了java配置之后还是很方便的,不过配置数据源还是用xml最好。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
   destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}"
   p:testOnBorrow="true" p:testWhileIdle="true" p:validationQuery="select 1"
   p:timeBetweenEvictionRunsMillis="30000" p:minEvictableIdleTimeMillis="1800000" />

既然是连接池,那么就从最重要的操作——获取一个连接入手吧。BasicDataSource中获取连接是getConnection()方法:

/**
 * Create (if necessary) and return a connection to the database.
 *
 * @throws SQLException if a database access error occurs
 * @return a database connection
 */
public Connection getConnection() throws SQLException {
    return createDataSource().getConnection();
}

这里为什么要这么写?继续看下去就知道了,createDataSource()方法是一个proteced方法,显然是方便子类进行重写,然后继承得到的getConnection()方法就调用到子类的createDataSource(),完成子类需要的功能。

/**
 * <p>Create (if necessary) and return the internal data source we are
 * using to manage our connections.</p>
 *
 * <p><strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the
 * "double checked locking" idiom in an attempt to avoid synchronizing
 * on every single call to this method.  However, this idiom fails to
 * work correctly in the face of some optimizations that are legal for
 * a JVM to perform.</p>
 *
 * @throws SQLException if the object pool cannot be created.
 */
protected synchronized DataSource createDataSource()
    throws SQLException {
    if (closed) {
        throw new SQLException("Data source is closed");
    }

    // Return the pool if we have already created it
    if (dataSource != null) {
        return (dataSource);
    }

    // create factory which returns raw physical connections
    ConnectionFactory driverConnectionFactory = createConnectionFactory();

    // create a pool for our connections
    createConnectionPool();

    // Set up statement pool, if desired
    GenericKeyedObjectPoolFactory statementPoolFactory = null;
    if (isPoolPreparedStatements()) {
        statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
                    -1, // unlimited maxActive (per key)
                    GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
                    0, // maxWait
                    1, // maxIdle (per key)
                    maxOpenPreparedStatements);
    }

    // Set up the poolable connection factory
    createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);

    // Create and return the pooling data source to manage the connections
    createDataSourceInstance();
    
    try {
        for (int i = 0 ; i < initialSize ; i++) {
            connectionPool.addObject();
        }
    } catch (Exception e) {
        throw new SQLNestedException("Error preloading the connection pool", e);
    }
    
    return dataSource;
}

数据源当然只有一个了,所以这是一个同步方法。这个方法里首先创建了ConnectionFactory,这是一个接口,dbcp默认的BasicDataSource创建的是DriverConnectionFactory类,所有的新连接最终都是由这个类的createConnection方法创建的,DriverConnectionFactory的代码如下:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.dbcp;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;

/**
 * A {@link Driver}-based implementation of {@link ConnectionFactory}.
 *
 * @author Rodney Waldhoff
 * @version $Revision: 479137 $ $Date: 2006-11-25 10:51:48 -0500 (Sat, 25 Nov 2006) $
 */
public class DriverConnectionFactory implements ConnectionFactory {
    public DriverConnectionFactory( driver, String connectUri, Properties props) {
        _driver = driver;
        _connectUri = connectUri;
        _props = props;
    }

    public Connection createConnection() throws SQLException {
        return _driver.connect(_connectUri,_props);
    }

    protected Driver _driver = null;
    protected String _connectUri = null;
    protected Properties _props = null;

    public String toString() {
        return this.getClass().getName() + " [" + String.valueOf(_driver) + ";" + String.valueOf(_connectUri) + ";"  + String.valueOf(_props) + "]";
    }
}

回到createDataSource方法,接下来用createConnectionPool创建一个对象池,使用了apache的common-pool,具体来说是一个GenericObjectPool类。所有的操作都经过了这个类的控制,而且可以看到参数也都设置在这里。

/**
 * Creates a connection pool for this datasource.  This method only exists
 * so subclasses can replace the implementation class.
 */
protected void createConnectionPool() {
    // Create an object pool to contain our active connections
    GenericObjectPool gop;
    if ((abandonedConfig != null) && (abandonedConfig.getRemoveAbandoned())) {
        gop = new AbandonedObjectPool(null,abandonedConfig);
    }
    else {
        gop = new GenericObjectPool();
    }
    gop.setMaxActive(maxActive);
    gop.setMaxIdle(maxIdle);
    gop.setMinIdle(minIdle);
    gop.setMaxWait(maxWait);
    gop.setTestOnBorrow(testOnBorrow);
    gop.setTestOnReturn(testOnReturn);
    gop.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
    gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
    gop.setTestWhileIdle(testWhileIdle);
    connectionPool = gop;
}

再回到createDataSource方法,跳过statement pool,下面创建了一个PooledConnectionFactory,这个factory包在之前的DriverConnectionFactory外面,DriverConnectionFactory在里面负责提供连接,而外层负责“池”的功能。看下面的具体实现,PoolableConnectionFactory的构造函数中,_pool = pool;_pool.setFactory(this);所以connectionPool和PoolableConnectionFactory互相持有对方的引用。

/**
 * Creates the PoolableConnectionFactory and attaches it to the connection pool.  This method only exists
 * so subclasses can replace the default implementation.
 * 
 * @param driverConnectionFactory JDBC connection factory
 * @param statementPoolFactory statement pool factory (null if statement pooling is turned off)
 * @param configuration abandoned connection tracking configuration (null if no tracking)
 * @throws SQLException if an error occurs creating the PoolableConnectionFactory
 */
protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory,
        KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration) throws SQLException {
    PoolableConnectionFactory connectionFactory = null;
    try {
        connectionFactory =
            new PoolableConnectionFactory(driverConnectionFactory,
                                          connectionPool,
                                          statementPoolFactory,
                                          validationQuery,
                                          validationQueryTimeout,
                                          connectionInitSqls,
                                          defaultReadOnly,
                                          defaultAutoCommit,
                                          defaultTransactionIsolation,
                                          defaultCatalog,
                                          configuration);
        validateConnectionFactory(connectionFactory);
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
    }
}

之后验证一下connectionFactory好用不好用,具体就是创建一个连接,然后执行test,所以说一定要配置validationQuery语句,testOnBorrow、testWhileIdel之类的参数,如果validationQuery为null则会忽略设置自动变成false。

最后,connectionPool按照初始大小添加对象,使用addObject方法,这个方法具体如下,可以看到对象池把具体的对象创建工作交给facoty,所以GenericObjectPool是一个通用的对象池的功能,负责对象的数量管理、借出和归还等等,而创建对象、测试对象有效性等具体操作都交给了facotyr,关于这个对象池的设计,之后可以专门写一篇分析一下。

/**
 * Create an object, and place it into the pool.
 * addObject() is useful for "pre-loading" a pool with idle objects.
 */
public void addObject() throws Exception {
    assertOpen();
    if (_factory == null) {
        throw new IllegalStateException("Cannot add objects without a factory.");
    }
    Object obj = _factory.makeObject();
    try {
        assertOpen();
        addObjectToPool(obj, false);
    } catch (IllegalStateException ex) { // Pool closed
        try {
            _factory.destroyObject(obj);
        } catch (Exception ex2) {
            // swallow
        }
        throw ex;
    }
}

这个factory是一个PoolableConnectionFactory,它的makeObject方法如下,可以看到在这个方法里,driverConnectionFactory负责创建原始的连接,然后被包装成一个PoolableConnection,这个类负责提供一些跟“池”相关的功能,跟PoolableConnectionFactory与原始的DriverConnectionFactory的关系是类似的。典型的例子是PoolableConnection改写了close方法,把关闭连接改为了向对象池归还对象。

public Object makeObject() throws Exception {
    Connection conn = _connFactory.createConnection();
    if (conn == null) {
        throw new IllegalStateException("Connection factory returned null from createConnection");
    }
    initializeConnection(conn);
    if(null != _stmtPoolFactory) {
        KeyedObjectPool stmtpool = _stmtPoolFactory.createPool();
        conn = new PoolingConnection(conn,stmtpool);
        stmtpool.setFactory((PoolingConnection)conn);
    }
    return new PoolableConnection(conn,_pool,_config);
}