Mybatis 數據源和數據庫鏈接池源碼解析(DataSource)

歡迎關注我的公衆號:Java技術大雜燴,天天10點精美文章準時奉上java

本文將從如下幾個方面介紹

相關文章sql

前言數據庫

類圖緩存

工廠類實現安全

數據庫鏈接實現服務器

鏈接池的實現ide

從鏈接池中獲取鏈接(流程圖)源碼分析

把鏈接放入到鏈接池中(流程圖)測試

相關文章

Mybatis 解析配置文件的源碼解析ui

Mybatis 類型轉換源碼分析

前言

在使用 Mybatis 的時候,數據庫的鏈接通常都會使用第三方的數據源組件,如 C3P0,DBCP 和 Druid 等,其實 Mybatis 也有本身的數據源實現,能夠鏈接數據庫,還有鏈接池的功能,下面就來看看 Mybatis 本身實現的數據源頭和鏈接池的一個實現原理。

類圖

Mybatis 數據源的實現主要是在 datasource 包下:

咱們常見的數據源組件都實現了 Javax.sql.DataSource 接口,Mybatis 也實現該接口而且提供了兩個實現類 UnpooledDataSource 和 PooledDataSource 一個使用鏈接池,一個不使用鏈接池,此外,對於這兩個類,Mybatis 還提供了兩個工廠類進行建立對象,是工廠方法模式的一個應用,首先來看下它們的一個類圖:

關於上述幾個類,PooledDataSource UnpooledDataSource 是數據源實現的主要邏輯,代碼比較複雜,放在後面來看,如今先看看看兩個工廠類 。

DataSourceFactory 

先來看看 DataSourceFactory  類,該類是 JndiDataSourceFactory UnpooledDataSourceFactory 兩個工廠類的頂層接口,只定義了兩個方法,以下所示:

public interface DataSourceFactory {
  // 設置 DataSource 的相關屬性,通常在初始化完成後進行設置
  void setProperties(Properties props);
  // 獲取數據源 DataSource 對象
  DataSource getDataSource();

}

UnpooledDataSourceFactory 

UnpooledDataSourceFactory 主要用來建立 UnpooledDataSource 對象,它會在構造方法中初始化 UnpooledDataSource 對象,並在 setProperties 方法中完成對 UnpooledDataSource 對象的配置

public class UnpooledDataSourceFactory implements DataSourceFactory {
  // 數據庫驅動前綴
  private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
  // 對應的數據源,即 UnpooledDataSource
  protected DataSource dataSource;

  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }
  // 對數據源 UnpooledDataSource 進行配置
  @Override
  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    // 建立 DataSource 相應的 MetaObject 
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    // 遍歷 properties 集合,該集合中存放了數據源須要的信息
    for (Object key : properties.keySet()) {
      String propertyName = (String) key;
      // 以 "driver." 開頭的配置項是對 DataSource 的配置,記錄到 driverProperties  中
      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)) { // 該屬性是否有 set 方法
        // 獲取對應的屬性值
        String value = (String) properties.get(propertyName);
        // 根據屬性類型進行類型的轉換,主要是 Integer, Long, Boolean 三種類型的轉換
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // 設置DataSource 的相關屬性值
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    // 設置 DataSource.driverProerties 屬性值
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }
  // 返回數據源
  @Override
  public DataSource getDataSource() {
    return dataSource;
  }
  // 類型轉
  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;
  }
}

JndiDataSourceFactory 依賴 JNDI 服務器中獲取用戶配置的 DataSource,這裏能夠不看。

PooledDataSourceFactory 

PooledDataSourceFactory 主要用來建立 PooledDataSource 對象,它繼承了 UnpooledDataSource 類,設置 DataSource 參數的方法複用UnpooledDataSource 中的 setProperties 方法,只是數據源返回的是  PooledDataSource 對象而已。

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

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

以上這些就是 Mybatis 用來建立數據源的工廠類,下面就來看下數據源的主要實現。

UnpooledDataSource

UnpooledDataSource 不使用鏈接池來建立數據庫鏈接,每次獲取數據庫鏈接時都會建立一個新的鏈接進行返回;

public class UnpooledDataSource implements DataSource {
  // 加載 Driver 類的類加載器
  private ClassLoader driverClassLoader;
  // 數據庫鏈接驅動的相關配置
  private Properties driverProperties;
  // 緩存全部已註冊的數據庫鏈接驅動
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();

  private String driver;
  private String url;
  private String username;
  private String password;
  // 是否自動提交
  private Boolean autoCommit;
  // 事物隔離級別
  private Integer defaultTransactionIsolationLevel;

  // 靜態塊,在初始化的時候,從 DriverManager 中獲取全部的已註冊的驅動信息,並緩存到該類的 registeredDrivers集合中
  static {
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

  public UnpooledDataSource() {
  }

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

接下來看下獲取鏈接的方法:

// 獲取一個新的數據庫鏈接
  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return doGetConnection(username, password);
  }

  // 根據 properties 獲取一個新的數據庫鏈接
  private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化數據庫驅動
    initializeDriver();
    // 經過 DriverManager 來獲取一個數據庫鏈接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置數據庫鏈接的 autoCommit 和隔離級別
    configureConnection(connection);
    // 返回新鏈接
    return connection;
  }

  // 初始化數據庫驅動
  private synchronized void initializeDriver() throws SQLException {
    // 若是當前的驅動尚未註冊,則進行註冊
    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();
        // 向  JDBC 的 DriverManager 註冊驅動
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        // 向本類的 registeredDrivers 註冊驅動
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }
  
  // 設置數據庫鏈接的 autoCommit 和隔離級別
  private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

以上代碼就是 UnpooledDataSource 類的主要實現邏輯,每次獲取鏈接都是從數據庫新建立一個鏈接進行返回,又由於,數據庫鏈接的建立是一個耗時的操做,且數據庫鏈接是很是珍貴的資源,若是每次獲取鏈接都建立一個,則可能會形成系統的瓶頸,拖垮響應速度等,這時就須要數據庫鏈接池了,Mybatis 也提供了本身數據庫鏈接池的實現,就是 PooledDataSource 類。

PooledDataSource

PooledDataSource  是一個比較複雜的類,PooledDataSource  新建立數據庫鏈接是使用 UnpooledDataSource  來實現的,且 PooledDataSource  並不會管理 java.sql.Connection 對象,而是管理 PooledConnection 對象,在 PooledConnection 中封裝了真正的數據庫鏈接對象和其代理對象;此外,因爲它是一個鏈接池,因此還須要管理鏈接池的狀態,好比有多少鏈接是空閒的,還能夠建立多少鏈接,此時,就須要一個類來管理鏈接池的對象,即 PoolState 對象;先來看下 PooledDataSource 的一個 UML 圖:

PooledConnection

先來看看 PooledConnection 類,它主要是用來管理數據庫鏈接的,它是一個代理類,實現了 InvocationHandler 接口,

class PooledConnection implements InvocationHandler {
  // close 方法
  private static final String CLOSE = "close";
  // 記錄當前的 PooledConnection 對象所在的 PooledDataSource 對象,該 PooledConnection 對象是從 PooledDataSource 對象中獲取的,當調用 close 方法時會將 PooledConnection 放回該 PooledDataSource 中去
  private PooledDataSource dataSource;
  // 真正的數據庫鏈接
  private Connection realConnection;
  // 數據庫鏈接的代理對象
  private Connection proxyConnection;
  // 從鏈接池中取出該鏈接的時間戳
  private long checkoutTimestamp;
  // 該鏈接建立的時間戳
  private long createdTimestamp;
  // 該鏈接最後一次被使用的時間戳
  private long lastUsedTimestamp;
  // 用於標識該鏈接所在的鏈接池,由URL+username+password 計算出來的hash值
  private int connectionTypeCode;
  // 該鏈接是否有效
  private boolean valid;

  // 建立鏈接
  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);
  }

  // 廢棄該鏈接
  public void invalidate() {
    valid = false;
  }

  // 判斷該鏈接是否有效,
  // 1.判斷 valid 字段
  // 2.向數據庫中發送檢測測試的SQL,查看真正的鏈接仍是否有效
  public boolean isValid() {
    return valid && realConnection != null && dataSource.pingConnection(this);
  }
 //     setter / getter  方法
}

接下來看下 invoke 方法,該方法是 proxyConnection 這個鏈接代理對象的真正代理邏輯,它會對 close 方法進行代理,而且在調用真正的鏈接以前對鏈接進行檢測。

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 若是執行的方法是 close 方法,則會把當前鏈接放回到 鏈接池中去,供下次使用,而不是真正的關閉數據庫鏈接
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        // 若是不是 close 方法,則 調用 真正的數據庫鏈接執行
        if (!Object.class.equals(method.getDeclaringClass())) {
          // 執行以前,須要進行鏈接的檢測
          checkConnection();
        }
        // 調用數據庫真正的鏈接進行執行
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

PoolState 

PoolState 類主要是用來管理鏈接池的狀態,好比哪些鏈接是空閒的,哪些是活動的,還能夠建立多少鏈接等。該類中只是定義了一些屬性來進行控制鏈接池的狀態,並無任何的方法。

public class PoolState {
  // 該 PoolState 屬於哪一個 PooledDataSource 
  protected PooledDataSource dataSource;

  // 來用存放空閒的 pooledConnection 鏈接
  protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();

  // 用來存放活躍的 PooledConnection 鏈接
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

  // 請求數據庫鏈接的次數
  protected long requestCount = 0;
  // 獲取鏈接的累計時間
  protected long accumulatedRequestTime = 0;
  // checkoutTime 表示從鏈接池中獲取鏈接到歸還鏈接的時間
  // accumulatedCheckoutTime 記錄了全部鏈接的累計 checkoutTime 時長
  protected long accumulatedCheckoutTime = 0;
  // 鏈接超時的鏈接個數
  protected long claimedOverdueConnectionCount = 0;
  // 累計超時時間
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  // 累計等待時間
  protected long accumulatedWaitTime = 0;
  // 等待次數
  protected long hadToWaitCount = 0;
  // 無效的鏈接數
  protected long badConnectionCount = 0;
 
  //  setter / getter  方法
}

PooledDataSource

PooledDataSource 它是一個簡單的,同步的,線程安全的數據庫鏈接池

知道了 UnpooledDataSource 用來建立數據庫新的鏈接,PooledConnection 用來管理鏈接池中的鏈接,PoolState 用來管理鏈接池的狀態以後,來看下 PooledDataSource 的一個邏輯,該類中主要有如下幾個方法:獲取數據庫鏈接的方法 popConnection,把鏈接放回鏈接池的方法 pushConnection,檢測數據庫鏈接是否有效的方法 pingConnection ,還有 關閉鏈接池中全部鏈接的方法 forceCloseAll,接下來就來看看這幾個方法是怎麼實現,在看以前,先看下該方法定義的一些屬性:

public class PooledDataSource implements DataSource {

  // 鏈接池的狀態
  private final PoolState state = new PoolState(this);
  
  // 用來建立真正的數據庫鏈接對象
  private final UnpooledDataSource dataSource;

  // 最大活躍的鏈接數,默認爲 10 
  protected int poolMaximumActiveConnections = 10;

  // 最大空閒鏈接數,默認爲 5
  protected int poolMaximumIdleConnections = 5;

  // 最大獲取鏈接的時長 
  protected int poolMaximumCheckoutTime = 20000;

  // 在沒法獲取到鏈接時,最大等待的時間
  protected int poolTimeToWait = 20000;

  // 在檢測一個鏈接是否可用時,會向數據庫發送一個測試 SQL 
  protected String poolPingQuery = "NO PING QUERY SET";
  // 是否容許發送測試 SQL
  protected boolean poolPingEnabled;

  // 當鏈接超過 poolPingConnectionsNotUsedFor 毫秒未使用時,會發送一次測試 SQL 語句,測試鏈接是否正常
  protected int poolPingConnectionsNotUsedFor;

  // 標誌着當前的鏈接池,是 url+username+password 的 hash 值
  private int expectedConnectionTypeCode;

  // 建立鏈接池
  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());
  }
  
   // 生成 hash 值
   private int assembleConnectionTypeCode(String url, String username, String password) {
    return ("" + url + username + password).hashCode();
  }
  //   setter /  getter 方法
}

接下來看下從數據庫鏈接池中獲取鏈接的實現邏輯:

從 鏈接池中獲取鏈接的方法主要是在 popConnection 中實現的,先來看下它的一個流程圖:

 

代碼邏輯以下:

// 獲取鏈接
  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
  }
  
  // 從鏈接池中獲取鏈接
  private PooledConnection popConnection(String username, String password) throws SQLException {
    // 等待的個數
    boolean countedWait = false;
    // PooledConnection 對象
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    // 無效的鏈接個數
    int localBadConnectionCount = 0;

    while (conn == null) {
      synchronized (state) {
        // 檢測是否還有空閒的鏈接
        if (!state.idleConnections.isEmpty()) {
          // 鏈接池中還有空閒的鏈接,則直接獲取鏈接返回
          conn = state.idleConnections.remove(0);
        } else {
          // 鏈接池中已經沒有空閒鏈接了
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // 活躍的鏈接數沒有達到最大值,則建立一個新的數據庫鏈接
            conn = new PooledConnection(dataSource.getConnection(), this);
          } else {
            // 若是活躍的鏈接數已經達到容許的最大值了,則不能建立新的數據庫鏈接
            // 獲取最早建立的那個活躍的鏈接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            // 檢測該鏈接是否超時
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // 若是該鏈接超時,則進行相應的統計
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              // 將超時鏈接移出 activeConnections 集合
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                  // 若是超時未提交,則自動回滾
                  oldestActiveConnection.getRealConnection().rollback();
              }
              // 建立新的 PooledConnection 對象,可是真正的數據庫鏈接並無建立
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              // 設置該超時的鏈接爲無效
              oldestActiveConnection.invalidate();
            } else {
               // 若是無空閒鏈接,沒法建立新的鏈接且無超時鏈接,則只能阻塞等待
              // Must wait
              try {
                if (!countedWait) {
                  state.hadToWaitCount++; // 等待次數
                  countedWait = true;
                }
                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();
            }
            // 設置 PooledConnection 相關屬性
            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 {
            // 無效鏈接
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
          }
        }
      }
    }
    return conn;
  }

以上就是從鏈接池獲取鏈接的主要邏輯。

如今來看下當執行 close 方法的時候,會把鏈接放入的鏈接池中以供下次從新使用,把鏈接放入到鏈接池中的方法爲 pushConnection 方法,它也是 PooledDataSource 類的一個主要方法,先來看下它的流程圖:

代碼以下:

// 把不用的鏈接放入到鏈接池中
  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      // 首先從活躍的集合中移除掉該鏈接
      state.activeConnections.remove(conn);
      // 檢測鏈接是否有效
      if (conn.isValid()) {
         // 若是空閒鏈接數沒有達到最大值,且 PooledConnection 爲該鏈接池的鏈接
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          // 累計 checkout 時長
          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());
          // 設置該鏈接爲無效狀態
          conn.invalidate();
          // 喚醒阻塞等待的線程
          state.notifyAll();
        } else {
          // 若是空閒鏈接數已經達到最大值
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          // 則關閉真正的數據庫連擊破
          conn.getRealConnection().close();
          // 設置該鏈接爲無效狀態
          conn.invalidate();
        }
      } else {
        // 無效鏈接個數加1
        state.badConnectionCount++;
      }
    }
  }

以上代碼就是把不用的鏈接放入到鏈接池中以供下次使用,

在上面兩個方法中,都調用了 isValid 方法來檢測鏈接是否可用,該方法除了檢測 valid 字段外,還會調用 pingConnection 方法來嘗試讓數據庫執行測試 SQL 語句,從而檢測真正的數據庫鏈接對象是否依然正常可用。

// 檢測鏈接是否可用
  public boolean isValid() {
    return valid && realConnection != null && dataSource.pingConnection(this);
  }
  // 向數據庫發送測試 SQL 來檢測真正的數據庫鏈接是否可用
  protected boolean pingConnection(PooledConnection conn) {
    // 結果
    boolean result = true;

    try { 
      // 檢測真正的數據庫鏈接是否已經關閉
      result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {    
      result = false;
    }
     // 若是真正的數據庫鏈接還沒關閉
    if (result) {
      // 是否執行測試 SQL 語句
      if (poolPingEnabled) {
         // 長時間(poolPingConnectionsNotUsedFor 指定的時長)未使用的鏈接,才須要ping操做來檢測鏈接是否正常
        if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
          try {
            // 發送測試 SQL 語句執行
            Connection realConn = conn.getRealConnection();
            Statement statement = realConn.createStatement();
            ResultSet rs = statement.executeQuery(poolPingQuery);
            rs.close();
            statement.close();
            if (!realConn.getAutoCommit()) {
              realConn.rollback();
            }
            result = true;
          } catch (Exception e) {
            try {
              conn.getRealConnection().close();
            } catch (Exception e2) {
            }
            result = false;
          }
        }
      }
    }
    return result;
  }

此外,當修改 PooledDataSource 相應的字段,如 數據庫的 URL,用戶名或密碼等,須要將鏈接池中鏈接所有關閉,以後獲取鏈接的時候從從新初始化。關閉鏈接池中所有鏈接的方法爲 forceCloseAll:

public void forceCloseAll() {
    synchronized (state) {
      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) {
          
        }
      }
    }
  }

總結

在鏈接池中提到了 鏈接池中的最大鏈接數和最大空閒數,在 獲取鏈接和把鏈接放入鏈接池中都有判斷,

1. 獲取鏈接:首先從鏈接池中進行獲取,若是鏈接池中已經沒有空閒的鏈接了,則會判斷當前的活躍鏈接數是否已經達到容許的最大值了,若是沒有,則還能夠建立新的鏈接,以後把它放到活躍的集合中進行使用,若是當前活躍的已達到最大值,則阻塞。

2.返還鏈接到鏈接池,在返還鏈接的時候,進行判斷,若是空閒鏈接數已達到容許的最大值,則直接關閉真正的數據庫鏈接,不然把該鏈接放入到空閒集合中以供下次使用。

Mybatis 數據源中,主要的代碼邏輯仍是在鏈接池類 PooledDataSource 中,對於獲取鏈接的方法 popConnection,返還鏈接的方法 pushConnection ,須要結合上圖來看,才能看得清楚。

相關文章
相關標籤/搜索