Mybatis源碼-datasource(數據源)總結

這個包主要功能的如何獲取到數據源對象, 間接獲取Connection(連接對象)來操作數據庫

1、獲取DataSource方式有兩種

1.1. 通過jndi的(InitialContext上下文)獲取,jndi的lookup方法,從某個地方獲取配置生成一個DataSource
1.2. 通過java代碼,傳入datasource需要參數,比如用戶名、密碼、驅動類路徑等等

2、這個包一個關係的簡圖

關係圖

3、PooledConnection類解析(部分核心代碼)

package org.apache.ibatis.datasource.pooled;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

import org.apache.ibatis.reflection.ExceptionUtil;

/**
 * 連接池
 * 1、連接池數據源、真實連接和代理連接對象
 * 2、還有時間相關,比如上一次使用連接對象的時間
 * 3、實現InvocationHandler的invoke方法,這個方法就是在調用真實方法之前調用該方法,在創建代理對象時候將代理對象與InvocationHandler
 * 進行關聯,相當於類成員變量proxyConnection,代理對象關聯本類的invoke方法,主要是爲判斷是否執行是close方法,執行
 * close方法需要進行額外的操作
 * @author Clinton Begin
 */
class PooledConnection implements InvocationHandler {

  /**
   * 關閉
   */
  private static final String CLOSE = "close";

  /**
   * 連接類,創建代理對象用改的
   */
  private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

  /**
   * hashCode
   */
  private final int hashCode;

  /**
   * 連接池數據源
   */
  private final PooledDataSource dataSource;

  /**
   * 真實連接
   */
  private final Connection realConnection;
  /**
   * 代理連接
   */
  private final Connection proxyConnection;

  /**
   * 檢出時間戳
   */
  private long checkoutTimestamp;
  /**
   * 創建的時間戳
   */
  private long createdTimestamp;
  /**
   * 最後使用的時間戳
   */
  private long lastUsedTimestamp;

  /**
   * 這個主要用於區分唯一性, 用戶名+密碼+url生成hashCode 確定唯一性
   */
  private int connectionTypeCode;

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

  /**
   * 使用連接對象和連接池數據源對象
   * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in.
   *
   * @param connection - the connection that is to be presented as a pooled connection
   * @param dataSource - the dataSource that the connection is from
   */
  public PooledConnection(Connection connection, PooledDataSource dataSource) {
    //連接hashCode
    //連接對象
    //數據源
    //創建時間戳
    //最後使用時間戳
    //初始化有效
    //代理連接對象創建在執行proxyConnection的方法將會調用 當前invoke的方法
    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);
  }

 /**
   * Required for InvocationHandler implementation.
   * 實現InvocationHandler的實現方法, 代理對象是爲了,判斷方法是不是close方法
   * @param proxy  - not used
   * @param method - the method to be executed
   * @param args   - the parameters to be passed to the method
   * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //獲取Connection調用的方法的名稱是不是close方法
    //是需要關閉連接操作
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && 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);
    }

  }

}

3.1 總結

  1. 這裏關鍵地方應該是它創建一個代理Connection對象,主要攔截close方法,將用完連接對象放回池中

4、PooledDataSource

/**
 * 這個是一個簡單、同步,線程安全數據連接池
 * This is a simple, synchronous, thread-safe database connection pool.
 * 1、PooledDataSource和UnpooledDataSource的區別主要前面比後面多一個方法, 從線程中取連接對象,popConnection, 連接集合對象的狀態
 * 2、修改配置信息需要強制清空激活連接數集合和空閒連接對象集合
 * 3、調用connection close的方法時候,它將用完的連接對象放回連接池,但是這個connection對象是不變,但是外面包PooledConnection變成最新的
 * 4、完成線程池的功能主要利用PoolState類的中兩個集合,活動連接集合和空閒連接集合, 包括裏面一些統計操作
 * @author Clinton Begin
 */
public class PooledDataSource implements DataSource {

  /**
   * 獲取日誌對象
   */
  private static final Log log = LogFactory.getLog(PooledDataSource.class);

  /**
   * 根據當前對象創建連接池狀態
   */
  private final PoolState state = new PoolState(this);

  /**
   * 非連接池數據源
   */
  private final UnpooledDataSource dataSource;

  // OPTIONAL CONFIGURATION FIELDS
  /**
   * 連接池活動連接對象默認是10
   */
  protected int poolMaximumActiveConnections = 10;
  /**
   * 空閒連接對象是5
   */
  protected int poolMaximumIdleConnections = 5;
  /**
   * 連接池的檢出的時間是2s
   */
  protected int poolMaximumCheckoutTime = 20000;

  /**
   * 連接池超時時間爲2s
   */
  protected int poolTimeToWait = 20000;

  /**
   * 可以容忍最大本地連接失敗是3次
   */
  protected int poolMaximumLocalBadConnectionTolerance = 3;

  /**
   * 連接池ping查詢
   */
  protected String poolPingQuery = "NO PING QUERY SET";
  /**
   * 開始連接池ping查詢
   */
  protected boolean poolPingEnabled;

  /**
   * 連接對象沒有被使用之後,需要ping 數據庫的時間間隔
   *
   */
  protected int poolPingConnectionsNotUsedFor;

  /**
   * 希望連接類型code,其實就是url+username+password 提取hashCode
   */
  private int expectedConnectionTypeCode;

  /**
   * 創建一個無參函數
   * PooledDataSource與UnpooledDataSource區別?
   *
   */
  public PooledDataSource() {
    dataSource = new UnpooledDataSource();
  }

  /**
   * 有一個參數的構造函數,UnpooledDataSource
   * @param dataSource
   */
  public PooledDataSource(UnpooledDataSource dataSource) {
    this.dataSource = dataSource;
  }
   /**
   * 將用完的連接對象放回連接池
   * @param conn
   * @throws SQLException
   */
  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      //從當前活動線程取出該連接對象
      //如果有效
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        //當前空閒連接對象的大小小於最大限制空閒連接數,且是當前用戶名和密碼登錄連接池
        //統計累積檢出時間
        //如果連接不是自動提交將手動調用rollback()方法
        //重新創建連接對象,並將它加入到空閒集合中
        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);
          //設置當前時間戳爲上一個連接的時間創建時間戳
          //最後使用的時間戳也是修改一下(其實應該說重新用PooledConnection 包一下老的Connection)
          //設置老connection無效
          //喚醒其他線程
          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++;
      }
    }
  }

  /**
   * 從數據庫中拿出一個連接代理Connection對象
   * @param username 用戶名
   * @param password 密碼
   * @return
   * @throws SQLException
   */
  private PooledConnection popConnection(String username, String password) throws SQLException {
    //計數等待
    //定義變量conn
    //本地失敗連接對象計數
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;
    //從連接池獲取連接對象
    //同步state對象
    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 {
                // 等待計數,需要等待poolTimeToWait(2s)時間
                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();
            }
            //設置連接hashcode
            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;
            //本次失敗次數 > 最大空閒數量+最大單次失敗次數,拋出異常(默認是9次
            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.");
            }
          }
        }
      }

    }
    // 爲啥conn會爲null
    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;
  }

  /**
   * 檢查是否可用, ping操作
   * Method to check to see if a connection is still usable
   *
   * @param conn - the connection to check
   * @return True if the connection is still usable
   */
  protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;
    //判斷connection本身是否關閉了
    try {
      result = !conn.getRealConnection().isClosed();
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
      result = false;
    }
    //未關閉操作
    //是否開啓ping操作
    if (result) {
      if (poolPingEnabled) {
        // 這個離上一次使用時間到現在時間段已經大於ping設置間隔時間
        if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
          try {
            if (log.isDebugEnabled()) {
              log.debug("Testing connection " + conn.getRealHashCode() + " ...");
            }
            //獲取真實對象
            //執行sql語句,然後關閉
            //如果拋出異常說明ping有問題
            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;
  }
}

4.1、總結

  1. 這裏關鍵方法就是popConnection,從連接池中取出連接對象,pushConnection將用完連接對象放回連接池,pingConnection判斷連接對象是否有效
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章