啃下MyBatis源码系列目录

啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构

啃下MyBatis源码 - org.apache.ibatis.logging包源码分析

啃下MyBatis源码 - org.apache.ibatis.datasource包源码分析

啃下MyBatis源码 - org.apache.ibatis.cache包源码分析

啃下MyBatis源码 - MyBatis核心流程三大阶段之初始化阶段

啃下MyBatis源码 - MyBatis核心流程三大阶段之代理阶段(binding模块分析)

啃下MyBatis源码 - MyBatis核心流程三大阶段之数据读写阶段

啃下MyBatis源码 - MyBatis面试题总结

--------------------------------------------------------------------------------------------------------------------------

啃下MyBatis源码 - org.apache.ibatis.datasource包源码分析

1.MyBatis怎样集成第三方数据源组件

2.MyBatis数据源模块用到了哪些设计模式

3.MyBatis自身提供的连接池具体实现

--------------------------------------------------------------------------------------------------------------------------

1.MyBatis是怎样集成第三方数据源组件的

       了解这个问题之前,童鞋们应该先去了解一下工厂模式:23种设计模式之工厂模式(Factory Pattern),没错MyBatis就是通过工厂模式来解决集成第三方数据源组件问题的。先看一下datasource包结构:

org.apache.ibatis.datasource包源码分析
啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构_sql

       除去jndi文件夹下的jndi工厂(百度了一下是和tomcat有关的,去拿tomcat里面的数据源)和mybatis自身无关,我们就先忽略以外,主要的类共有七个,分别是

DataSourceFactory:数据工厂接口

UnpooledDataSource:不使用连接池的数据源

UnpooledDataSourceFactory:不使用连接池的数据源工厂

PooledDataSource:一个MyBatis自己实现的简单的,同步的,线程安全的数据库连接池

PooledDataSourceFactory:使用连接池的数据源工厂

PooledConnection:使用动态代理封装了真正的数据库连接对象

PoolState:管理PooledConnection对象状态的组件,内部分别存储了空闲状态和活跃状态的连接资源

关系类图为:

org.apache.ibatis.datasource包源码分析
啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构_连接池_02

       看到这里我们大致可以得到答案了,与日志模块不同(日志模块采用适配器模式),MyBatis数据源模块采用了工厂模式来解决和第三方数据源的集成,对接第三方数据源只需要自己写一个工厂类实现DataSourceFactory接口并重写接口内的两个方法:

public interface DataSourceFactory {
    //设置DataSource相关属性
    void setProperties(Properties props);
    //获取数据源
    DataSource getDataSource();
}

       还需要自己写一个DataSource实现javax.sql内的DataSource接口,搞定,只需要两步就可以实现和第三方数据源的集成,工厂模式就是这么方便。

2.MyBatis数据源模块用到了哪些设计模式

       根据问题1我们知道了MyBatis数据源模块用到了工厂模式来负责数据源的创建,除了引用了工厂模式外,由类图那个非常明显的InvocationHandler接口可知,数据源模块还引入了代理模式,那么要增强的究竟是什么呢?PooledConnection类部分源码如下:

class PooledConnection implements InvocationHandler {
  ...
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    //对Connection的close方法进行拦截,因为引入了连接池,用户手动调用Connection.close方法的时 
    //候,并不是真正的关闭连接,而是改为对连接池内的连接进行操作
    if (CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // 使用前要检查连接是否有效
        heckConnection();
      }
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
   ...
}

       增强的地方有两处,第一处是对Connection的close方法进行拦截,因为引入了连接池,当用户手动调用Connection.close方法的时候,并不是真正的关闭连接,而是改为对连接池内的连接进行操作;第二个是在使用连接前对连接是否有效进行检查。

3.MyBatis自身提供的连接池具体实现

       具体数据源实现主要就是PooledDataSource内的popConnection()(获取连接)和pushConnection()(回收连接)方法。

popConnection()流程图:

org.apache.ibatis.datasource包源码分析
啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构_工厂模式_03

popConnection()获取连接源码:

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) {//检测是否已经超过最长使用时间
              // 如果超时,则对超时连接的信息进行统计
              state.claimedOverdueConnectionCount++;//超时连接次数+1
              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 {
              // 无空闲连接,最早创建的连接还在使用,没有失效,则无法创建连接,只能阻塞
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;//连接池累计次数+1
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);//阻塞等待指定的时间
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;//累计等待时间
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // 如果获取连接成功,测试连接是否有效,同时因为是新连接,所以要更新连接信息
          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 {//如果连接无效,记录日志
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;//累计获取无效连接次数+1
            localBadConnectionCount++;//当前获取无效次数+1
            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;
  }

pushConnection()流程图:

org.apache.ibatis.datasource包源码分析
啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构_sql_04

pushConnection()回收源码:

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 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.");
          }
          //唤醒其他被阻塞的线程
          state.notifyAll();
        } else {//如果闲置连接池数量已达上限,则将连接真实关闭
          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.");
        }
        //无效连接次数+1
        state.badConnectionCount++;
      }
    }
  }

       我在获取连接和回收连接的源码内每个关键的步骤加上了注释,慢慢看还是可以看懂的。