搞懂TomcatJdbc之连接获取

前言

当我们使用类似于MyBatisORM框架来执行一条SQL时,其中一步就是会从数据库连接池里获取一个连接,了解从数据库连接池获取连接的过程,对于排查SQL耗时问题具有很大的帮助。

本文将针对TomcatJdbc数据库连接池获取连接的源码进行学习,Tomcat版本为9.0.82

TomcatJdbc往期文章:

接口访问超时引发的思考
搞懂TomcatJdbc之连接池初始化

正文

当初始化数据库连接池完毕后,会得到一个ConnectionPool,后续获取连接均从得到的ConnectionPool中获取,对应方法为ConnectionPool#getConnection,如下所示。

public Connection getConnection() throws SQLException {
    // 先从连接池获取数据库连接
    PooledConnection con = borrowConnection(-1,null,null);
    // 为获取出来的连接创建代理对象
    return setupConnection(con);
}

继续跟进ConnectionPool#borrowConnection方法,如下所示。

private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException {

    if (isClosed()) {
        throw new SQLException("Connection pool closed.");
    }

    long now = System.currentTimeMillis();
    // 从空闲队列获取一个连接
    // 若没有空闲连接则得到null
    PooledConnection con = idle.poll();

    while (true) {
        if (con!=null) {
            // 这里主要是校验一下刚刚获取到的空闲连接
            // 如果校验不通过则会重新连接一次数据库
            PooledConnection result = borrowConnection(now, con, username, password);
            // 借出的连接数加1
            borrowedCount.incrementAndGet();
            if (result!=null) {
                return result;
            }
        }

        // 执行到这里说明需要创建连接
        // size表示当前连接池的总连接数
        // 如果size小于配置的最大连接数则创建一个连接
        if (size.get() < getPoolProperties().getMaxActive()) {
            if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
                size.decrementAndGet();
            } else {
                // 创建一个连接并校验
                // 校验只有在testOnConnect配置为true时才执行
                return createConnection(now, con, username, password);
            }
        }

        // 执行到这里说明当前无空闲连接可用且无法再继续创建连接
        // 那么后续就进入等待获取连接的状态
        long maxWait = wait;
        // wait配置为-1表示等待时间使用连接池的默认值
        if (wait==-1) {
            // 默认是等待30秒
            maxWait = (getPoolProperties().getMaxWait()<=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait();
        }
        // 计算还需要等待的时间
        long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now));
        // 当前正在等待获取连接的线程计数加1
        waitcount.incrementAndGet();
        try {
            // 从空闲队列中超时等待获取连接
            con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ex) {
            if (getPoolProperties().getPropagateInterruptState()) {
                Thread.currentThread().interrupt();
            }
            SQLException sx = new SQLException("Pool wait interrupted.");
            sx.initCause(ex);
            throw sx;
        } finally {
            // 获取到连接或者等待超时都需要将wait减1
            // 表示结束等待
            waitcount.decrementAndGet();
        }
        if (maxWait==0 && con == null) {
            if (jmxPool!=null) {
                jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - no wait.");
            }
            // 不等待并且没获取到连接
            // 抛出异常并说明情况
            throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                    "NoWait: Pool empty. Unable to fetch a connection, none available["+busy.size()+" in use].");
        }
        if (con == null) {
            if ((System.currentTimeMillis() - now) >= maxWait) {
                if (jmxPool!=null) {
                    jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - timeout.");
                }
                // 等到达到了超时时间也没有获取到连接
                // 则抛出异常
                throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                    "Timeout: Pool empty. Unable to fetch a connection in " + (maxWait / 1000) +
                    " seconds, none available[size:"+size.get() +"; busy:"+busy.size()+"; idle:"+idle.size()+"; lastwait:"+timetowait+"].");
            } else {
                continue;
            }
        }
    }
}

获取连接的流程是很清晰的,可以归纳如下。

  1. 首先从空闲连接队列idle中获取一个连接。这里是通过poll() 方法获取,所以得到的连接可能为null
  2. 校验获取到的连接。如果要触发校验连接的动作,需要将testOnBorrow配置为true,如果校验成功则返回连接,如果校验失败,则会基于当前连接重连一次数据库;
  3. 前面如果都没有获取到连接则尝试新建一个连接。新建连接有一个前提,就是当前连接池的总连接数要小于允许的最大连接数;
  4. 如果无法新建连接则进入超时等待获取连接状态。如果从空闲连接队列idle中无法获取到一个有效连接且也不允许继续创建连接,则需要在指定时间范围内从idle中等待获取连接。

总结

TomcatJdbc获取连接的整个流程图示意如下。

数据库连接池-TomcatJdbc获取连接流程图


链接:https://juejin.cn/post/7309158089374335027
 

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值