mybatis 源码系列 组件之 datasource

Question: 

. mybatis 各种 datasource Factory类型是如何实现的?

PooledDataSource 是如何实现的? 为什么需要 idleConnections 和 activeConnections 两个 容器 来管理?

.UnpooledDataSource 和 PooledDataSource 之间的关系是什么?

PooledDataSource 包含哪些属性, 是如何 影响 连接池的行为的?


Mybatis 针对不同情况对不同的datasource做了封装,利用 JndiDataSourceFactory, UnpooledDataSourceFactory,PooledDataSourceFactory 3个datasource 分别 生成 不同的datasource, JndiDataSourceFactory 是依赖jndi服务从容器中获取用户配置的datasource, UnpooledDataSourceFactory 则是生成 不使用连接池的数据源,用户每次其每次通过datasouce获取connection,都会直接调用jdbc获取connection,而 PooledDataSourceFactory 则依赖自己的实现,实现了 连接池的管理功能


至于 JndiDataSourceFactory生成数据源的实现,可以自己查看,很容易理解,下面先讲下 UnpooledDataSourceFactory 是 如何实现:

查看DataSourceFactory 接口,有两个抽象方法,分别是 setPropertiesgetDataSource, setProperties可以对datasource的属性进行额外的设置,而getDataSource则是提供给用户获取数据源的方法, 下面是 UnpooledDataSourceFactory 的 setProperties实现:

public void setProperties(Propertiesproperties) {
    Properties driverProperties = new Properties();
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);// 此处的datasource 引用为UnpooledDataSourceFactory构造函数实例化UnpooledDataSource 类,同时 SystemMetaObject 工具类不熟悉的可以查看源码或者上一篇的 《mybatis 源码系列 组件之 reflection》
    for (Object key :properties.keySet()) {
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH),value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource,propertyName, value);
        metaDataSource.setValue(propertyName,convertedValue); // 设置 datasource的属性值
      } else {
        throw new DataSourceException("Unknown DataSource property: " +propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties",driverProperties);
    }
  }



顺着这条路走看看 UnpooledDataSource 是如何实现,着重看其 getConnection 方法:

  public Connection getConnection() throws SQLException {
    return doGetConnection(username,password);
  }

  public Connection getConnection(String username, String password) 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) {
      props.setProperty("user",username);
    }
    if (password !=null) {
      props.setProperty("password",password);
    }
    return doGetConnection(props);
  }

  private Connection doGetConnection(Propertiesproperties) throws SQLException {
    initializeDriver(); // 检查是否已经注册当前用户配置的 driver类型,如果没有,则为用户注册当前配置的driver类型
    Connection connection = DriverManager.getConnection(url,properties); // 是不是很熟悉,直接通过原始jdbc获取connection
    configureConnection(connection);// 根据用户配置数据源的属性,对 connection进行配置
    returnconnection;// 直接返回实例化的 connection,未做任何缓存,没有连接池的概念
  }
  private void configureConnection(Connectionconn) throws SQLException {
    if (autoCommit !=null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit); // 设置 是否自动提交
    }
    if (defaultTransactionIsolationLevel !=null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);//设置事务隔离级别
    }
  }


这就是 UnpooledDataSource 的主要实现了,是不是很容易!!


下面进入正题,看 PooledDataSource 是如何实现:


首先需要了解下 PoolState 这个类,其主要功能有:

. 利用 idleConnections 和 activeConnections 管理 连接池下 空闲和活跃的 数据库连接

. 一系列 监控 连接池状态的属性,包括:

. requestCount 用户请求 数据库连接的次数

. accumulatedRequestTime 对用户所有的数据库连接池的获取请求 的一个 时间总计

. accumulatedCheckoutTime, checkTime 为 这个 数据库连接从DriverManager获取后 的时间,而这个累计值值表示所有数据库连接池的 checkout 和

. claimedOverdueConnectionCount, 在mybatis针对每个 poolConnection 都有个 最大 checkedout(当前时间 减去 checkout 的时间) 时间,如果 某一个 connection的 checkedout 大于 poolMaximumCheckoutTime(默认为20000),则会累加当前值,并将超时的poolconnection从 activeConnections中去除

. hadToWaitCount 记录了用户获取连接池暂时无法获取的情况下,需要等待的数目

. hadToWaitCount 则记录了用户获取连接池暂时无法获取的情况下,需要等待的的时间累计值

. badConnectionCount 记录了 无效 connection的数目,可能是手动设置无效,或者 真实的 connection已经被设置为了 nulll, 或者 是 已经与数据库失去了连接


接着我们看看 PooledDataSource, 首先看其属性列表和构造函数,如下:

  private final PoolState state = new PoolState(this);

  private final UnpooledDataSource dataSource; // 看到了吗? 在这 引用了 一个 unpooledDataSource的实例,利用组合关系,在从数据库中获取真实连接的时候就是利用这层关系,用unpooledDataSource代理生成真实数据库连接

  // OPTIONAL CONFIGURATION FIELDS
  protected int poolMaximumActiveConnections = 10;
  protected int poolMaximumIdleConnections = 5;
  protected int poolMaximumCheckoutTime = 20000;
  protected int poolTimeToWait = 20000;
  protected String poolPingQuery = "NO PING QUERY SET";
  protected boolean poolPingEnabled = false;
  protected int poolPingConnectionsNotUsedFor = 0;

  private int expectedConnectionTypeCode; // 根据 <span style="font-family: Arial, Helvetica, sans-serif;">数据库配置 </span><span style="font-family: Arial, Helvetica, sans-serif;">url, username, password 生成的一个hashcode, 用于标志着当前的数据库池,如果url或者 username,或者password有变化,需要关闭现有连接</span>

  public PooledDataSource() {
    dataSource = new UnpooledDataSource();
  }

  public PooledDataSource(String driver, String url, String username, String password) {
    dataSource = new UnpooledDataSource(driver, url, username, password);
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  }

  public PooledDataSource(String driver, String url, Properties driverProperties) {
    dataSource = new UnpooledDataSource(driver, url, driverProperties);
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  }

  public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
    dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  }

  public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
    dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  }

下面进入正轨,看其是如何获取数据库连接的:


在这之前,需要了解下 PooledConnection 这个类,首先看其属性列表和构造函数:

  private int hashCode = 0;
  private PooledDataSource dataSource;
  private Connection realConnection; // 真实的数据库连接
  private Connection proxyConnection; // 代理的 数据库连接
  private long checkoutTimestamp;
  private long createdTimestamp;
  private long lastUsedTimestamp;
  private int connectionTypeCode;
  private boolean valid;

  /*
   * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in
   *
   * @param connection - the connection that is to be presented as a pooled connection
   * @param dataSource - the dataSource that the connection is from
   */
  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;
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); // 在这 直接生成 真实数据库连接的代理
  }


下面就看看这个代理做了什么:

  /*
   * Required for InvocationHandler implementation.
   *
   * @param proxy  - not used
   * @param method - the method to be executed
   * @param args   - the parameters to be passed to the method
   * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 如果方法名称为 close, 则在用户关闭当前代理数据库连接时,其并不会真正的关闭数据库连接,而是 调用 pooledDataSource的 pushConnection方法,尽可能的 重用
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        if (!Object.class.equals(method.getDeclaringClass())) { //如果此方法不是从Object里继承过来的,也就是数据库连接的方法,则在调用之前需要检查其是否还有效
          // issue #579 toString() should never fail
          // throw an SQLException instead of a Runtime
          checkConnection();
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

接着,看 PooledDataSource的 getConnection方法:

  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); // 返回给用户的其实是代理类,并不是真实的数据库连接
  }

  public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();<span style="font-family: Arial, Helvetica, sans-serif;"> // 返回给用户的其实是代理类,并不是真实的数据库连接</span>
  }

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false; // 标志 <span style="font-family: Arial, Helvetica, sans-serif;">本次获取connection 是否进入等待</span>
    PooledConnection conn = null;
    long t = System.currentTimeMillis(); // 记录 获取connection的开始时间
    int localBadConnectionCount = 0; // 记录本次因获取connection,但获取无效的connection次数

    while (conn == null) { // 轮询,直到 不为空
      synchronized (state) {
        if (state.idleConnections.size() > 0) { // 首先检查 是否有 闲置的 连接,如果有,则直接返回闲置连接下的第一个
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // Pool does not have available connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) {// 如果没有闲置连接,则检查 活跃连接数目是否已经达到用户配置的上限,如果没有,则直接生成
            // Can create new connection
            conn = new PooledConnection(dataSource.getConnection(), this); //直接利用 datasource  (注意,其是 unpooledDataSource引用) 代理生成 真实数据库的连接
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {// 如果已经达到用户配置活跃连接数目的上限,则 检查 活跃连接的第一个连接的checked out time是否已经 超时,如果已经超时,则将其从活跃连接容器中删除,同时生成pooledConnection
            // Cannot create new connection
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {// 如果活跃连接中的第一个连接没有超时,则必须等待 轮询 获取 
              // Must wait
              try {
                if (!countedWait) {// 利用这个标志,可以 使 <span style="font-family: Arial, Helvetica, sans-serif;">state.hadToWaitCount 只会累加一次</span>
                  state.hadToWaitCount++;
                  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); // 将当前获取的connection加入到 获取连接容器中
            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++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
              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;
  }


有数据库连接的获取,就有数据库连接的返还,在用户调用 proxyConnection 的close方法后,会调用 pooledDataSource的pushConnection方法,代码如下:

  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      state.activeConnections.remove(conn); //首先将当前connection从活跃连接中删除
      if (conn.isValid()) { //检查是否有效
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {//检查 限制连接的数目是否已经达到上限,同时查看当前数据库连接是否是当前连接池生成的,如果满足,则将其放入 闲置连接容器中, 同时,通知 popConnection中等待线程
          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 {// 如果无效,则记录 badConnectionCount
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    }
  }













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值