搞懂TomcatJdbc之连接获取

前言

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

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

TomcatJdbc往期文章:

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

正文

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

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

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

java 复制代码
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获取连接的整个流程图示意如下。

相关推荐
qq_4419960520 分钟前
Mybatis官方生成器使用示例
java·mybatis
巨大八爪鱼27 分钟前
XP系统下用mod_jk 1.2.40整合apache2.2.16和tomcat 6.0.29,让apache可以同时访问php和jsp页面
java·tomcat·apache·mod_jk
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring