[druid 源碼解析] 5 歸還連接

我們?cè)趧?chuàng)建鏈接的時(shí)候會(huì)發(fā)現(xiàn),返回給 Mybatis 的并不是一個(gè)簡(jiǎn)單的 connection 而是一個(gè) DruidPooledConnection 這里是一個(gè)我們需要注意點(diǎn)的點(diǎn)髓棋,會(huì)到正題卖子,我們拿到一個(gè)鏈接媳瞪,假如使用完成后我們最重要就是 close 掉這個(gè)鏈接因谎,那么 Mybatis 也是這樣做的亏推。我們可以看一下 SqlSessionInterceptor 這個(gè)類(lèi)的 Invoke 方法敛瓷,所有 Dao 都是通過(guò)這個(gè)攔截器生成代理對(duì)象叁巨。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

我們看 finally 方法最終調(diào)用了 closeSqlSession 方法,我們根據(jù)最后調(diào)用鏈路呐籽,最終跟蹤到了DruidPooledConnection 的 recycle 方法锋勺,調(diào)用棧如下:

調(diào)用棧

我們先看一下他的 recycle 方法。

public void recycle() throws SQLException {
        if (this.disable) {
            return;
        }

        DruidConnectionHolder holder = this.holder;
        if (holder == null) {
            if (dupCloseLogEnable) {
                LOG.error("dup close");
            }
            return;
        }

        if (!this.abandoned) {
            DruidAbstractDataSource dataSource = holder.getDataSource();
            dataSource.recycle(this);
        }

        this.holder = null;
        conn = null;
        transactionInfo = null;
        closed = true;
    }

我們看一下主要的邏輯狡蝶,就是調(diào)用 DruiddataSourcerecycle 方法, 兜兜轉(zhuǎn)轉(zhuǎn)終于回到了我們的 DruiddataSource庶橱。

 protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
        final DruidConnectionHolder holder = pooledConnection.holder;

        if (holder == null) {
            LOG.warn("connectionHolder is null");
            return;
        }
        // 假如當(dāng)前線(xiàn)程和獲取鏈接不是同一個(gè)線(xiàn)程,這里會(huì) warn 報(bào)錯(cuò)贪惹, 這里會(huì)涉及到事務(wù)相關(guān)的東西苏章,我們后面詳解。
        if (logDifferentThread //
            && (!isAsyncCloseConnectionEnable()) //
            && pooledConnection.ownerThread != Thread.currentThread()//
        ) {
            LOG.warn("get/close not same thread");
        }

        final Connection physicalConnection = holder.conn;
        // 從 activeConnections 中移除當(dāng)前 connection
        if (pooledConnection.traceEnable) {
            Object oldInfo = null;
            activeConnectionLock.lock();
            try {
                if (pooledConnection.traceEnable) {
                    oldInfo = activeConnections.remove(pooledConnection);
                    pooledConnection.traceEnable = false;
                }
            } finally {
                activeConnectionLock.unlock();
            }
            if (oldInfo == null) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
                }
            }
        }

        final boolean isAutoCommit = holder.underlyingAutoCommit;
        final boolean isReadOnly = holder.underlyingReadOnly;
        final boolean testOnReturn = this.testOnReturn;

        try {
            // check need to rollback?
            if ((!isAutoCommit) && (!isReadOnly)) {
                pooledConnection.rollback();
            }
            // 假如不是同個(gè)線(xiàn)程馍乙,需要開(kāi)啟鎖后才能rest布近,rest 主要是做一些關(guān)于重置 holder setting 的事情垫释。
            // reset holder, restore default settings, clear warnings
            boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
            if (!isSameThread) {
                final ReentrantLock lock = pooledConnection.lock;
                lock.lock();
                try {
                    holder.reset();
                } finally {
                    lock.unlock();
                }
            } else {
                holder.reset();
            }

            if (holder.discard) {
                return;
            }
            // j檢查是否大于最大連接數(shù),是的話(huà)清除
            if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
                discardConnection(holder);
                return;
            }
            // 檢查線(xiàn)程是否關(guān)閉
            if (physicalConnection.isClosed()) {
                lock.lock();
                try {
                    if (holder.active) {
                        activeCount--;
                        holder.active = false;
                    }
                    closeCount++;
                } finally {
                    lock.unlock();
                }
                return;
            }
            // 檢查線(xiàn)程是否存活
            if (testOnReturn) {
                boolean validate = testConnectionInternal(holder, physicalConnection);
                if (!validate) {
                    JdbcUtils.close(physicalConnection);

                    destroyCountUpdater.incrementAndGet(this);

                    lock.lock();
                    try {
                        if (holder.active) {
                            activeCount--;
                            holder.active = false;
                        }
                        closeCount++;
                    } finally {
                        lock.unlock();
                    }
                    return;
                }
            }
            if (holder.initSchema != null) {
                holder.conn.setSchema(holder.initSchema);
                holder.initSchema = null;
            }

            if (!enable) {
                discardConnection(holder);
                return;
            }

            boolean result;
            final long currentTimeMillis = System.currentTimeMillis();
            // 檢查是否超過(guò)醉倒時(shí)長(zhǎng)
            if (phyTimeoutMillis > 0) {
                long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
                if (phyConnectTimeMillis > phyTimeoutMillis) {
                    discardConnection(holder);
                    return;
                }
            }
            // 獲取鎖撑瞧,最重要的邏輯開(kāi)始了
            lock.lock();
            try {
                // 計(jì)時(shí)器操作
                if (holder.active) {
                    activeCount--;
                    holder.active = false;
                }
                closeCount++;
                // 將連接放回 connections
                result = putLast(holder, currentTimeMillis);
                recycleCount++;
            } finally {
                lock.unlock();
            }

            if (!result) {
                JdbcUtils.close(holder.conn);
                LOG.info("connection recyle failed.");
            }
        } catch (Throwable e) {
            holder.clearStatementCache();

            if (!holder.discard) {
                discardConnection(holder);
                holder.discard = true;
            }

            LOG.error("recyle error", e);
            recycleErrorCountUpdater.incrementAndGet(this);
        }
    }

這里的邏輯比較簡(jiǎn)單棵譬,主要如下:

  1. 檢查當(dāng)前線(xiàn)程和獲取鏈接的線(xiàn)程是否同一個(gè),這里可能涉及到并發(fā)問(wèn)題和事務(wù)問(wèn)題预伺,我們后續(xù)進(jìn)行講解订咸。
  2. 件當(dāng)前線(xiàn)程從活躍線(xiàn)程組 activeConnections 中移除,主要是方便后面的丟棄或者回收的工作酬诀。
  3. 然后檢查是否需要進(jìn)行回滾脏嚷,不需要繼續(xù)往下走。
  4. reset 當(dāng)前 connectionholder 的相關(guān)配置瞒御。
  5. 接下來(lái)是對(duì)各項(xiàng)信息進(jìn)行檢查父叙,主要是看連接是否還可以重用。
  6. 鎖住然后進(jìn)行真正的回收工作肴裙,這里回收交給了 putLast 方法趾唱。
    最后我們來(lái)看一下 putLast 方法。
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
        if (poolingCount >= maxActive || e.discard || this.closed) {
            return false;
        }

        e.lastActiveTimeMillis = lastActiveTimeMillis;
        connections[poolingCount] = e;
        incrementPoolingCount();

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

        notEmpty.signal();
        notEmptySignalCount++;

        return true;
    }

這里主要是將連接放回到 connections 空閑連接數(shù)組的最后面蜻懦,這里我們還記得甜癞,獲取鏈從頭開(kāi)始取,然后釋放就放回到最后一位宛乃。最后發(fā)送信號(hào)悠咱,通知等待線(xiàn)程獲取連接。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末征炼,一起剝皮案震驚了整個(gè)濱河市析既,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柒室,老刑警劉巖渡贾,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雄右,居然都是意外死亡空骚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)擂仍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)囤屹,“玉大人,你說(shuō)我怎么就攤上這事逢渔±呒幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)智厌。 經(jīng)常有香客問(wèn)我诲泌,道長(zhǎng),這世上最難降的妖魔是什么铣鹏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任敷扫,我火速辦了婚禮,結(jié)果婚禮上诚卸,老公的妹妹穿的比我還像新娘葵第。我一直安慰自己,他們只是感情好合溺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布卒密。 她就那樣靜靜地躺著,像睡著了一般棠赛。 火紅的嫁衣襯著肌膚如雪哮奇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天睛约,我揣著相機(jī)與錄音屏镊,去河邊找鬼。 笑死痰腮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的律罢。 我是一名探鬼主播膀值,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼误辑!你這毒婦竟也來(lái)了沧踏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巾钉,失蹤者是張志新(化名)和其女友劉穎翘狱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體砰苍,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潦匈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赚导。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茬缩。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吼旧,靈堂內(nèi)的尸體忽然破棺而出凰锡,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布掂为,位于F島的核電站裕膀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勇哗。R本人自食惡果不足惜昼扛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望智绸。 院中可真熱鬧野揪,春花似錦、人聲如沸瞧栗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迹恐。三九已至挣惰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殴边,已是汗流浹背憎茂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锤岸,地道東北人竖幔。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像是偷,于是被迫代替她去往敵國(guó)和親拳氢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容