Mybatis源码阅读----数据源模块(DataSource)

数据源模块主要对数据库的底层连接进行了封装
DataSource模块所在位置:
Mybatis源码阅读----数据源模块(DataSource)_第1张图片

常见的数据源组件都实现了javax.sql.DataSource, Mybatis 自身在这里插入代码片实现的数据源也不例外。MyBatis 提供了两个 av ax. sq l.DataSource 接口实现,分别是 PooledDataSource,UnpooledDataSource。Mybatis 使用不同的 DataSourceFactory 接口实现创建不同类型的DataSource。

Mybatis源码阅读----数据源模块(DataSource)_第2张图片
1. DataSourceFactory工厂类接口,其中定义方法如下

public interface DataSourceFactory {


  /**
   *  设置dataSource的先关属性,在初始化完成之后
   */
  void setProperties(Properties props);

  /**
   *  获取dataSource对象
   * @return
   */
  DataSource getDataSource();

}

2.DataSourceFactory其中一个实现类(具体工厂)UnpooledDataSourceFactory,主要用于创建UnpooledDataSource,通过构造器创建UnpooledDataSource,

 public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }

其获取数据源直接调用如下方法返回数据源即可:

 @Override
  public DataSource getDataSource() {
    return dataSource;
  }

为数据源设置相属性

 @Override
  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    // 获取数据源的相关属性,生成metaObject对象
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    // 遍历propertis中存储的配置信息
    for (Object key : properties.keySet()) {
      // 获取属性名
      String propertyName = (String) key;
      // 如果属性名以prefix开头,说明不是非必须属性,记录在driverProperties进行保存
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        // 获取该属性的名的值
        String value = properties.getProperty(propertyName);
        // 为该属性名设值
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
        // 判断该属性是否有set方法
      } else if (metaDataSource.hasSetter(propertyName)) {
        String value = (String) properties.get(propertyName);
        // 根据属性类型进行转换 <1>
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // 设值属性值
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    // 设值driverProperties到metaObject中
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

在<1>处根据属性类型进行转换主要为Integer, Long, 和 Boolean类型

/**
 *  将字符串转化成对应的数据类型
 * @param metaDataSource  metaObject对象
 * @param propertyName  属性名
 * @param value   属性值
 * @return  转化后的对象
   */
  private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
    // 用于记录转化类型
    Object convertedValue = value;
    // 获取属性名的类型
    Class<?> targetType = metaDataSource.getSetterType(propertyName);
    // 判断后进行相应的转化
    if (targetType == Integer.class || targetType == int.class) {
      convertedValue = Integer.valueOf(value);
    } else if (targetType == Long.class || targetType == long.class) {
      convertedValue = Long.valueOf(value);
    } else if (targetType == Boolean.class || targetType == boolean.class) {
      convertedValue = Boolean.valueOf(value);
    }
    return convertedValue;
  }

3.UnpooledDataSource数据源实现了DataSource接口,实现了相关的方法,并重载了部分方法。
这个数据源的实现只是每次被请求时打开和关闭连接。 虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类的完全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

  • url – 这是数据库的 JDBC URL 地址。

  • username – 登录数据库的用户名。

  • password – 登录数据库的密码。

  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

作为可选项,你也可以传递属性给数据库驱动。要这样做,属性的前缀为“driver.”,例如: driver.encoding=UTF8
该类定义的成员变量如下:

/**
   *  加载driver的类加载器
   */
  private ClassLoader driverClassLoader;
  /**
   *  数据库连接驱动的相关配置
   */
  private Properties driverProperties;

  /**
   *  缓存已经注册的链接驱动
   */
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();

  /**
   *  数据库连接驱动的名称
   */
  private String driver;
  /**
   *  数据库的地址
   */
  private String url;
  /**
   *  用户名
   */
  private String username;
  /**
   *  密码
   */
  private String password;

  /**
   *  是否自动提交
   */
  private Boolean autoCommit;
  /**
   *  事务的隔离级别
   */
  private Integer defaultTransactionIsolationLevel;
  /**
   *  数据库连接超时时间
   */
  private Integer defaultNetworkTimeout;

/**
   *  将已经注册到DriverManager中的注册信息JDBC driver 复制到registeredDrivers 中
   */
  static {
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

定义了一系列的构造器

public UnpooledDataSource(String driver, String url, String username, String password) {
    this.driver = driver;
    this.url = url;
    this.username = username;
    this.password = password;
  }
 public UnpooledDataSource(String driver, String url, Properties driverProperties) {
    this.driver = driver;
    this.url = url;
    this.driverProperties = driverProperties;
  }
  ....

获取数据库链接的方法:

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

doGetConnection(String username, String password)方法如下

private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();

    if (driverProperties != null) {
      // 如果数据库驱动连接的相关配置不为空,将其全部放到props中
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }

doGetConnection(Properties props)方法如下:

private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化数据路驱动
    initializeDriver();
    // 获取连接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置数据库连接池aCommit和隔离级别
    configureConnection(connection);
    return connection;
  }

initializeDriver() 方法如下:

private synchronized void initializeDriver() throws SQLException {
    // 检测是registeredDrivers是否已经存在,没有存在则初始化driver
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          // 初始化driver,类似于我们常见的Class.forName("com.mysql.jdbc.Driver")
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        // 创建driver实例
        Driver driverInstance = (Driver)driverType.getDeclaredConstructor().newInstance();
        // 创建driverProxy并注册到DriverManager
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        // 添加到registeredDrivers中
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

configureConnection(Connection conn) 方法如下:

 /**
 *  主要设置连接超时的时间、是否自动提交和数据库隔离级别
 * @param conn
 * @throws SQLException
   */
  private void configureConnection(Connection conn) throws SQLException {
    if (defaultNetworkTimeout != null) {
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

4、PooledDataSourceFactory主要用于创PooledDataSource,其继承了UnpooledDataSourceFactory。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
除了上述提到 UNPOOLED 的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000
    毫秒(即 20 秒)
  • poolTimeToWait –这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000
    毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置,作用于每一个尝试从缓存池获取连接的线程 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections poolMaximumLocalBadConnectionTolerance之和。 默认值:3 (新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置
  • poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery
    的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

  /**
   *  继承了UnpooledDataSouceFactory,将数据源变成了PooledDataSource
   */
  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }

}

5、PooledDataSource 该类是池化的实现类,实现了DataSource接口。
Mybatis源码阅读----数据源模块(DataSource)_第3张图片
PooledConnection类实现了InvocationHandler接口,用于JDK动态代理。PooledConnection中字段如下:

 /**
   *  关闭connection方法名
   */
  private static final String CLOSE = "close";
  /**
   *  JDK Proxy接口
   */
  private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

  /**
   *  对象的标识,基于hashcode
   */
  private final int hashCode;

  /**
   *  所属PooledDataSource对象
   */
  private final PooledDataSource dataSource;
  /**
   *  Connection链接
   */
  private final Connection realConnection;
  /**
   *  connection 动态代理连接
   */
  private final Connection proxyConnection;

  /**
   *  从连接池中获取走的时间
   */
  private long checkoutTimestamp;

  /**
   *  对象创建的时间
   */
  private long createdTimestamp;

  /**
   *  最后更新的时间
   */
  private long lastUsedTimestamp;

  /**
   *  链接表示
   */
  private int connectionTypeCode;

  /**
   *  是否有效
   */
  private boolean valid;

PoolConnection构造器如下:

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

该类中主要方法为 invoke代理获取连接的方法:

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 判断是否为 CLOSE 方法,若是,则将连接放回到连接池中,避免连接被关闭
    if (CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      // 将链接放回了连接池,返回空
      return null;
    }
    try {
      // 判断非 Object 的方法,则先检查连接是否可用
      if (!Object.class.equals(method.getDeclaringClass())) {
        // 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);
    }

  }

checkConnection()检查链接是否可用的方法如下

 private void checkConnection() throws SQLException {
    if (!valid) {
      throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
    }
  }

PoolState 主要管理PoolConnection对象状态的组件。记录了链接的相关信息。

// 所属的PooledDataSource 对象
  protected PooledDataSource dataSource;
  // 空闲的PooledConnection集合
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // 激活的PooledConnection 集合
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  // 全局统计 - 获取连接的次数
  protected long requestCount = 0;
  //  全局统计 - 获取连接的时间
  protected long accumulatedRequestTime = 0;
  // 获取到连接非超时 + 超时的占用时长
  protected long accumulatedCheckoutTime = 0;
  // 获取到连接超时的次数
  protected long claimedOverdueConnectionCount = 0;
  // 获取到连接超时的占用时长
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  //  等待连接的时间
  protected long accumulatedWaitTime = 0;
  // 等待连接的次数
  protected long hadToWaitCount = 0;
  // 全局统计 - 获取到坏的连接的次数
  protected long badConnectionCount = 0;

PoolState中还定以了一些获取上述字段的方法,比较简单。
在PooledDataSource的dataSource实际为UplooledDataSource。PooledDataSource中定义的相关字段如下:

/**
   *  poolState 对象,记录池化的状态
   */
  private final PoolState state = new PoolState(this);

  /**
   * UnpooledDataSource对象
   */
  private final UnpooledDataSource dataSource;


 // 任意一时间可以存在的连接数量
  protected int poolMaximumActiveConnections = 10;
  // 最小空闲的连接数量
  protected int poolMaximumIdleConnections = 5;
  // 在被强制返回之前,池中连接被检出(checked out)时间。单位:毫秒
  protected int poolMaximumCheckoutTime = 20000;
  // 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败)。单位:毫秒
  protected int poolTimeToWait = 20000;
  // 这是一个关于坏连接容忍度的底层设置,作用于每一个尝试从缓存池获取连接的线程.
  // 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接
  // 但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  // 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。
  protected String poolPingQuery = "NO PING QUERY SET";
  // 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句
  protected boolean poolPingEnabled;
  // 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测
  // — 当然仅当 poolPingEnabled 为 true 时适用)
  protected int poolPingConnectionsNotUsedFor;

  // 望 Connection 的类型编码
  private int expectedConnectionTypeCode;

在PooledDataSource中获取连接首先会调用popConnection(…)方法
获取PooledConnection对象,然后在获取其代理对象。

 @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();
  }

popConnection方法时PooledDataSource的核心方法之一,其逻辑如下:
Mybatis源码阅读----数据源模块(DataSource)_第4张图片
代码如下

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()) {
          // 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
          // 当激活连接数小于poolMaximumActiveConnections 时
          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 {
            // 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()) {
                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 无效
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
              // 检查到没有超时时
            } else {
              // Must wait
              try {
                //对等待连接进行统计。通过 countedWait 标识,在这个循环中,只记录一次
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                // 记录当前时间
                long wt = System.currentTimeMillis();
                // 等待,直到超时,或 pingConnection 方法中归还连接时的唤醒
                state.wait(poolTimeToWait);
                // 统计等待链接的时间
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        // 如果链接不为空
        if (conn != null) {
          // ping to server and check the connection is valid or not
          // 判断链接是否有效,通过ping的方式进行检测
          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());
            // 向活跃的链接集合中添加新conn
            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++;
            // 记录获取到坏的连接的次数【本方法】
            localBadConnectionCount++;
            // 将conn置空,可以进行继续获取
            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.");
            }
          }
        }
      }

    }

    // 获取不到连接抛出SQL异常
    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;
  }

在PoolConnection的invoke方法中,当方法名为close时应该关闭链接,但是其并未真正调用close()方法,而是调用了方法pushConnection(PooledConnection conn),该方法也是PooledDataSource中的重要方法之一,主要作用两个:当空闲链接没有超过最大时,将连接放回连接池,当线程已经超过最大允许空闲连接时,则将其真正关闭。其逻辑如下:

代码如下:

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 对象,并添加到空闲的链接集合中
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          // 设置原链接失效,将使用新的newConn,避免再次调用该conn,否则将会抛出异常
          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.");
        }
        // 统计坏连接的次数
        state.badConnectionCount++;
      }
    }
  }

isValid() 方法主要检测当前链接是否有效,方法如下:

public boolean isValid () { 
  return valid && realConnection != null && dataSource.p ngConnection(this);
}

而pingConnection(this)主要通过ping的方式检查是否与数据据是否建立了链接。

protected boolean pingConnection(PooledConnection conn) {
    // 默认是有效的
    boolean result = true;

    try {
      // 判断真实的链接是否已经关闭,若已经关闭,则ping失败
      result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
      result = false;
    }

    // 没有关闭的情况下
    if (result) {
      // 是否启用侦测查询
      if (poolPingEnabled) {
        //判断是否长时间未使用。若是,才需要发起 ping
        if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
          try {
            if (log.isDebugEnabled()) {
              log.debug("Testing connection " + conn.getRealHashCode() + " ...");
            }
            // 获取真实链接
            Connection realConn = conn.getRealConnection();
            try (Statement statement = realConn.createStatement()) {
              // 创建statemet,并执行poolPingQuery,发起ping
              statement.executeQuery(poolPingQuery).close();
            }
            // 没有自动提交将进行回滚
            if (!realConn.getAutoCommit()) {
              realConn.rollback();
            }
            // 标记执行成功
            result = true;
            if (log.isDebugEnabled()) {
              log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
            }
          } 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;
  }

其中在PooledDataSource中还有一个关闭全部链接的方法如下:

public void forceCloseAll() {
    synchronized (state) {
      // 计算 expectedConnectionTypeCode
      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.");
    }
  }

至此,DataSource模块主要源码解读完毕 !
如有错误不当之处还望指出,谢谢!
参考资料 《Mybatis 技术内幕》!

你可能感兴趣的:(Mybatis源码,mysql,java,mybatis)