Databases 23 min read

Understanding Druid Connection Pool: Initialization, Connection Acquisition, Execution, and Recycling

This article provides a comprehensive technical walkthrough of the Druid database connection pool, covering its architecture, initialization flow, connection acquisition and release mechanisms, execution and exception handling, as well as recommended configurations and monitoring practices for optimal performance.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
Understanding Druid Connection Pool: Initialization, Connection Acquisition, Execution, and Recycling

1 Introduction

When troubleshooting database connection‑pool issues such as connection starvation, exceptions, long‑running holds, or performance overhead, many developers only have a superficial understanding of connection pools. This article explains the working principle of the Druid connection pool and offers practical recommendations.

2 Druid Overview

With a connection pool, multiple database connections are created at application startup and stored in the pool. When a client request arrives, a connection is taken from the pool; after the request finishes, the connection is returned to the pool, similar to a shared‑bike system.

Resource reuse : Reusing connections avoids the cost of frequent creation and release.

Performance improvement : Connections are ready for immediate use, reducing response latency.

Optimized resource allocation : Limits on maximum active connections prevent a single application from monopolizing the database.

Connection management : Idle‑timeouts and forced reclamation prevent resource leaks.

3 Initialization Process init()

The initialization is triggered on the first getConnection() call or when init() is invoked directly.

3.1 LogStatsThread (Druid-ConnectionPool-Log-)

If timeBetweenLogStatsMillis > 0, the thread logs pool statistics at the configured interval.

3.2 CreateConnectionThread (Druid-ConnectionPool-Create-)

This background thread creates new physical connections when the empty condition signal is received and the pool has not reached maxActive .

if (activeCount + poolingCount >= maxActive) {
    empty.await();
    continue;
}

3.2.1 Condition to create a new connection

The thread checks whether creating a new connection would exceed maxActive . If so, it waits for the next empty signal.

3.2.2 createPhysicalConnection() – creating a physical JDBC connection

public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
    // ... (omitted for brevity)
    Connection conn = createPhysicalConnection(url, physicalConnectProperties);
    // initialization and validation logic
    return new PhysicalConnectionInfo(conn, ...);
}

3.2.3 put(holder, createTaskId) – placing a connection into the pool

private boolean put(DruidConnectionHolder holder, long createTaskId) {
    lock.lock();
    try {
        if (poolingCount >= maxActive) return false;
        connections[poolingCount] = holder;
        incrementPoolingCount();
        notEmpty.signal();
        notEmptySignalCount++;
        return true;
    } finally {
        lock.unlock();
    }
}

3.3 DestroyConnectionThread (Druid-ConnectionPool-Destroy-)

This thread periodically scans idle connections (interval defined by timeBetweenEvictionRunsMillis ) and performs eviction, keep‑alive probing, or forced reclamation of abandoned connections.

public void run() {
    shrink(true, keepAlive);
    if (isRemoveAbandoned()) {
        removeAbandoned();
    }
}

3.3.1 shrink(checkTime, keepAlive) – eviction logic

public void shrink(boolean checkTime, boolean keepAlive) {
    // iterate over connections, evaluate idle time, physical timeout, etc.
    // evict or keepAlive as appropriate
}

3.3.2 removeAbandoned() – leak detection

If removeAbandoned is enabled and a connection has been borrowed longer than removeAbandonedTimeoutMillis , the connection is forcibly closed and returned to the pool.

public int removeAbandoned() {
    // iterate over active connections, detect leaks, close them
    return removeCount;
}

4 Get Connection Process getConnection()

The core method getConnectionDirect() obtains an available connection from the pool.

public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
    for (;;) {
        DruidPooledConnection pc = getConnectionInternal(maxWaitMillis);
        if (testOnBorrow) {
            boolean valid = testConnectionInternal(pc.holder, pc.conn);
            if (!valid) {
                discardConnection(pc.holder);
                continue;
            }
        }
        // optional testWhileIdle logic omitted for brevity
        if (removeAbandoned) {
            // record stack trace and borrow time
            activeConnections.put(pc, PRESENT);
        }
        return pc;
    }
}

4.1 getConnectionInternal() – fetch a raw connection

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
    // wait for a free connection or create a new one
    // increment usage count
    return new DruidPooledConnection(holder);
}

4.2 takeLast() – block until a connection is available

DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
    while (poolingCount == 0) {
        emptySignal();
        notEmptyWaitThreadCount++;
        try {
            notEmpty.await();
        } finally {
            notEmptyWaitThreadCount--;
        }
    }
    decrementPoolingCount();
    DruidConnectionHolder last = connections[poolingCount];
    connections[poolingCount] = null;
    return last;
}

5 Execution & Exception Handling

MyBatis invokes SqlSessionTemplate$SqlSessionInterceptor.invoke() , which ultimately calls DruidPooledPreparedStatement.execute() . If an exception occurs, DruidDataSource.handleConnectionException() determines whether the connection is fatal and may discard it.

public boolean execute() throws SQLException {
    conn.beforeExecute();
    try {
        return stmt.execute();
    } catch (Throwable t) {
        errorCheck(t);
        throw checkException(t);
    } finally {
        conn.afterExecute();
    }
}

5.1.1 handleConnectionException()

public void handleConnectionException(DruidPooledConnection pc, Throwable t, String sql) throws SQLException {
    if (t instanceof SQLException) {
        SQLException sqlEx = (SQLException) t;
        if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {
            handleFatalError(pc, sqlEx, sql);
        }
        throw sqlEx;
    } else {
        throw new SQLException("Error", t);
    }
}

6 Recycle Process recycle()

After SQL execution, the application calls DruidPooledConnection.recycle() , which delegates to DruidDataSource.recycle() to reset and return the connection to the pool.

public void recycle() throws SQLException {
    DruidConnectionHolder holder = this.holder;
    DruidAbstractDataSource ds = holder.getDataSource();
    ds.recycle(this);
}

6.1 DruidDataSource.recycle()

protected void recycle(DruidPooledConnection pc) throws SQLException {
    DruidConnectionHolder holder = pc.holder;
    Connection physical = holder.conn;
    try {
        holder.reset();
        if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
            discardConnection(holder);
            return;
        }
        if (testOnReturn) {
            // optional validation logic
        }
        if (phyTimeoutMillis > 0 && System.currentTimeMillis() - holder.connectTimeMillis > phyTimeoutMillis) {
            discardConnection(holder);
            return;
        }
        lock.lock();
        try {
            boolean result = putLast(holder, System.currentTimeMillis());
            recycleCount++;
            if (!result) JdbcUtils.close(holder.conn);
        } finally {
            lock.unlock();
        }
    } catch (Throwable e) {
        // error handling omitted
    }
}

6.2 putLast() – return connection to the tail of the pool and signal notEmpty

boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
    if (poolingCount >= maxActive || e.discard) return false;
    e.lastActiveTimeMillis = lastActiveTimeMillis;
    connections[poolingCount] = e;
    incrementPoolingCount();
    notEmpty.signal();
    notEmptySignalCount++;
    return true;
}

7 Summary

init() : creates initialSize connections and starts LogStatsThread, CreateConnectionThread, and DestroyConnectionThread.

getConnection() : removes a connection from the pool; the connection is bound to the requesting thread.

recycle() : returns the connection to the pool, making it available for reuse.

7.2 Condition‑Signal Collaboration

When a request finds the pool empty, it sends an empty signal and waits on notEmpty . The CreateConnectionThread reacts to empty by creating a new connection (if allowed) and then signals notEmpty .

When a connection is recycled, notEmpty is signaled, waking any waiting request threads.

7.3 Detection and Destruction Logic

Borrowing: optional testOnBorrow and testWhileIdle validation.

Execution: fatal exceptions identified by exceptionSorter trigger handleFatalError() .

Returning: testOnReturn , usage‑count limit phyMaxUseCount , and physical timeout phyTimeoutMillis may cause discarding.

DestroyConnectionThread: evicts idle connections based on minEvictableIdleTimeMillis , maxEvictableIdleTimeMillis , keep‑alive probing, and abandoned‑connection removal.

8 Common & Recommended Configuration

Typical Spring XML configuration for a Druid datasource:

<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="name" value="userdataSource"/>
    <property name="url" value="${userdataSource_url}"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="initialSize" value="1"/>
    <property name="minIdle" value="3"/>
    <property name="maxActive" value="20"/>
    <property name="maxWait" value="60000"/>
    <property name="validationQuery" value="SELECT 1"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testWhileIdle" value="true"/>
    <property name="testOnReturn" value="false"/>
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>

This configuration creates one connection at startup, keeps at least three idle connections, caps the total at twenty, waits up to 60 seconds for a connection, and performs periodic validation and eviction.

9 Monitoring

By leveraging Druid’s SPI extension points, a custom monitoring component can export metrics to Prometheus, providing real‑time visibility of pool size, active/idle counts, wait times, and error rates.

Reference: [1] Druid – https://github.com/alibaba/druid [2] "聊聊数据库连接池 Druid" – https://www.cnblogs.com/makemylife/p/17889584.html

JavaperformanceDatabaseConnection PoolDruid
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.