在使用 JDBC 时,我们都是通过 DriverManager 的接口方法获取数据库连接。本节所要分析的源码也不例外,一起看一下吧。


// -☆- UnpooledDataSource

public Connection getConnection() throws SQLException {

    return doGetConnection(username, password);

}

private Connection doGetConnection(String username, String password) throws SQLException {

    Properties props = new Properties();

    if (driverProperties != null) {

        props.putAll(driverProperties);

    }

    if (username != null) {

        // 存储 user 配置

        props.setProperty("user", username);

    }

    if (password != null) {

        // 存储 password 配置

        props.setProperty("password", password);

    }

    // 调用重载方法

    return doGetConnection(props);

}

private Connection doGetConnection(Properties properties) throws SQLException {

    // 初始化驱动

    initializeDriver();

    // 获取连接

    Connection connection = DriverManager.getConnection(url, properties);

    // 配置连接,包括自动提交以及事务等级

    configureConnection(connection);

    return connection;

}

private void configureConnection(Connection conn) throws SQLException {

    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {

        // 设置自动提交

        conn.setAutoCommit(autoCommit);

    }

    if (defaultTransactionIsolationLevel != null) {

        // 设置事务隔离级别

        conn.setTransactionIsolation(defaultTransactionIsolationLevel);

    }

} 

如上,上面方法将一些配置信息放入到 Properties 对象中,然后将数据库连接和 Properties 对象传给 DriverManager 的 getConnection 方法即可获取到数据库连接。

好了,关于 UnpooledDataSource 就先说到这。下面分析一下 PooledDataSource,它的实现要复杂一些。

[]( )4.PooledDataSource


PooledDataSource 内部实现了连接池功能,用于复用数据库连接。因此,从效率上来说,PooledDataSource 要高于 UnpooledDataSource。PooledDataSource 需要借助一些辅助类帮助它完成连接池的功能,所以接下来,我们先来认识一下相关的辅助类。

[]( )4.1 辅助类介绍

PooledDataSource 需要借助两个辅助类帮其完成功能,这两个辅助类分别是 PoolState 和 PooledConnection。PoolState 用于记录连接池运行时的状态,比如连接获取次数,无效连接数量等。同时 PoolState 内部定义了两个 PooledConnection 集合,用于存储空闲连接和活跃连接。PooledConnection 内部定义了一个 Connection 类型的变量,用于指向真实的数据库连接。以及一个 Connection 的代理类,用于对部分方法调用进行拦截。至于为什么要拦截,随后将进行分析。除此之外,PooledConnection 内部也定义了一些字段,用于记录数据库连接的一些运行时状态。接下来,我们来看一下 PooledConnection 的定义。


class PooledConnection implements InvocationHandler {

    private static final String CLOSE = "close";

    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    private final int hashCode;

    private final PooledDataSource dataSource;

    // 真实的数据库连接

    private final Connection realConnection;

    // 数据库连接代理

    private final Connection proxyConnection;

    // 从连接池中取出连接时的时间戳

    private long checkoutTimestamp;

    // 数据库连接创建时间

    private long createdTimestamp;

    // 数据库连接最后使用时间

    private long lastUsedTimestamp;

    // connectionTypeCode = (url + username + password).hashCode()

    private int connectionTypeCode;

    // 表示连接是否有效

    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {

        this.hashCode = connection.hashCode();

        this.realConnection = connection;

        this.dataSource = dataSource;

        this.createdTimestamp = System.currentTimeMillis();

        this.lastUsedTimestamp = System.currentTimeMillis();

        this.valid = true;

        // 创建 Connection 的代理类对象

        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);

    }

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}

    java

    // 省略部分代码

} 

下面再来看看 PoolState 的定义。


public class PoolState {

    protected PooledDataSource dataSource;

    // 空闲连接列表

    protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();

    // 活跃连接列表

    protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

    // 从连接池中获取连接的次数

    protected long requestCount = 0;

    // 请求连接总耗时(单位:毫秒)

    protected long accumulatedRequestTime = 0;

    // 连接执行时间总耗时

    protected long accumulatedCheckoutTime = 0;

    // 执行时间超时的连接数

    protected long claimedOverdueConnectionCount = 0;

    // 超时时间累加值

    protected long accumulatedCheckoutTimeOfOverdueConnections = 0;

    // 等待时间累加值

    protected long accumulatedWaitTime = 0;

    // 等待次数

    protected long hadToWaitCount = 0;

    // 无效连接数

    protected long badConnectionCount = 0;

} 

上面对 PooledConnection 和 PoolState 的定义进行了一些注释,这两个类中有很多字段用来记录运行时状态。但在这些字段并非核心,因此大家知道每个字段的用途就行了。关于这两个辅助类的介绍就先到这

[]( )4.2 获取连接

前面已经说过,PooledDataSource 会将用过的连接进行回收,以便可以复用连接。因此从 PooledDataSource 获取连接时,如果空闲链接列表里有连接时,可直接取用。那如果没有空闲连接怎么办呢?此时有两种解决办法,要么创建新连接,要么等待其他连接完成任务。具体怎么做,需视情况而定。下面我们深入到源码中一探究竟。


public Connection getConnection() throws SQLException {

    // 返回 Connection 的代理对象

    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();

}

private PooledConnection popConnection(String username, String password) throws SQLException {

    boolean countedWait = false;

    PooledConnection conn = null;

    long t = System.currentTimeMillis();

    int localBadConnectionCount = 0;

    while (conn == null) {

        synchronized (state) {

            // 检测空闲连接集合(idleConnections)是否为空

            if (!state.idleConnections.isEmpty()) {

                // idleConnections 不为空,表示有空闲连接可以使用

                conn = state.idleConnections.remove(0);

            } else {

                /*

                 * 暂无空闲连接可用,但如果活跃连接数还未超出限制

                 *(poolMaximumActiveConnections),则可创建新的连接

                 */

                if (state.activeConnections.size() < poolMaximumActiveConnections) {

                    // 创建新连接

                    conn = new PooledConnection(dataSource.getConnection(), this);

                } else {    // 连接池已满,不能创建新连接

                    // 取出运行时间最长的连接

                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);

                    // 获取运行时长

                    long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();

                    // 检测运行时长是否超出限制,即超时

                    if (longestCheckoutTime > poolMaximumCheckoutTime) {

                        // 累加超时相关的统计字段

                        state.claimedOverdueConnectionCount++;

                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;

                        state.accumulatedCheckoutTime += longestCheckoutTime;

                        // 从活跃连接集合中移除超时连接

                        state.activeConnections.remove(oldestActiveConnection);

                        // 若连接未设置自动提交,此处进行回滚操作

                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {

                            try {

                                oldestActiveConnection.getRealConnection().rollback();

                            } catch (SQLException e) {...}

                        }

                        /*

                         * 创建一个新的 PooledConnection,注意,

                         * 此处复用 oldestActiveConnection 的 realConnection 变量

                         */

                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);

                        /*

                         * 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的 

                         * createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection 

                         * 的创建时间。所以这里要复用原连接的时间信息。

                         */

                        conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());

                        conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

                        // 设置连接为无效状态

                        oldestActiveConnection.invalidate();

                    } else {    // 运行时间最长的连接并未超时

                        try {

                            if (!countedWait) {

                                state.hadToWaitCount++;

                                countedWait = true;

                            }

                            long wt = System.currentTimeMillis();

                            // 当前线程进入等待状态

                            state.wait(poolTimeToWait);

                            state.accumulatedWaitTime += System.currentTimeMillis() - wt;

                        } catch (InterruptedException e) {

                            break;

                        }

                    }

                }

            }

            if (conn != null) {

                /*

                 * 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,

                 * 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,

                 * 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。

                 * 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的

                 * 属性,有三个:poolPingQuery,poolPingEnabled 和 

                 * poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档

                 */

                if (conn.isValid()) {

                    if (!conn.getRealConnection().getAutoCommit()) {

                        // 进行回滚操作

                        conn.getRealConnection().rollback();

                    }

                    conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));

                    // 设置统计字段

                    conn.setCheckoutTimestamp(System.currentTimeMillis());

                    conn.setLastUsedTimestamp(System.currentTimeMillis());

                    state.activeConnections.add(conn);

                    state.requestCount++;

                    state.accumulatedRequestTime += System.currentTimeMillis() - t;

                } else {

                    // 连接无效,此时累加无效连接相关的统计字段

                    state.badConnectionCount++;

                    localBadConnectionCount++;

                    conn = null;

                    if (localBadConnectionCount > (poolMaximumIdleConnections

                        + poolMaximumLocalBadConnectionTolerance)) {

                        throw new SQLException(...);

                    }

                }

            }

        }

    }

    if (conn == null) {

        throw new SQLException(...);

    }

    return conn;

} 

上面代码冗长,过程比较复杂,下面把代码逻辑梳理一下。从连接池中获取连接首先会遇到两种情况:

  1. 连接池中有空闲连接

  2. 连接池中无空闲连接

对于第一种情况,处理措施就很简单了,把连接取出返回即可。对于第二种情况,则要进行细分,会有如下的情况。

  1. 活跃连接数没有超出最大活跃连接数

  2. 活跃连接数超出最大活跃连接数

对于上面两种情况,第一种情况比较好处理,直接创建新的连接即可。至于第二种情况,需要再次进行细分。

  1. 活跃连接的运行时间超出限制,即超时了

  2. 活跃连接未超时

对于第一种情况,我们直接将超时连接强行中断,并进行回滚,然后复用部分字段重新创建 PooledConnection 即可。对于第二种情况,目前没有更好的处理方式了,只能等待了。下面用一段伪代码演示各种情况及相应的处理措施,如下:


if (连接池中有空闲连接) {

    1. 将连接从空闲连接集合中移除

} else {

    if (活跃连接数未超出限制) {

        1. 创建新连接

    } else {

        1. 从活跃连接集合中取出第一个元素

        2. 获取连接运行时长

        if (连接超时) {

            1. 将连接从活跃集合中移除

            2. 复用原连接的成员变量,并创建新的 PooledConnection 对象

        } else {

            1. 线程进入等待状态

            2. 线程被唤醒后,重新执行以上逻辑

        }

    }

}

1. 将连接添加到活跃连接集合中

2. 返回连接 

最后用一个流程图大致描绘 popConnection 的逻辑,如下:

img

[]( )4.3 回收连接

相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。下面看一下相关的逻辑。



protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {

        // 从活跃连接池中移除连接

        state.activeConnections.remove(conn);

        if (conn.isValid()) {

            // 空闲连接集合未满

            if (state.idleConnections.size() < poolMaximumIdleConnections

                && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {

                state.accumulatedCheckoutTime += conn.getCheckoutTime();

                // 回滚未提交的事务

                if (!conn.getRealConnection().getAutoCommit()) {

                    conn.getRealConnection().rollback();

                }

                // 创建新的 PooledConnection

                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);

                state.idleConnections.add(newConn);

                // 复用时间信息

                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());

                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());

                // 将原连接置为无效状态

                conn.invalidate();

                // 通知等待的线程

                state.notifyAll();

            } else {    // 空闲连接集合已满

                state.accumulatedCheckoutTime += conn.getCheckoutTime();

                // 回滚未提交的事务

                if (!conn.getRealConnection().getAutoCommit()) {

                    conn.getRealConnection().rollback();

                }

                // 关闭数据库连接

### 最后

**经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。**
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。如有需要获取资料文档的朋友,[可以点击这里免费获取](https://gitee.com/vip204888/java-p7)
![](https://s2.51cto.com/images/20210905/1630817798289489.jpg)
![](https://s2.51cto.com/images/20210905/1630817799976106.jpg)
![](https://s2.51cto.com/images/20210905/1630817799665650.jpg)
![](https://s2.51cto.com/images/20210905/1630817800101134.jpg)
![](https://s2.51cto.com/images/20210905/1630817801803253.jpg)
![](https://s2.51cto.com/images/20210905/1630817801364446.jpg)
![](https://s2.51cto.com/images/20210905/1630817802549336.jpg)

**由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!**