我們在創(chuàng)建鏈接的時候會發(fā)現(xiàn),返回給 Mybatis 的并不是一個簡單的 connection 而是一個 DruidPooledConnection 這里是一個我們需要注意點的點,會到正題,我們拿到一個鏈接,假如使用完成后我們最重要就是 close 掉這個鏈接,那么 Mybatis 也是這樣做的。我們可以看一下 SqlSessionInterceptor 這個類的 Invoke 方法,所有 Dao 都是通過這個攔截器生成代理對象。
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)用 DruiddataSource 的 recycle 方法, 兜兜轉(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)前線程和獲取鏈接不是同一個線程,這里會 warn 報錯, 這里會涉及到事務(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();
}
// 假如不是同個線程,需要開啟鎖后才能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ù),是的話清除
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
discardConnection(holder);
return;
}
// 檢查線程是否關(guān)閉
if (physicalConnection.isClosed()) {
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
// 檢查線程是否存活
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();
// 檢查是否超過醉倒時長
if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
// 獲取鎖,最重要的邏輯開始了
lock.lock();
try {
// 計時器操作
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);
}
}
這里的邏輯比較簡單,主要如下:
- 檢查當(dāng)前線程和獲取鏈接的線程是否同一個,這里可能涉及到并發(fā)問題和事務(wù)問題,我們后續(xù)進(jìn)行講解。
- 件當(dāng)前線程從活躍線程組
activeConnections中移除,主要是方便后面的丟棄或者回收的工作。 - 然后檢查是否需要進(jìn)行回滾,不需要繼續(xù)往下走。
-
reset當(dāng)前connection的holder的相關(guān)配置。 - 接下來是對各項信息進(jìn)行檢查,主要是看連接是否還可以重用。
- 鎖住然后進(jìn)行真正的回收工作,這里回收交給了
putLast方法。
最后我們來看一下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ù)組的最后面,這里我們還記得,獲取鏈從頭開始取,然后釋放就放回到最后一位。最后發(fā)送信號,通知等待線程獲取連接。