搞懂TomcatJdbc之连接池初始化

前言

TomcatJdbc 数据库连接池最初是设计来用于替代Apache Commons DBCP ,但现在TomcatJdbc 数据库连接池更多是与Druid 和HikariCP 数据库连接池进行比较,尽管扩展性不及Druid ,性能不及HikariCP ,但TomcatJdbc数据库连接池依旧有较为广泛的使用,这主要得益于其简单的配置和简洁的代码实现。

本文将对TomcatJdbc 数据库连接池的初始化源码进行分析,Tomcat 版本为9.0.82。

正文

TomcatJdbc 数据库连接池的初始化,发生在第一次获取数据库连接时,即第一次调用DataSourceProxy#getConnection方法的时候,如下所示。

java 复制代码
public Connection getConnection() throws SQLException {
    // 一开始pool为null
    if (pool == null) {
        // 调用createPool()方法创建连接池
        return createPool().getConnection();
    }
    return pool.getConnection();
}

public ConnectionPool createPool() throws SQLException {
    if (pool != null) {
        return pool;
    } else {
        // 调用pCreatePool()方法创建连接池
        return pCreatePool();
    }
}

private synchronized ConnectionPool pCreatePool() throws SQLException {
    if (pool != null) {
        return pool;
    } else {
        // 调用数据库连接池的构造函数
        // 初始化逻辑在构造函数中
        pool = new ConnectionPool(poolProperties);
        return pool;
    }
}

public ConnectionPool(PoolConfiguration prop) throws SQLException {
    // 调用init()方法完成初始化
    init(prop);
}

在首次获取数据库连接时,由于ConnectionPool 为null ,此时会new 一个ConnectionPool 作为数据库连接池并调用其init() 方法完成初始化,下面看一下init() 方法的实现。

java 复制代码
protected void init(PoolConfiguration properties) throws SQLException {
    // 这里的properties实际是PoolProperties
    // 用户的数据库连接池配置会加载到properties中
    // 没有配置的项就使用PoolProperties中的默认值
    poolProperties = properties;

    // 做一下数据库连接池配置的常规校验
    // 例如maxIdle不能小于minIdle
    checkPoolConfiguration(properties);

    // busy队列保存借出的正在使用的连接
    busy = new LinkedBlockingQueue<>();
    if (properties.isFairQueue()) {
        // idle队列保存可用的空闲连接
        // 默认配置下会使用公平队列
        // 越先等待的线程越先获取到连接
        idle = new FairBlockingQueue<>();
    } else {
        idle = new LinkedBlockingQueue<>();
    }

    // 初始化连接池清理器,实际是启动一个定时任务
    // 每隔TimeBetweenEvictionRunsMillis运行一次
    initializePoolCleaner(properties);

    if (this.getPoolProperties().isJmxEnabled()) {
        createMBean();
    }

    // 解析得到并初始化拦截器
    ......

    // 根据配置的初始连接数initialSize来预热连接池
    // 若未配置initialSize则默认取值为10
    PooledConnection[] initialPool = new PooledConnection[poolProperties.getInitialSize()];
    try {
        for (int i = 0; i < initialPool.length; i++) {
            // 这里创建连接时,传入的用户名和密码为null不影响连接创建
            // 创建出来的连接会先暂时存放在initialPool数组里
            initialPool[i] = this.borrowConnection(0, null, null);
        }
    } catch (SQLException x) {
        log.error("Unable to create initial connections of pool.", x);
        if (!poolProperties.isIgnoreExceptionOnPreLoad()) {
            if (jmxPool!=null) {
                jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
            }
            close(true);
            throw x;
        }
    } finally {
        for (int i = 0; i < initialPool.length; i++) {
            if (initialPool[i] != null) {
                try {
                    // 将预热出来的连接放到idle队列中
                    this.returnConnection(initialPool[i]);
                } catch(Exception x) {

                }
            }
        }
    }
    // 将连接池关闭标识置为false
    closed = false;
}

总结

通过init() 方法,我们可以总结如下几点。

  1. 连接池的默认配置在PoolProperties 中。TomcatJdbc 数据库连接池会读取用户的配置到PoolProperties 中,如果有用户没有配置的项,则会使用PoolProperties 预置的默认值,所以查看TomcatJdbc 数据库连接池配置的默认值,可以在PoolProperties中查看;
  2. 连接池的连接存放在busy 和idle 队列中。busy 队列保存借出的正在使用的连接,idle 队列保存可用的空闲连接,默认情况下,idle 队列使用的是公平队列FairBlockingQueue,以确保最先等待获取连接的线程能最先获取到连接;
  3. 连接池在初始化时会创建连接池清理器PoolCleaner 并启动。PoolCleaner 本质是一个定时任务,每间隔timeBetweenEvictionRunsMillis 运行一次,每次运行会执行连接泄漏检测 ,可用连接检测保活 和idle队列大小调整;
  4. 连接池会在初始化时预热。预热即预先创建指定数量的连接出来并放在idle 队列中,预热连接数量通过initialSize指定,不指定时默认为10。
相关推荐
法欧特斯卡雷特11 分钟前
Kotlin 新特性抢先看:伴生扩展与伴生块
后端·面试·开源
桃李醉春风27 分钟前
被微信拒审那天,我才真正学会 Vibecoding:一个人 + AI,3 万行代码、1.8 万张素材的小程序全复盘
后端
CoderYanger29 分钟前
一轮复习——F.动态规划模型总结(入门篇)
java·开发语言·数据结构·算法·leetcode·职场和发展·动态规划
她的男孩29 分钟前
打印模板草稿能保存,一点发布就报主从关系:我们把校验拆成了两档
java·spring boot·后端
小鹿的周先生30 分钟前
第19章-Agent
java·spring·ai
溪语流沙31 分钟前
【Web全栈进阶】FastAPI工程化:APIRouter拆分 + 配置 + 依赖注入
java·前端·fastapi
Bazingga1 小时前
Embabel学习笔记:把Agent的决策权从LLM手里拿回来
后端
用户8314550980311 小时前
基于 Firecracker 自建 Agent 沙箱集群:状态机 + 文件管理 + 进程编排,附 E2B 生产经验校准
后端
写后端的胖头鱼1 小时前
Redis 的 RDB 和 AOF
java·数据库·redis
cxoptics1 小时前
LBO 激光损伤阈值 LIDT 典型数值?如何避免晶体打坏?
java