Tomcat JDBC 连接池

Tomcat JDBC 连接池

连接池的创建

(1) 创建

// ConnectionPool.java
protected void init(PoolConfiguration properties) throws SQLException {
    busy = new LinkedBlockingQueue<>();
    if (properties.isFairQueue()) {
        idle = new FairBlockingQueue<>();
        //idle = new MultiLockFairBlockingQueue<PooledConnection>();
        //idle = new LinkedTransferQueue<PooledConnection>();
        //idle = new ArrayBlockingQueue<PooledConnection>(properties.getMaxActive(),false);
    } else {
        idle = new LinkedBlockingQueue<>();
    }
}

(2) 构建拦截链

// ConnectionPool.java
protected Connection setupConnection(PooledConnection con) throws SQLException {
    JdbcInterceptor handler = new ProxyConnection(this,con,getPoolProperties().isUseEquals());

    // 创建自定义的拦截链

    // setup statement proxy
    if (getPoolProperties().getUseStatementFacade()) {
        handler = new StatementFacade(handler);
    }

    if (getPoolProperties().getUseDisposableConnectionFacade() ) {
        connection = (Connection)proxyClassConstructor.newInstance(new Object[] { new DisposableConnectionFacade(handler) });
    }

    return connection;
}

上述构造出的拦截链的图如下所示:

代理类 -> DisposableConnectionFacade -> StatementFacade -> ProxyConnection

当应用主动调用 connection.close() 的时候,DisposableConnectionFacade 类会首先拦截该方法,并将内部的指向 StatementFacadenext 指针置为 null

// DisposableConnectionFacade.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // ...
    if (compare(CLOSE_VAL, method)) {
        setNext(null);
    }
}

后续应用如果再错误地使用该条连接执行增删改查的时候,因为 next 指针为空,所以会抛出 NullPointerException 异常,该异常被 DisposableConnectionFacade 捕获,并会重新抛出异常 PooledConnection has already been closed.

// DisposableConnectionFacade.java
@Override
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
    try {
        return super.invoke(proxy, method, args);
    } catch (NullPointerException e) {
        if (getNext() == null) {
            // ...
            throw new SQLException(
                    "PooledConnection has already been closed.");
        }

        throw e;
    } 
}

连接的回收

通过 PoolCleaner 来主动回收资源:

// ConnectionPool.java
public void initializePoolCleaner(PoolConfiguration properties) {
    //if the evictor thread is supposed to run, start it now
    if (properties.isPoolSweeperEnabled()) {
        poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
        poolCleaner.start();
    } //end if
}

那么什么情况下会创建资源回收器呢:

// PoolProperties.java
public boolean isPoolSweeperEnabled() {
    boolean timer = getTimeBetweenEvictionRunsMillis()>0;
    boolean result = timer && (isRemoveAbandoned() && getRemoveAbandonedTimeout()>0);
    result = result || (timer && getSuspectTimeout()>0);
    result = result || (timer && isTestWhileIdle() && getValidationQuery()!=null);
    result = result || (timer && getMinEvictableIdleTimeMillis()>0);
    return result;
}

资源回收器做哪些事情:

// PoolCleaner.java
if (pool.getPoolProperties().isRemoveAbandoned()
        || pool.getPoolProperties().getSuspectTimeout() > 0)
    pool.checkAbandoned();

if (pool.getPoolProperties().getMinIdle() < pool.idle.size())
    pool.checkIdle();

if (pool.getPoolProperties().isTestWhileIdle())
    pool.testAllIdle();

(1) 检查 Abandoned 连接

Iterator<PooledConnection> locked = busy.iterator();
while (locked.hasNext()) {
    PooledConnection con = locked.next();
    boolean setToNull = false;
    try {
        con.lock();

        //the con has been returned to the pool or released
        //ignore it
        if (idle.contains(con) || con.isReleased())
            continue;

        long time = con.getTimestamp();
        long now = System.currentTimeMillis();
        if (shouldAbandon() && (now - time) > con.getAbandonTimeout()) {
            busy.remove(con);
            abandon(con);
        } //end if
    } finally {
        con.unlock();
    }
}

abandon(con) 内部就是 release(con)