# Mybatis深入之數據庫連接池原理
### 簡介
主要記錄Mybatis數據庫連接池實現原理、如何使用連接池來管理數據庫連接的、連接池如何向外提供數據庫連接、當外部調用使用完成之后是如何將數據庫連接放回數據庫連接池的。
### 準備
有前面的相關文章的鋪墊、這里就不再從Mybatis數據庫相關信息的初始化以及何時創建一個真正的數據庫連接并且向外提供使用的。這兩方面的過程可以參見[Mybatis深入之DataSource實例化過程 ](http://blog.csdn.net/crave_shy/article/details/46584803)和[Mybatis深入之獲取數據庫連接 ](http://blog.csdn.net/crave_shy/article/details/46597239)兩篇文章。
了解Mybatis數據庫連接池如何配置
~~~
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
~~~
- 標簽`<dataSource type="POOLED">`指明使用Mybatis自帶數據庫連接池
- 標簽`<transactionManager type="JDBC"/>`指明使用JDBC形式管理事務、參見[ Mybatis深入之事務管理 ](http://blog.csdn.net/crave_shy/article/details/46595391)
### 原理分析
當Mybatis初始化完成之后、根據上面的配置以及前面的文章知道、 環境中的DataSource實例是PooledDataSource、環境中的Transaction實例是JdbcTransaction。
[Mybatis深入之獲取數據庫連接 ](http://blog.csdn.net/crave_shy/article/details/46597239)中知道、當執行第一個sql語句時才會嘗試獲取數據庫連接詳細的可以看前面的文章、這里直接上獲取數據庫連接的關鍵代碼:
~~~
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
~~~
- 獲取PooledConnection——popConnection(dataSource.getUsername(), dataSource.getPassword())
- 調用PooledConnection的getProxyConnection()方法返回真正連接的代理連接
當嘗試獲取數據庫連接的時候會使用數據庫連接池功能、詳細獲取數據庫連接的代碼[Mybatis深入之獲取數據庫連接 ](http://blog.csdn.net/crave_shy/article/details/46597239)有簡單介紹。
在想要了解數據庫連接池原理之前需要了解一個Mybatis用來存放數據庫連接池狀態的類:PoolState、PooledConnection以及PooledDataSource類屬性以及之間的關系:

- PoolState數據庫連接池狀態類、其內部有標識數據庫連接池狀態的各個屬性、重點是兩個屬性:`List<PooledConnection> idleConnections`用于存放空閑狀態的數據庫連接。`List<PooledConnection> activeConnections`用于存放活動狀態的連接、也就是正在使用的數據庫連接。
- PoolState內部持有一個DataSource引用、在PoolState被實例化時初始化、主要用于展示當前數據庫連接的一些配置信息、比如用戶名、密碼等。
-
PooledConnection內部持有一個PooledDataSource、同樣在PooledConnection被構造時實例化PooledDataSource、其中有兩個屬性`private long createdTimestamp;
private long lastUsedTimestamp;`用來標識當前PooledConnection的創建時間和最后使用時間、用來判斷此連接是否過期。
-
PooledDataSource 簡單的線程安全的數據庫連接池、對外提供數據庫連接池功能。
-
### 獲取PooledConnection
從上面可以知道程序中使用了數據庫連接池之后、獲取的數據庫連接是從PooledDataSource中方法popConnection(String username, String password)獲取的、獲取代碼:
~~~
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
//恒成立、直到上面定義的PooledConnection被正確實例化或者程序異常中止
while (conn == null) {
//synchronized (state)是保證PooledDataSource是一個線程安全的數據庫連接池的原因。
synchronized (state) {
//如果當前數據庫連接池中有空閑狀態的數據庫連接、則直接取出一個作為當前方法執行結果返回。
if (state.idleConnections.size() > 0) {
// 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
//創建一個內部持有真正數據庫連接的PooledConnection
conn = new PooledConnection(dataSource.getConnection(), this);
@SuppressWarnings("unused")
//used in logging, if enabled
//真正的數據庫連接
Connection realConn = conn.getRealConnection();
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()) {
oldestActiveConnection.getRealConnection().rollback();
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} 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.");
}
}
}
}
}
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;
}
~~~
總結上述步驟
1. 先看是否有空閑(idle)狀態下的PooledConnection對象,如果有,就直接返回一個可用的PooledConnection對象;否則進行第2步。
1. 查看活動狀態的PooledConnection池activeConnections是否已滿;如果沒有滿,則創建一個新的PooledConnection對象,然后放到activeConnections池中,然后返回此PooledConnection對象;否則進行第三步;
1. 看最先進入activeConnections池中的PooledConnection對象是否已經過期:如果已經過期,從activeConnections池中移除此對象,然后創建一個新的PooledConnection對象,添加到activeConnections中,然后將此對象返回;否則進行第4步。
1. 線程等待,循環2步
下面是從網絡上摘抄的一個流程圖:

### 獲取最終數據庫連接
通過PooledConnection.getProxyConnection()來獲取最終數據庫連接。
~~~
public Connection getProxyConnection() {
return proxyConnection;
}
~~~
這里只要弄清楚proxyConnection是什么就行,在上面一節獲取PooledDataSource時候的popConnection方法中知道PooledConnection是在這個方法里面創建的、調用的都是相同的PooledConnection構造方法:
~~~
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);
}
~~~
- 重點關注最后一行代碼`this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);`
- 這里是動態代理模式來使用當前類PooledConnection來代理Connection類。
- 關于動態代理設計模式相關的這里不是重點、可以看一下[Java設計模式之代理 ](http://blog.csdn.net/crave_shy/article/details/21000887)
所以最終返回的是真正的Connection的代理類PooledConnection。至于為什么要這樣做、接著看。
### 數據庫連接使用完畢放回連接池
不使用數據庫連接池時、正常使用數據庫連接的情況下、當使用完畢之后我們就會調用其close()方法來關閉連接、避免資源浪費。但是當使用了數據庫連接池之后、一個數據庫連接被使用完之后就不再是調用其close方法關閉掉、而是應該將這個數據庫連接放回連接池、那么我們就要攔截Connection.close()方法、將這個Connection放回連接池、而不是關閉。
使用動態代理的方式實現上述功能、PooledConnection實現了InvocationHandler接口、并提供了invoke方法的實現。當調用Connection的方法時會執行PooledConnection的invoke方法:
~~~
/*
* Required for InvocationHandler implementation.
* * @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[])
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
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);
}
}
}
~~~
- 攔截Connection執行方法、
- 如果是close方法、放回數據庫連接池
- 如果不是、放掉、由Connection繼續執行
~~~
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包含真正Connection重新創建一個PooledConnection并放到連接池中
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++;
}
}
}
~~~
1. 如果當前需要關閉的數據庫連接以失效、廢棄不管PoolState的badConnectionCount自增1
1. 如果當前連接有效并且當前數據庫連接池中空閑連接數沒有達到數據庫連接池連接最大數、并且此數據庫連接是所期望放回數據庫連接池的。重新根據當前PooledConnection中的真正數據庫連接Connection創建一個新的PooledConnection并放回數據庫連接池中。
1. 如果當前空閑連接數已達到數據庫連接池容量最大值、或者不是所期望的數據庫連接、關閉連接
到這里、數據庫連接池原理就結束了。
### 補充
Mybatis的數據庫連接池簡單、線程安全。當與spring結合的時候通常也可以使用第三方數據庫連接池、將有關數據庫連接、事務都交由spring去管理。
更多內容:[Mybatis 目錄](http://blog.csdn.net/crave_shy/article/details/45825599)