Configuration--environments--dataSource(三-5-1)

上一篇文章中我们讲了environments,在environments中主要的配置就是dataSourcetransactionManager.本篇文章我们就来讲讲dataSource.

不知道大家还记不记得解析environments标签中的解析dataSource的代码:

  // 通过xml文件配置返回一个数据源工厂
  DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
  // 通过工厂获取一个数据源DataSource
  DataSource dataSource = dsFactory.getDataSource();
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

从上述代码我们可以看出,我们在xml中配置的type实际上需要是一个DataSourceFactory(实际上DataSourceFactory的作用只是用来生成一个DataSource(数据源)),而我们可以自定义DataSource,不过自定义DataSource不是本篇文章的重点.

本篇文章,我们首先来看一下mybatis内置的三种DataSource(数据源):
1.UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接.
2.POOLED:这种数据源的实现利用的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间.
3.JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用.

Configuration类的构造方法中,已经帮我们定义了这三个类的别名,所以我们如果使用这三个DataSourceFactory中的一个,只需要使用它们的别名即可:

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

在看mybatis内置的三种数据源之前,我们先来看看DataSourceFactory接口:

public interface DataSourceFactory {

  // 为数据源设置参数
  void setProperties(Properties props);

  // 返回一个数据源
  DataSource getDataSource();

}

从这里可以看出DataSourceFactory的作用就是获取DataSource.

DataSource主要的作用就是获取Connection:

public interface DataSource extends CommonDataSource, Wrapper {
  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;
}

1.UNPOOLED

我们先来看一下UnpooledDataSourceFactory的构造方法:

  public UnpooledDataSourceFactory() {
    // 从这里可以看出,如果dataSource标签的type使用的是UNPOOLED,那么最终将使用UnpooledDataSource数据源
    this.dataSource = new UnpooledDataSource();
  }

再来我们看一下UnpooledDataSourceFactory中的其他方法:

  // 这个方法就是返回UnpooledDataSourceFactory中的数据源
  public DataSource getDataSource() {
      return dataSource;
  }

  // 不知道大家还记不记得,这个方法是在解析dataSource标签的dataSourceElement方法中调用的,当时通过resolveClass实例化了一个DataSourceFactory,之后就调用了factory.setProperties(props);
  // 这个方法虽然看起来复杂,但是主要作用就是将配置的一些参数值,设置到dataSource中,这里不对该方法展开来分析
  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    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);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

从上述代码我们可以看出,其实最重要的还是DataSource,所以我们来看一下UnpooledDataSourcegetConnection()方法:

  public Connection getConnection() throws SQLException {
    // 使用在DataSourceFactory的setProperties方法中设置的username和password去打开Connection
    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);
    }
    // 将各个参数放入Properties,调用doGetConnection
    return doGetConnection(props);
  }

  private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化数据库驱动
    initializeDriver();
    // 这里的代码相信写过JDBC的同学会特别熟悉,这完全就是JDBC中通过DriverManager获取Connection的代码
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置Connection的一些参数
    configureConnection(connection);
    return connection;
  }

  private void configureConnection(Connection conn) throws SQLException {
    // 设置Connection是否自动提交
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    // 设置Connection的隔离级别
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

  private synchronized void initializeDriver() throws SQLException {
    // registeredDrivers是存储已经注册的数据库驱动,若还未注册驱动,则注册数据库驱动
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        Driver driverInstance = (Driver)driverType.newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        // 注册完驱动后,加入registeredDrivers中
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

从上述代码我们可以看出,若是使用UNPOOLED数据源,其实本质上就是每次获取Connection就会去获取一个新的连接.


2.POOLED

我们接着来看POOLED,首先我们看PooledDataSourceFactory:

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }

}

从这里我们可以看出PooledDataSourceFactorysetPropertiesgetDataSourceUnpooledDataSourceFactory一样,不一样的是PooledDataSourceFactorygetDataSource方法返回的是PooledDataSource.

所以我们直接来看PooledDataSource的代码,首先看PooledDataSource的构造方法:

  private final UnpooledDataSource dataSource;

  // 我们可以看到,在PooledDataSource中持有了一个UnpooledDataSource
  public PooledDataSource() {
    dataSource = new UnpooledDataSource();
  }

我们继续来看PooledDataSourcegetConnection()方法:

  public Connection getConnection() throws SQLException {
    // popConnection方法返回一个PooledConnection,通过PooledConnection的getProxyConnection方法获取一个Connection
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

继续看popConnection方法:

  private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    // 当conn为null时,继续循环去获取conn
    while (conn == null) {

      // state的定义:private final PoolState state = new PoolState(this),PoolState中维护着PooledDataSource中空闲的Connection的List和忙碌的Connection的List.
      // 获取Connection的过程中要加锁
      synchronized (state) {

        // idleConnections实际上是一个ArrayList,定义为:protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
        // idleConnections这个属性就是表示当前这个DataSource连接池中的空闲的Connection的List,如果有,则直接返回第一个空闲Connection
        if (state.idleConnections.size() > 0) {
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else { // 若连接池中没有空闲的Connection,则运行到这里

          // activeConnections也是一个ArrayList,定义为:protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
          // activeConnections这个属性就是表示当前这个DataSource连接池中的忙碌的Connection的List
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // 若忙碌的Connection的数量小于poolMaximumActiveConnections(最大连接数,默认是10),则创建一个新的Connection
            // 我们可以看到,虽然这里返回的是PooledConnection,但是实际使用的是dataSource.getConnection(),而dataSource就是在构造方法中实例化的UnpooledDataSource.
            // 这里使用PooledConnection的原因其实是为了给真正的Connection做一个代理,让其close的时候,并不是真正关闭Connection,而是将Connection返还给连接池,PooledConnection的代码我们接下来会分析
            conn = new PooledConnection(dataSource.getConnection(), this);
            @SuppressWarnings("unused")
            Connection realConn = conn.getRealConnection();
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {

            // 若Connection数量已经达到最大,则获取第一个工作中的Connection(也就是工作最久的一个Connection)
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            // 这里取出的时间是该Connection已经在工作状态工作的时间
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();

            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // 若已经工作的时间大于最大的工作时间(默认20000毫秒,也就是20秒),则记录一些值
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              // 然后将该Connection从工作List中移除
              state.activeConnections.remove(oldestActiveConnection);
              // 如果该Connection事务不是自动提交,则回滚之前所有操作(从这里我们可以看出,如果一个Connection工作时间超过了最大的工作时间,该Connection可能会被别的操作给抢夺走)
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              // 然后将其封装成一个新的PooledConnection
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              // 将valid设置为false,该值可以理解为Connection是否可用
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // 如果没有超过最大工作时间的Connection,则需要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();
                // 等待最大时间为poolTimeToWait,(poolTimeToWait默认为20000毫秒,也就是20秒)
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                // 如果线程被中断了,则直接退出while循环
                break;
              }
            }
          }
        }
        if (conn != null) {

          // 如果获得的Connection是可用的,并且事务不是自动提交,则回滚之前的所有操作
          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());
            // 将该Connection加入到工作List中
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {

            // 若获取的Connection不可用,则记录一些状态,然后继续获取,因为while(conn != null)
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            // 当获取的不可用Connection数量大于最大可以空闲的Connection数poolMaximumIdleConnections+3时,(poolMaximumIdleConnections默认为5),则抛出异常
            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;
  }

上述代码的流程图:

  popConnection流程图

之后就是调用PooledConnectiongetProxyConnection()方法返回一个Connection,我们先来看一下PooledConnection的构造方法:

  // 该Connection最初是通过dataSource.getConnection()获取的,也就是UnpooledDataSource
  public PooledConnection(Connection connection, PooledDataSource dataSource) {
    this.hashCode = connection.hashCode();
    // 真正的Connection
    this.realConnection = connection;
    this.dataSource = dataSource;
    this.createdTimestamp = System.currentTimeMillis();
    this.lastUsedTimestamp = System.currentTimeMillis();
    this.valid = true;
    // 该属性就是被代理的Connection
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }

我们继续来看PooledConnectioninvoke方法,看看生成的代理Connection会做什么:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 调用close方法时,并不会去执行Connection的close方法去关闭连接,而是执行pushConnection将该连接返还给数据库连接池,这里就是数据库连接池中的连接得以复用的原因.
    // 这里的dataSource是构造方法中传入的PooledDataSource
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        // 如果调用其他方法,都会去检测一下,当前Connection是否还可用
        if (!Object.class.equals(method.getDeclaringClass())) {
          checkConnection();
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

我们接着来看PooledDataSourcepushConnection方法:

  protected void pushConnection(PooledConnection conn) throws SQLException {

    // 将连接返回给连接池时,也需要加锁
    synchronized (state) {
      // 在该连接池的工作Connection的List中移除该连接
      state.activeConnections.remove(conn);
      if (conn.isValid()) {

        // 若该连接池中的空闲Connection的List的数量小于最大空闲连接poolMaximumIdleConnections(默认为5)且expectedConnectionTypeCode相同(expectedConnectionTypeCode是数据库连接的url+username+password生成的一个hashCode值),则将该连接中的真实连接realConnection取出,重新封装成一个PooledConnection,并将其加入空闲Connection的List中
        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.");
          }
          // 这里执行的notifyAll,会唤醒之前在获取Connection时,调用wait()等待的线程
          state.notifyAll();
        } else {
          // 若空闲的Connection数量已经达到最大,则取出真实连接realConnection,将其真正的close掉
          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++;
      }
    }
  }

3.JNDI

这个数据源,笔者也没有使用过,这里就不展开讲了,之后如果有机会,可以单独开一篇文章讲解.


到这里,dataSource相关内容就基本讲完了.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值