From 869125a0e6c37567e00c0a68b1e0d09080617453 Mon Sep 17 00:00:00 2001 From: AmyliaY <471816751@qq.com> Date: Fri, 6 Dec 2019 21:07:30 +0800 Subject: [PATCH] =?UTF-8?q?mybatis=E5=86=85=E7=BD=AE=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E6=B1=A0PooledDataSource?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E5=AE=9E=E7=8E=B0=E4=BB=A3=E7=A0=81=E8=A7=A3?= =?UTF-8?q?=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2、DataSource及Transaction.md | 330 +++++++++++++++++- 1 file changed, 327 insertions(+), 3 deletions(-) diff --git a/docs/Mybatis/基础支持层/2、DataSource及Transaction.md b/docs/Mybatis/基础支持层/2、DataSource及Transaction.md index 66a4301..f600e73 100644 --- a/docs/Mybatis/基础支持层/2、DataSource及Transaction.md +++ b/docs/Mybatis/基础支持层/2、DataSource及Transaction.md @@ -338,9 +338,7 @@ public class PoolState { #### 1.3.3 PooledDataSource PooledDataSource管理的数据库连接对象 是由其持有的UnpooledDataSource对象创建的,并由PoolState管理所有连接的状态。 PooledDataSource的getConnection()方法会首先调用popConnection()方法获取PooledConnection对象,然后通过PooledConnection的getProxyConnection()方法获取数据库连接的代理对象。popConnection()方法是PooledDataSource的核心逻辑之一,其整体的逻辑关系如下图: - -![avatar](/images/mybatis连接池获取连接逻辑图.png) - +![在这里插入图片描述](https://img-blog.csdnimg.cn/20191205213903828.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) ```java public class PooledDataSource implements DataSource { @@ -373,9 +371,335 @@ public class PooledDataSource implements DataSource { // 根据数据库URL、用户名、密码 生成的一个hash值, // 该hash值用于标记当前的连接池,在构造函数中初始化 private int expectedConnectionTypeCode; + + /** + * 下面的两个getConnection()方法都会调用popConnection() + * 获取PooledConnection对象,然后调用该对象的getProxyConnection()方法 + * 获取数据库连接的代理对象 + */ + @Override + public Connection getConnection() throws SQLException { + return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return popConnection(username, password).getProxyConnection(); + } + + /** + * 本方法实现了连接池获取连接对象的具体逻辑,是PooledDataSource的核心逻辑之一 + */ + 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) { + // 如果连接池中有空闲的 数据库连接对象,就取出一个 + if (!state.idleConnections.isEmpty()) { + conn = state.idleConnections.remove(0); + if (log.isDebugEnabled()) { + log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); + } + } else { + // 没有空闲的连接对象,就判断一下 活跃的连接数是否已达 设定的峰值 + if (state.activeConnections.size() < poolMaximumActiveConnections) { + // 还没达到峰值 就创建一个新的连接 + conn = new PooledConnection(dataSource.getConnection(), this); + if (log.isDebugEnabled()) { + log.debug("Created connection " + conn.getRealHashCode() + "."); + } + } else { + // 如果活跃的连接已达上限,就取出最老的活跃连接对象,判断其是否超时 + PooledConnection oldestActiveConnection = state.activeConnections.get(0); + long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); + if (longestCheckoutTime > poolMaximumCheckoutTime) { + // 如果最老的连接超时了,就在PoolState中记录一下相关信息,然后将该连接对象释放掉 + state.claimedOverdueConnectionCount++; + state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; + state.accumulatedCheckoutTime += longestCheckoutTime; + state.activeConnections.remove(oldestActiveConnection); + // 如果最老的连接不是 自动提交事务的,就将事务回滚掉 + if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { + try { + oldestActiveConnection.getRealConnection().rollback(); + } catch (SQLException e) { + /* + Just log a message for debug and continue to execute the following + statement like nothing happened. + Wrap the bad connection with a new PooledConnection, this will help + to not interrupt current executing thread and give current thread a + chance to join the next competition for another valid/good database + connection. At the end of this loop, bad {@link @conn} will be set as null. + */ + log.debug("Bad connection. Could not roll back"); + } + } + // 从最老连接中取出真正的 数据库连接对象及相关信息,用来构建新的PooledConnection对象 + conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); + conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); + conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); + // 将最老活跃连接设为无效 + oldestActiveConnection.invalidate(); + if (log.isDebugEnabled()) { + log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); + } + } else { + // 如果最老的连接对象也没超时,则进入阻塞等待, + // 等待时间poolTimeToWait可自行设置 + try { + if (!countedWait) { + // 等待次数加一 + state.hadToWaitCount++; + countedWait = true; + } + if (log.isDebugEnabled()) { + log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); + } + long wt = System.currentTimeMillis(); + // native方法,使执行到这里的线程阻塞等待poolTimeToWait毫秒 + state.wait(poolTimeToWait); + // 统计累计等待的时间 + state.accumulatedWaitTime += System.currentTimeMillis() - wt; + } catch (InterruptedException e) { + break; + } + } + } + } + // 到了这里 基本上就获取到连接对象咯,但我们还要确认一下该连接对象是否是有效的 可用的 + if (conn != null) { + // ping一下数据库服务器,确认该连接对象是否有效 + if (conn.isValid()) { + // 如果事务提交配置为手动的,则先让该连接回滚一下事务,防止脏数据的出现 + if (!conn.getRealConnection().getAutoCommit()) { + conn.getRealConnection().rollback(); + } + // 设置 由数据库URL、用户名、密码 计算出来的hash值,可用于标识该连接所在的连接池 + conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); + // 设置 从连接池中取出该连接时的时间戳 + conn.setCheckoutTimestamp(System.currentTimeMillis()); + // 设置 最后一次使用的时间戳 + conn.setLastUsedTimestamp(System.currentTimeMillis()); + // 将该连接加入活跃的连接对象列表 + state.activeConnections.add(conn); + // 请求数据库连接的次数加一 + state.requestCount++; + // 计算 获取连接的累计时间(accumulate累计) + state.accumulatedRequestTime += System.currentTimeMillis() - t; + // 如果获取到的连接无效 + } else { + if (log.isDebugEnabled()) { + log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); + } + // 对无效连接进行统计 + state.badConnectionCount++; + localBadConnectionCount++; + conn = null; + // 如果无效连接超出 阈值,则抛出异常 + if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) { + if (log.isDebugEnabled()) { + log.debug("PooledDataSource: Could not get a good connection to the database."); + } + throw new SQLException("PooledDataSource: Could not get a good connection to the database."); + } + } + } + } + + } + + // 如果到了这里 连接还为空,则抛出一个未知的服务异常 + if (conn == null) { + if (log.isDebugEnabled()) { + log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); + } + throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); + } + + // 返回数据库连接对象 + return conn; + } + + /** + * 看一下之前讲过的PooledConnection中的动态代理方法invoke(),可以发现 + * 当调用数据库连接代理对象的close()方法时,并未关闭真正的数据库连接, + * 而是调用了本方法,将连接对象归还给连接池,方便后续使用,本方法也是PooledDataSource的核心逻辑之一 + */ + protected void pushConnection(PooledConnection conn) throws SQLException { + // 国际惯例,操作公共资源先上个锁 + synchronized (state) { + // 先将该连接从活跃的连接对象列表中剔除 + state.activeConnections.remove(conn); + // 如果该连接有效 + if (conn.isValid()) { + // 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于本连接池(通过之前获取的expectedConnectionTypeCode进行校验) + if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) { + // CheckoutTime = 应用从连接池取出连接到归还连接的时长 + // accumulatedCheckoutTime = 所有连接累计的CheckoutTime + state.accumulatedCheckoutTime += conn.getCheckoutTime(); + // 不是自动提交事务的连接 先回滚一波 + if (!conn.getRealConnection().getAutoCommit()) { + conn.getRealConnection().rollback(); + } + // 从conn中取出真正的 数据库连接对象,重新封装成PooledConnection + PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); + // 将newConn放进空闲连接对象列表 + state.idleConnections.add(newConn); + // 设置newConn的相关属性 + newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); + newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); + // 将原本的conn作废 + conn.invalidate(); + if (log.isDebugEnabled()) { + log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); + } + // 唤醒阻塞等待的线程 + state.notifyAll(); + } else { + // 如果空闲连接已达阈值 或 该连接对象不属于本连接池,则做好统计数据 + // 回滚连接的事务,关闭真正的连接,最后作废该conn + state.accumulatedCheckoutTime += conn.getCheckoutTime(); + if (!conn.getRealConnection().getAutoCommit()) { + conn.getRealConnection().rollback(); + } + conn.getRealConnection().close(); + if (log.isDebugEnabled()) { + log.debug("Closed connection " + conn.getRealHashCode() + "."); + } + conn.invalidate(); + } + // 如果该连接是无效的,则记录一下无效的连接数 + } else { + if (log.isDebugEnabled()) { + log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); + } + state.badConnectionCount++; + } + } + } + + /** + * 关闭连接池中 所有活跃的 及 空闲的连接 + * 当修改连接池的配置(如:用户名、密码、URL等),都会调用本方法 + */ + public void forceCloseAll() { + // 日常上锁 + synchronized (state) { + // 更新当前连接池的标识 + expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword()); + // 依次关闭活跃的连接对象 + for (int i = state.activeConnections.size(); i > 0; i--) { + try { + PooledConnection conn = state.activeConnections.remove(i - 1); + conn.invalidate(); + + Connection realConn = conn.getRealConnection(); + if (!realConn.getAutoCommit()) { + realConn.rollback(); + } + realConn.close(); + } catch (Exception e) { + // ignore + } + } + // 依次关闭空闲的连接对象 + for (int i = state.idleConnections.size(); i > 0; i--) { + try { + PooledConnection conn = state.idleConnections.remove(i - 1); + conn.invalidate(); + + Connection realConn = conn.getRealConnection(); + if (!realConn.getAutoCommit()) { + realConn.rollback(); + } + realConn.close(); + } catch (Exception e) { + // ignore + } + } + } + if (log.isDebugEnabled()) { + log.debug("PooledDataSource forcefully closed/removed all connections."); + } + } } ``` +最后,我们来看一下popConnection()和pushConnection()都调用了的isValid()方法,该方法除了检测PooledConnection中的valid字段外 还还会调用PooledDataSource中的pingConnection()方法,让数据库连接对象 执行指定的sql语句,检测连接是否正常。 +```java +class PooledConnection implements InvocationHandler { + /** + * 检测PooledConnection对象的有效性 + */ + public boolean isValid() { + return valid && realConnection != null && dataSource.pingConnection(this); + } +} + + +public class PooledDataSource implements DataSource { + /** + * ping一下数据库,检测数据库连接是否正常 + */ + protected boolean pingConnection(PooledConnection conn) { + boolean result = true; + + try { + result = !conn.getRealConnection().isClosed(); + } catch (SQLException e) { + if (log.isDebugEnabled()) { + log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage()); + } + result = false; + } + if (result) { + // 是否允许发送检测语句,检测数据库连接是否正常,poolPingEnabled可自行配置 + // 该检测会牺牲一定的系统资源,以提高安全性 + if (poolPingEnabled) { + // 超过poolPingConnectionsNotUsedFor毫秒未使用的连接 才会检测其连接状态 + if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) { + try { + if (log.isDebugEnabled()) { + log.debug("Testing connection " + conn.getRealHashCode() + " ..."); + } + // 获取真正的连接对象,执行 poolPingQuery = "NO PING QUERY SET" sql语句 + Connection realConn = conn.getRealConnection(); + try (Statement statement = realConn.createStatement()) { + statement.executeQuery(poolPingQuery).close(); + } + if (!realConn.getAutoCommit()) { + realConn.rollback(); + } + result = true; + if (log.isDebugEnabled()) { + log.debug("Connection " + conn.getRealHashCode() + " is GOOD!"); + } + // 如果上面这段代码抛出异常,则说明数据库连接有问题,将该连接关闭,返回false + } catch (Exception e) { + log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage()); + try { + conn.getRealConnection().close(); + } catch (Exception e2) { + //ignore + } + result = false; + if (log.isDebugEnabled()) { + log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage()); + } + } + } + } + } + return result; + } +} +``` ## 2 Transaction