MyBatis 源码分析 - 内置数据源 (3)

下面再来看看 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; }

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

连接池中有空闲连接

连接池中无空闲连接

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

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

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

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

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

活跃连接未超时

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywxws.html