mybaits原始碼分析--事務管理(八)

童話述說我的結局發表於2021-09-08

一、事務管理

寫到這也快進入收尾階段了了,在介紹MyBatis中的事務管理時不可避免的要接觸到DataSource的內容,所以接下來會分別來介紹DataSource和Transaction兩塊內容。

1. DataSource

在資料持久層中,資料來源是一個非常重要的元件,其效能直接關係到整個資料持久層的效能,在實際開發中我們常用的資料來源有 Apache Common DBCP,C3P0,Druid 等,MyBatis不僅可以整合第三方資料來源,還提供的有自己實現的資料來源。在MyBatis中提供了兩個 javax.sql.DataSource 介面的實現,分別是 PooledDataSource 和UnpooledDataSource .

 

 1.1 DataSourceFactory

 DataSourceFactory是用來建立DataSource物件的,介面中宣告瞭兩個方法,作用如下

public interface DataSourceFactory {
  // 設定 DataSource 的相關屬性,一般緊跟在初始化完成之後
  void setProperties(Properties props);
  // 獲取 DataSource 物件
  DataSource getDataSource();

}

DataSourceFactory介面的兩個具體實現是 UnpooledDataSourceFactory 和PooledDataSourceFactory 這兩個工廠物件的作用通過名稱我們也能發現是用來建立不帶連線池的資料來源物件和建立帶連線池的資料來源物件,先來看下 UnpooledDataSourceFactory 中的方法

  @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;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        // 以 "driver." 開頭的配置項是對 DataSource 的配置
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        // 有該屬性的 setter 方法
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // 設定 DataSource 的相關屬性值
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      // 設定 DataSource.driverProperties 的屬性值
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

UnpooledDataSourceFactory的getDataSource方法實現比較簡單,直接返回DataSource屬性記錄的 UnpooledDataSource 物件

1.2 UnpooledDataSource

UnpooledDataSource 是 DataSource介面的其中一個實現,但是 UnpooledDataSource 並沒有提供資料庫連線池的支援,看下他的具體實現;UnpooledDataSource 中獲取Connection的方法最終都會呼叫 doGetConnection() 方法。

public class UnpooledDataSource implements DataSource {

  private ClassLoader driverClassLoader; // 載入Driver的類載入器
  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;

  static {
    // 從 DriverManager 中獲取 Drivers
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      // 將獲取的 Driver 記錄到 Map 集合中
      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;
  }

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

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

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

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

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

  @Override
  public void setLoginTimeout(int loginTimeout) {
    DriverManager.setLoginTimeout(loginTimeout);
  }

  @Override
  public int getLoginTimeout() {
    return DriverManager.getLoginTimeout();
  }

  @Override
  public void setLogWriter(PrintWriter logWriter) {
    DriverManager.setLogWriter(logWriter);
  }

  @Override
  public PrintWriter getLogWriter() {
    return DriverManager.getLogWriter();
  }

  public ClassLoader getDriverClassLoader() {
    return driverClassLoader;
  }

  public void setDriverClassLoader(ClassLoader driverClassLoader) {
    this.driverClassLoader = driverClassLoader;
  }

  public Properties getDriverProperties() {
    return driverProperties;
  }

  public void setDriverProperties(Properties driverProperties) {
    this.driverProperties = driverProperties;
  }

  public synchronized String getDriver() {
    return driver;
  }

  public synchronized void setDriver(String driver) {
    this.driver = driver;
  }

  public String getUrl() {
    return url;
  }

  public void setUrl(String url) {
    this.url = url;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public Boolean isAutoCommit() {
    return autoCommit;
  }

  public void setAutoCommit(Boolean autoCommit) {
    this.autoCommit = autoCommit;
  }

  public Integer getDefaultTransactionIsolationLevel() {
    return defaultTransactionIsolationLevel;
  }

  public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
    this.defaultTransactionIsolationLevel = defaultTransactionIsolationLevel;
  }

  /**
   * @since 3.5.2
   */
  public Integer getDefaultNetworkTimeout() {
    return defaultNetworkTimeout;
  }

  /**
   * Sets the default network timeout value to wait for the database operation to complete. See {@link Connection#setNetworkTimeout(java.util.concurrent.Executor, int)}
   *
   * @param defaultNetworkTimeout
   *          The time in milliseconds to wait for the database operation to complete.
   * @since 3.5.2
   */
  public void setDefaultNetworkTimeout(Integer defaultNetworkTimeout) {
    this.defaultNetworkTimeout = defaultNetworkTimeout;
  }

  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(Properties properties) throws SQLException {
    initializeDriver();
    // 建立真正的資料庫連線
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置Connection的自動提交和事務隔離級別
    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);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver)driverType.getDeclaredConstructor().newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

  private void configureConnection(Connection conn) throws SQLException {
    if (defaultNetworkTimeout != null) {
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    // 配置Connection的自動提交
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    // 配置Connection的事務隔離級別
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

  private static class DriverProxy implements Driver {
    private Driver driver;

    DriverProxy(Driver d) {
      this.driver = d;
    }

    @Override
    public boolean acceptsURL(String u) throws SQLException {
      return this.driver.acceptsURL(u);
    }

    @Override
    public Connection connect(String u, Properties p) throws SQLException {
      return this.driver.connect(u, p);
    }

    @Override
    public int getMajorVersion() {
      return this.driver.getMajorVersion();
    }

    @Override
    public int getMinorVersion() {
      return this.driver.getMinorVersion();
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
      return this.driver.getPropertyInfo(u, p);
    }

    @Override
    public boolean jdbcCompliant() {
      return this.driver.jdbcCompliant();
    }

    @Override
    public Logger getParentLogger() {
      return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }
  }

  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    throw new SQLException(getClass().getName() + " is not a wrapper.");
  }

  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return false;
  }

  @Override
  public Logger getParentLogger() {
    // requires JDK version 1.6
    return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
  }

}

1.3 PooledDataSource

在運算元據庫的時候資料庫連線的建立過程是非常耗時的,資料庫能夠建立的連線數量也是非常有限的,所以資料庫連線池的使用是非常重要的,使用資料庫連線池會給我們帶來很多好處,比如可以實現資料庫連線的重用,提高響應速度,防止資料庫連線過多造成資料庫假死,避免資料庫連線洩漏等等。先 進入他的工廠物件中來

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

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

}
會發現它的工廠物件直接幫我們建立了PooledDataSource物件,會發現PooledDataSource類中的無參構造中本質上就是UnpooledDataSource(),至於為什麼這麼設計那就要回歸到UnpooledDataSource中去了,在UnpooledDataSource類中本來就幫我們設計好了與資料庫的連線和關閉

 

 在上圖的程式碼中有一行是管理連線池的資訊的

 // 管理狀態
  private final PoolState state = new PoolState(this);

可以點進去看下

public class PoolState {

  protected PooledDataSource dataSource;
  // 空閒的連線
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // 活躍的連線
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  protected long requestCount = 0; // 請求資料庫連線的次數
  protected long accumulatedRequestTime = 0; // 獲取連線累計的時間
  // CheckoutTime 表示應用從連線池中取出來,到歸還連線的時長
  // accumulatedCheckoutTime 記錄了所有連線累計的CheckoutTime時長
  protected long accumulatedCheckoutTime = 0;
  // 當連線長時間沒有歸還連線時,會被認為該連線超時
  // claimedOverdueConnectionCount 記錄連線超時的個數
  protected long claimedOverdueConnectionCount = 0;
  // 累計超時時間
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  // 累計等待時間
  protected long accumulatedWaitTime = 0;
  // 等待次數
  protected long hadToWaitCount = 0;
  // 無效連線數
  protected long badConnectionCount = 0;

  public PoolState(PooledDataSource dataSource) {
    this.dataSource = dataSource;
  }

  public synchronized long getRequestCount() {
    return requestCount;
  }

  public synchronized long getAverageRequestTime() {
    return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
  }

  public synchronized long getAverageWaitTime() {
    return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;

  }

  public synchronized long getHadToWaitCount() {
    return hadToWaitCount;
  }

  public synchronized long getBadConnectionCount() {
    return badConnectionCount;
  }

  public synchronized long getClaimedOverdueConnectionCount() {
    return claimedOverdueConnectionCount;
  }

  public synchronized long getAverageOverdueCheckoutTime() {
    return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
  }

  public synchronized long getAverageCheckoutTime() {
    return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
  }


  public synchronized int getIdleConnectionCount() {
    return idleConnections.size();
  }

  public synchronized int getActiveConnectionCount() {
    return activeConnections.size();
  }

  @Override
  public synchronized String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("\n===CONFINGURATION==============================================");
    builder.append("\n jdbcDriver                     ").append(dataSource.getDriver());
    builder.append("\n jdbcUrl                        ").append(dataSource.getUrl());
    builder.append("\n jdbcUsername                   ").append(dataSource.getUsername());
    builder.append("\n jdbcPassword                   ").append(dataSource.getPassword() == null ? "NULL" : "************");
    builder.append("\n poolMaxActiveConnections       ").append(dataSource.poolMaximumActiveConnections);
    builder.append("\n poolMaxIdleConnections         ").append(dataSource.poolMaximumIdleConnections);
    builder.append("\n poolMaxCheckoutTime            ").append(dataSource.poolMaximumCheckoutTime);
    builder.append("\n poolTimeToWait                 ").append(dataSource.poolTimeToWait);
    builder.append("\n poolPingEnabled                ").append(dataSource.poolPingEnabled);
    builder.append("\n poolPingQuery                  ").append(dataSource.poolPingQuery);
    builder.append("\n poolPingConnectionsNotUsedFor  ").append(dataSource.poolPingConnectionsNotUsedFor);
    builder.append("\n ---STATUS-----------------------------------------------------");
    builder.append("\n activeConnections              ").append(getActiveConnectionCount());
    builder.append("\n idleConnections                ").append(getIdleConnectionCount());
    builder.append("\n requestCount                   ").append(getRequestCount());
    builder.append("\n averageRequestTime             ").append(getAverageRequestTime());
    builder.append("\n averageCheckoutTime            ").append(getAverageCheckoutTime());
    builder.append("\n claimedOverdue                 ").append(getClaimedOverdueConnectionCount());
    builder.append("\n averageOverdueCheckoutTime     ").append(getAverageOverdueCheckoutTime());
    builder.append("\n hadToWait                      ").append(getHadToWaitCount());
    builder.append("\n averageWaitTime                ").append(getAverageWaitTime());
    builder.append("\n badConnectionCount             ").append(getBadConnectionCount());
    builder.append("\n===============================================================");
    return builder.toString();
  }

}

看到上面可以看到很多連線池資訊,什麼空閉連線數啥的,標註的很清楚就不過多說明,後退一步;上面說了很多連線池的一些屬性資訊,接下來就是如何管理這些連線池資訊;站在客戶端的角度來說,當客戶端傳送一個連線請求時,連線池就會去維護和資料庫的一個連線,那麼客戶端和連線池之間肯定要返回一個Connection物件,至於連線池是怎麼返回的呢,那麼一般設計肯定要在連線池那邊給外部提供一個API介面供呼叫;介面如下

@Override
  public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
  }

會發現其中呼叫了 popConnection 方法,在該方法中 返回的是 PooledConnection 物件,而PooledConnection 物件實現了 InvocationHandler 介面,所以會使用到Java的動態代理;

  public Connection getProxyConnection() {
    return proxyConnection;
  }

 

 重點關注下這個類中的invoke 方法

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.equals(methodName)) {
      // 如果是 close 方法被執行則將連線放回連線池中,而不是真正的關閉資料庫連線
      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // issue #579 toString() should never fail
        // throw an SQLException instead of a Runtime
        // 通過上面的 valid 欄位來檢測 連線是否有效
        checkConnection();
      }
      // 呼叫真正資料庫連線物件的對應方法
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

  }

還有就是前面提到的 PoolState 物件,它主要是用來管理 PooledConnection 物件狀態的元件,通過兩個 ArrayList 集合分別管理空閒狀態的連線和活躍狀態的連線,先點進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()) { // 檢測空閒連線
// 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);
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;
// 將超時連線移除 activeConnections
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());
// 將超時的 PooledConnection 設定為無效
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// Must wait 無空閒連線,無法建立新連線和無超時連線 那就只能等待
// Must 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();
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
// 檢查 PooledConnection 是否有效
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 {
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 + 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;
}

為了更好理解,畫了張圖

 

 

上面是需要連線連線池時的操作,但連線池用完後總要關閉吧,下面要看的就是當我們從連線池中使用完成了資料庫的相關操作後,是如何來關閉連線的,通過前面的 invoke 方法的介紹其實我們能夠發現,當我們執行代理物件的 close 方法的時候其實是執行的pushConnection 方法。


 

 

  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      // 從 activeConnections 中移除 PooledConnection 物件
      state.activeConnections.remove(conn);
      if (conn.isValid()) {// 檢測 連線是否有效
        if (state.idleConnections.size() < poolMaximumIdleConnections //是否達到上限
          && conn.getConnectionTypeCode() == expectedConnectionTypeCode // 該PooledConnection 是否為該連線池的連線

        ) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();// 累計checkout 時長
          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();// 將原來的 PooledConnection 連線設定為無效
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          // 喚醒阻塞等待的執行緒
          state.notifyAll();
        } else {
          // 空閒連線達到上限或者 PooledConnection不屬於當前的連線池
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          // 累計checkout 時長
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          // 關閉真正的資料庫連線
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          // 設定 PooledConnection 無線
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        // 統計無效的 PooledConnection 物件個數
        state.badConnectionCount++;
      }
    }
  }

同樣為了把思路搞清,也搞張圖

 

 還有就是我們在原始碼中多處有看到 conn.isValid方法來檢測連線是否有效

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

dataSource.pingConnection(this)中會真正的實現資料庫的SQL執行操作

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;

    try {
      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) {
        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()) {
              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中的屬性的時候都會執行forceCloseAll來強制關閉所有的連線。

  public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
    this.poolMaximumActiveConnections = poolMaximumActiveConnections;
    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) {
          // ignore
        }
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("PooledDataSource forcefully closed/removed all connections.");
    }
  }

 2.Transaction

在實際開發中,控制資料庫事務是一件非常重要的工作,MyBatis使用Transaction介面對事務進行了抽象,定義的介面為

public interface Transaction {

  /**
   * Retrieve inner database connection.
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException;

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException;

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException;

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException;

  /**
   * Get transaction timeout if set.
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException;

}

Transaction介面的實現有兩個分別是 JdbcTransaction 和 ManagedTransaction兩個

2.1 JdbcTransaction

 JdbcTransaction 依賴於JDBC Connection來控制事務的提交和回滾,宣告的相關的屬性為

在構造方法中會完成除了 Connection 屬性外的另外三個屬性的初始化,而Connection會延遲初始化,在我們執行getConnection方法的時候才會執行相關的操作。原始碼比較簡單

2.2 ManagedTransaction

ManagedTransaction的實現更加的簡單,它同樣依賴 DataSource 欄位來獲取 Connection 物件,但是 commit方法和rollback方法都是空的,事務的提交和回滾都是依賴容器管理的。在實際開發中MyBatis通常會和Spring整合,資料庫的事務是交給Spring進行管理的



相關文章