1. 程式人生 > 資料庫 >Mybatis + Druid 資料庫連線池的連線快取原理

Mybatis + Druid 資料庫連線池的連線快取原理

Mybatis + Druid 資料庫連線池的連線快取原理

Mybatis 預設資料庫連線池快取原理

Mybatis 預設資料庫連線池快取原理,和為什麼要使用連線池, 網站上較多文章可以通過這個連結檢視https://www.cnblogs.com/yougewe/articles/10061276.html

Mybatis+Druid 連線池原理

在Druid中定義了DruidDataSource 類來維護資料庫連線池狀態,定義了connections陣列來儲存已經建立的資料庫連線。

    private volatile DruidConnectionHolder[] connections;

driud 將與資料庫的物理連線(最原始的資料庫Connection)儲存在DruidConnectionHolder中其中,DruidConnectionHolder儲存了資料庫連線Connection和連線監控資訊。


Druid 連線池初始化和連接獲取

Mybatis+Druid 資料庫連線池一起使用時,首先是通過SqlSessionFactory.openSession獲取一個SqlSession的例項,執行資料庫操作時又通過SqlSession獲取對應的Mapper,呼叫Mapper的方法,實際資料庫連線Connection的建立是在執行資料庫操作的時候建立的。獲取資料庫連線的邏輯是在DruidDataSource中

    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        //按配置初始化資料庫連線池
        init();

        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis); //從資料庫連線池中獲取資料庫連線
        }
    }

在呼叫DruidDataSource.geConnection時首先會呼叫init方法,init方法按照mybatis的配置進行資料庫連線池的初始化,初始化之後getConnectionDirect再從連線池中返回資料庫連線。

其中init方法中建立資料庫連線池的部分邏輯如下:


//..校驗配置資訊合法性,通過配置資訊建立載入驅動類

                while (poolingCount < initialSize) {
                    try {
                        PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); //通過讀取的配置創建於資料庫的連線
                        DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); //用DruidConnectionHolder儲存的連線和連線資訊
                        connections[poolingCount++] = holder;  //將DruidConnectionHolder儲存到連線池中
                    } catch (SQLException ex) {
                        LOG.error("init datasource error, url: " + this.getUrl(), ex);
                        if (initExceptionThrow) {
                            connectError = ex;
                            break;
                        } else {
                            Thread.sleep(3000);
                        }
                    }
                }

init() 方法中按配置的初始池連線數初始化資料庫連線,前後部分還的校驗配置的和合法性、統計活躍的連線和殭屍連線、設定連線狀態回撥處理器等操作,這寫內容不展示。

然後下一步getConnectionDirect方法就是直接從連線池中獲取上一步初始化的資料庫連線,關鍵邏輯如下:


    if (maxWait > 0) {
        holder = pollLast(nanos);//從connections陣列中獲取資料庫連線
    } else {
        holder = takeLast();
    }

    if (holder != null) {
        activeCount++;
        if (activeCount > activePeak) {
            activePeak = activeCount;
            activePeakTime = System.currentTimeMillis();
        }
    }

getConnectionDirect 的主要工作就是通過pollLast方法從connections陣列中獲取資料庫連線,並返回,實現從連線池中獲取資料庫連線的效果。

Druid 連接回收重用

在Mybatis中SqlSession在非執行緒安全的,不能被共享的, 所以在每一次業務呼叫觸發並返回響應之後,就應該呼叫sqlSession的close()方法關閉它。如果不使用資料庫連線池呼叫sqlSession.close()方法就會連通對應的資料庫連線一同關閉掉。

SqlSession.close的呼叫鏈


//1.  DefaultSqlSession.java
 @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

//2.  CachingExecutor.java
  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        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;
    }
  }

  //3. JdbcTransaction.java
  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close(); //關閉資料庫連線
    }
  }

如程式碼所示最後呼叫connection.close()了,在沒有資料庫連線池的情況下與資料庫的連線connection就是關閉了,但是在使用Druid資料庫連線池時connection的是com.alibaba.druid.pool.DruidPooledConnection 類的例項,DruidPooledConnection類實現了java.sql.Connection介面,並重寫了close()方法,在重寫的close方法中並沒有直接關閉掉資料庫連線,而是將資料庫連接回收到了DruidDataSource中的connections陣列中。回收方法是DruidDataSource的recycle方法中,處理邏輯如下:

    lock.lock();
    try {
        activeCount--;
        closeCount++;

        result = putLast(holder, currentTimeMillis);//呼叫putLast方法將DruidConnectionHolder回收到connections中
        recycleCount++;
    } finally {
        lock.unlock();
    }

putLast邏輯如下:


    boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
        if (poolingCount >= maxActive) {
            return false;
        }

        e.lastActiveTimeMillis = lastActiveTimeMillis;
        connections[poolingCount] = e;//將當前關閉sqlSession對應的資料庫連線DruidConnectionHolder放回connections中
        incrementPoolingCount();

        if (poolingCount > poolingPeak) {
            poolingPeak = poolingCount;
            poolingPeakTime = lastActiveTimeMillis;
        }

        notEmpty.signal();
        notEmptySignalCount++;

        return true;
    }

就是這樣的流程在使用Mybatis+Druid的時候關閉SqlSession,對應的連資料庫連線不會直接關掉,而是重新儲存到DruidDataSource.connections中,在下一次執行資料庫操作時可以直接從DruidDataSource.connections中直接獲取已建立的連線,從而優化建立資料庫連線帶來的效率影響。