mybatis 源碼分析(四) 自帶連接池
- mybatis 源碼分析(一) Xml解析,容器初始化
- mybatis 源碼分析(二) sql執行路徑分析
- 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();
}
}
}