从源码看Mybatis1-池化数据源的关键实现

110 阅读3分钟

源码中经常会出现很多的设计模式来实现组件与组件之间的解藕,比如工厂模式(常见)用于对象的创建。 在现在信息爆炸的时代,耐心的咀嚼源码,体会其设计思想对我们的思维有很大的提升与帮助

Mybatis池化数据源的设计

使用代理模式的PooledDataSource来增强java.sql中的Connection类功能,

  1. 使其能够统计checkoutTime
  2. 使其能够在close的时候,能够与dataSource进行通信,调用dataSource.pushConnection()方法将自身进行关闭或者包装成新的连接。
  3. 再每次对Connection的方法调用时都先通过checkConnection()方法来检测自身的状态,来避免非法访问数据源。(PoolConnection会在DataSource调用forceCloseAll(),pushConnection())

通过单独的PoolState来进行数据源的管理

  1. 统计如requstConut、badConnectionCount,accumulatedWaitTime等数据
  2. 通过使用两个List来存储idleConnections和activeConnections。

池化数据源的两个关键操作

PooledDataSource类中popConnection和pushConnection是池化数据源的核心操作方法(对应着getConnection()和close,通过state(PoolState)对象来判断数据池当前的状态决定是否要增加数据源或减少数据源。

pushConnection()用于关闭一个连接,在一个连接自身调用了close的时候会由pooledConnection的invoke方法进行触发。

protected void  pushConnection(PooledConnection conn) throws SQLException {

    lock.lock();
    try {
      state.activeConnections.remove(conn);
      // 连接是否有效
      if (conn.isValid()) {
         // 池中空闲数量是否小于设定的最大空闲连接数量, 并且connectionType是否一致,这里的connectionTypeCode
          // 是由url + user + password来决定的 以此来判断数据源和连接是否指向同一个库
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          // 如果设置了不自动提交,就先回滚一下,中断该连接的操作,因为要关闭了。
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          // 创建一个新的连接,因为空闲连接池还没满,需要让池中的连接保持在poolMaximumIdelConnections
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          condition.signal();
        } else {
          // 如果池中空闲数据已满则直接关闭,其实这里代码写的不太好? 在一段if-else中出现了5行相同的代码
          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中记录一下
        state.badConnectionCount++;
      }
    } finally {
      lock.unlock();
    }
  }

popConnection用于创建一个连接,在被调用DataSource.getConnection()时触发,用于从池中拿取连接。

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) {
      lock.lock();
      try {
        // 如果池中有空闲连接,直接从头拿取一个即可
        if (!state.idleConnections.isEmpty()) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // 否则当前活跃连接数少于配置的最大活跃连接数,就添加创建新连接
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            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();
            // 如果该连接的检查时间已经过期了,先进行一波统计,再将最老的连接重新用pooledConnection包装一次
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              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");
                }
              }
              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 {
              // Must wait
              // 否则只能等待,进入下一轮循环
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                condition.await(poolTimeToWait, TimeUnit.MILLISECONDS);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                // set interrupt flag
                Thread.currentThread().interrupt();
                break;
              }
            }
          }
        }
        // 此时已经走出循环,拿到了一个pooledConnection
        if (conn != null) {
          // ping to server and check the connection is valid or not
          // 检查一下连接能否使用
          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 {
            // 如果连接不能使用,就统计badConnection
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            // 如果拿到的坏连接太多了,就抛出SQL异常,无法拿到一个可以使用的数据库
            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.");
            }
          }
        }
      } finally {
        lock.unlock();
      }

    }

    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;
  }