Mybatis源碼解讀(一) 數據庫連接及連接池

Mybatis學習筆記

Mybaits是什麼

官網說明

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

首先它是一個持久性框架,支持自定義SQL、存儲過程和高級映射;

特點就是消除了幾乎所有的JDBC代碼、手動設置參數和檢索結果;

可以使用xml或者註解的方式進行配置;

JDBC操作數據庫流程

既然mybatis消除了jdbc代碼,就證明它底層其實也是根據jdbc來操作數據庫的,只不過這些代碼不用開發者來維護了

  1. 加載數據庫驅動
  2. 建立數據庫連接
  3. 創建數據庫操作對象
  4. 定義操作數據庫的sql
  5. 執行數據庫操作
  6. 獲取結果集
  7. 關閉對象回收數據庫資源

既然mybatis使用了jdbc來操作數據庫,那我們就分析一下mybatis是如何一步一步將他們封裝的

1.加載數據庫驅動

首先是數據源javax.sql.DataSource,Mybatis裏也有實現

UnpooledDataSource 和 PooledDataSource

UnpooledDataSource不使用數據庫連接池

PooledDataSource使用數據庫連接池

在獲取他們的時候Mybatis還使用DataSourceFactory工廠方法模式來獲取

0700e11809bef4d78c17383abf86b894fbc.jpg

DataSourceFactory

頂級工廠定義了兩個方法

package org.apache.ibatis.datasource;

import java.util.Properties;
import javax.sql.DataSource;

public interface DataSourceFactory {
  	// 設置數據源的配置
  	// Properties 就是我們平常設置的 username password url等數據
    void setProperties(Properties var1);

  	// 獲取數據源
    DataSource getDataSource();
}

UnpooledDataSourceFactory

package org.apache.ibatis.datasource.unpooled;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * @author Clinton Begin
 */
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();

  protected DataSource dataSource;

  // 在實例化這個工廠的時候就指定了從這個工廠獲取的DataSource是UnpooledDataSource
  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }

  // 設置連接數據
  @Override
  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是成員變量DataSource這是個接口,在使用這個類獲取的時候就是UnpooledDataSource
  @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;
  }

}
1. 構造函數,在實例化這個工廠的時候就指定了從這個工廠獲取的DataSource是UnpooledDataSource
2. getDataSource() ,這裏獲取的DataSource是成員變量DataSource這是個接口,在使用這個類獲取的時候就是UnpooledDataSource

PooledDataSourceFactory

這個類較爲簡單只是繼承了UnpoolDataSourceFactory,添加了一個自己的構造函數

package org.apache.ibatis.datasource.pooled;

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * @author Clinton Begin
 */
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

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

}

這裏的構造函數與UnpooledDataSourceFactory的構造函數是一個目的,就是爲了聲明私有變量DataSource是PoolDataSource;

在繼承關係中字類沒有的方法默認使用父類的,所以setProperties與getDataSource是使用的UnpooledDataSourceFactory的函數;

setProperties是通用的不多解釋;

getDataSource,因爲在構造中就有聲明 this.dataSource = new PooledDataSource(), 所以這裏返回的就是PooledDataSource;

到此爲止我們已經獲取到創建數據源的工廠了,下面是獲取數據源

DataSource

DataSource是用來獲取數據源的,這就是DataSource的作用

UnpooledDataSource

UnpooledDataSource這個類的操作都做些什麼呢?
在這裏插入圖片描述

簡單通過方法命名就可以看到是用來管理數據庫連接的配置信息與獲取數據庫連接的

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

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

獲取連接的方法有兩個一個是帶用戶名密碼的一個是不帶的,因爲我們都會配置數據源的用戶名密碼,用戶名密碼已經被加載到這個類的成員變量中了(在工廠中通過setProperties方法反射到成員變量中的)

doGetConnection(username , password)只是一個適配,適配到doGetConnection(props)上

private Connection doGetConnection(Properties properties) throws SQLException {
  initializeDriver();
  Connection connection = DriverManager.getConnection(url, properties);
  configureConnection(connection);
  return connection;
}

初始化驅動、獲取連接、配置連接(超時時間、自定提交、事務等配置)、返回連接

沒有使用到連接池

PooledDataSource

這個數據源是使用連接池的

先說明一下里面重要的幾個配置

// OPTIONAL CONFIGURATION FIELDS
// 連接池中最大活躍連接
protected int poolMaximumActiveConnections = 10;
// 連接池最大個數
protected int poolMaximumIdleConnections = 5;
// 連接最大佔用時間
protected int poolMaximumCheckoutTime = 20000;
// 獲取不到連接的時候的等待時間
protected int poolTimeToWait = 20000;
// 最大容忍度重複獲取連接的次數
protected int poolMaximumLocalBadConnectionTolerance = 3;

這個裏面又做了些什麼呢(主要的還是獲取數據庫連接)

在這裏插入圖片描述

第一個框:這裏引用了UnPoolDataSource並寫了username、password等的getset方法在工廠setProperties的時候可以形成完整的反射,用來存儲這些信心

第二個框:設置連接池的一些屬性,都是有默認值的可以手動設置也可以使用默認

我們還是來看getConnection這個也是一個代理代理到popConnection上面

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;
              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");
                }
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // 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
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            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;
  }

  1. 聲明連接
  2. 連接回空進入循環體
  3. 對連接池(state)加鎖也就是獲取連接的過程是單線程執行的
  4. 如果連接池不爲空則將第一個連接獲取到連接池中的第一個空閒連接
  5. 獲取到的連接不可以直接返回,需要將校驗一下(防止這個連接上次的事務沒有提交,先進行一次回滾,保障這次使用的連接是一個乾淨可用的連接)
  6. 記錄正在使用的連接數
  7. 將連接放入正在使用的連接池中
  8. 記錄使用時間
  9. 返回連接
  10. 4->如果連接池爲空(沒有初始化連接或連接已經被使用完)
  11. 判斷已經使用中的連接數是否大於最大連接數
  12. 如果沒有超過那就新建一個連接
  13. 重複5->9
  14. 如果超過
  15. 獲取連接池中最早的一個連接
  16. 獲取連接的佔用時間
  17. 判斷是否超過了我們配置的最大佔用時間
  18. 沒有超過就是沒有連接供使用
  19. 超過就等待(Object.wait)
  20. 記錄等待時間等待下次循環
  21. 超過了就判斷此鏈接是否自動提交,沒有自動提交就手動提交一下
  22. 創建一個新的連接
  23. 重複5->9
PoolState(連接池)
package org.apache.ibatis.datasource.pooled;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Clinton Begin
 */
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;
  protected long accumulatedCheckoutTime = 0;
  protected long claimedOverdueConnectionCount = 0;
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  protected long accumulatedWaitTime = 0;
  protected long hadToWaitCount = 0;
  protected long badConnectionCount = 0;

idleConnections 就是連接池

activeConnections是正在被使用的連接池 順序按使用時間先後排序

回收連接

首先說入口,找不到,很正常,入口是動態代理的,

在創建連接的時候又這麼一行代碼

this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);

這個對象被動態代理了也就是IFACES(Connection.class)執行的所有方法都會走它的代理類PooledConnection.invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  String methodName = method.getName();
  if (CLOSE.equals(methodName)) {
    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
      checkConnection();
    }
    return method.invoke(realConnection, args);
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }

}

當方法是close的時候就見這個連接重新放回連接池中來做連接的回收操作pushConnection

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 newConn = new PooledConnection(conn.getRealConnection(), this);
        state.idleConnections.add(newConn);
        newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        conn.invalidate();
        if (log.isDebugEnabled()) {
          log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
        }
        state.notifyAll();
      } else {
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {
          conn.getRealConnection().rollback();
        }
        conn.getRealConnection().close();
        if (log.isDebugEnabled()) {
          log.debug("Closed connection " + conn.getRealHashCode() + ".");
        }
        conn.invalidate();
      }
    } else {
      if (log.isDebugEnabled()) {
        log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
      }
      state.badConnectionCount++;
    }
  }
}

簡單一點就是如果連接池超了最大連接數就不將此連接放回連接池將此連接標記爲使用完成

發佈了9 篇原創文章 · 獲贊 2 · 訪問量 3735
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章