mybatis 源碼分析(四) 自帶連接池-

mybatis 源碼分析(四) 自帶連接池


mybatis 連接池

mybatis 支持3種數據源
1) jndi
顧名思義 支持JNDI創建數據源
2) pooled
連接池實現
3) unpooled
來一個請求連接一個數據庫連接

本篇重點介紹pooled的實現

pooled

**pooled 主要是由4個類構成

這裏寫圖片描述

簡單解釋下:

類名 作用
PooledConnection 連接對象 實現了 InvocationHandler 動態代理接口
PooledDataSource 數據源 實現了 DataSource 接口
PooledDataSourceFactory dataSource生產工廠
PoolState 記錄連接池的狀態 (活動連接數 空閒連接數 請求數 …)

我們先從工廠類開始

PooledDataSourceFactory

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  /** 很簡單 構造方法初始化了dataSource */
  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }
}

PooledDataSource

這個類東西有點多,關注重點
1.實現了dataSource接口(javax.sql.DataSource)
2.構造函數中使用的都是 UnpooledDataSource(可以理解爲就是建立一個連接)
3.實現接口的getConnection 拿到的都是動態代理類
4.關注pushConnection,popConnection 2個方法 連接池的核心實現

public class PooledDataSource implements DataSource {
      public PooledDataSource() {
    dataSource = new UnpooledDataSource();
  }
  /** 省略其他函數 */
    /** 返回的都是一個動態代理類 */
    public Connection getConnection() throws SQLException {
    /** popConnection 從空閒連接池中獲取連接 */
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }
  /** 返回的都是一個動態代理類 */
  public Connection getConnection(String username, String password) throws SQLException {
    /** popConnection 從空閒連接池中獲取連接 */
    return popConnection(username, password).getProxyConnection();
  }
  /** 省略 */
  /** 把當前連接對象從活躍池中移除 放回到空閒池中 */
    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++;
      }
    }
  }
  /** 從空閒連接池中獲取連接 刪除部分代碼*/
  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.size() > 0) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
        } else {
          // 判斷活躍連接池數量
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // 創建一個新的連接
            conn = new PooledConnection(dataSource.getConnection(), this);
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
          } else {
            // Cannot create new connection
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            /** 連接活躍檢查 */
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
            //...........
              }
            } 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) {
          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 + 3)) {
              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.");
            }
          }
        }
      }
    }

    return conn;
  }
}

重點 重點

getConnection 獲取是一個 getProxyConnection 這是個什麼類?動態代理類 具體的實現是在 PooledConnection

PooledConnection

class PooledConnection implements InvocationHandler {
    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class[]{Connection.class};
    //最重要的就是 一個構造函數 和 invoke
      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 Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    /** 判斷method方法是不是close 如果是就調用 pushConnection 放回連接池*/
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      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);
      }
    }
  }
}

通過構造函數 動態代理實現Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this)

代理的是Connection 接口 返回連接池是通過代理close方法實現的.

那什麼時候 調用的connection.close()呢?


誰執行了connection.close方法

我們通過 第一章 知道最終注入mapper接口的bean都是通過 MapperFactoryBean 生成的 MapperProxy 的動態代理類.
在MapperProxy 中 需要一個sqlSession的實例. 從第一章 我們知道 這裏的實例對象是 SqlSessionTemplate.
我們看下 SqlSessionTemplate 怎麼做的

SqlSessionTemplate

public class SqlSessionTemplate implements SqlSession {
      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    /** 生成了一個動態代理類 具體實現在 SqlSessionInterceptor */
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
    /** 動態代理的實現 invoke 代理了全部的 SqlSession 方法*/
    private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      /** getSqlSession 具體實現請看SqlSessionUtils  
        * 首先會從事務中獲取sqlSession
        * 如果沒有 就從SqlSeesionFactory獲取(默認是DefaultSqlSession)
        */
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
          //....
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          //....
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          /** 這個方法很重要 具體實現 請移步
           *  (org.mybatis.spring.SqlSessionUtils)  
           *  最重要是調用了 session.close() 
           *  上面提到 sqlSession的實例對象是DefaultSqlSession
           *  那我們看下 close都幹了什麼
           */
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
}

DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

      public void close() {
    try {
      /** executor 默認實現是SimpleExecutor 
        * close方法 在父類BaseExecutor
        */
      executor.close(isCommitOrRollbackRequired(false));
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

BaseExecutor

public abstract class BaseExecutor implements Executor {
      public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        /** 這裏的實例對象是 JdbcTransaction */
        if (transaction != null) transaction.close();
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }
}

JdbcTransaction

public class JdbcTransaction implements Transaction {
      public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }
}
大家已經看到了 這段代碼 回答了上面的問題:

誰執行了 connection.close

我們再來結合下 第一章 第二章內容 覆盤下整個SQL執行流程

Created with Raphaël 2.1.2MapperProxyFactoryMapperProxyFactoryMapperProxyMapperProxySqlSessionTemplateSqlSessionTemplateSqlSessionInterceptorSqlSessionInterceptormapperMethodmapperMethodDefaultSqlSessionDefaultSqlSessionBaseExecutorBaseExecutorJdbcTransactionJdbcTransactionConnectionConnection生成spring注入的bean注入sqlSession實例代理了sqlSession所有方法bean調用方法 proxy代理 交於mapperMethod.execute執行執行SQL調用代理類代理SqlSession 執行SQL 並調用close調用executor.close()方法調用JdbcTransaction.close()方法調用Connection的close()方法

流程看完了。我們在回到 自帶的連接池 pooled

這裏Connection的實例對象是PooledConnection

這裏的close實現就是 pushConnection(this);

回收連接了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章