搞懂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获取连接的整个流程图示意如下。

相关推荐
弗拉唐几秒前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi7732 分钟前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
2401_857610031 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀1 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20201 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
凌冰_1 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞1 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举