這個包主要功能的如何獲取到數據源對象, 間接獲取Connection(連接對象)來操作數據庫
1、獲取DataSource方式有兩種
1.1. 通過jndi的(InitialContext上下文)獲取,jndi的lookup方法,從某個地方獲取配置生成一個DataSource
1.2. 通過java代碼,傳入datasource需要參數,比如用戶名、密碼、驅動類路徑等等
2、這個包一個關係的簡圖
3、PooledConnection類解析(部分核心代碼)
package org.apache.ibatis.datasource.pooled;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.ibatis.reflection.ExceptionUtil;
/**
* 連接池
* 1、連接池數據源、真實連接和代理連接對象
* 2、還有時間相關,比如上一次使用連接對象的時間
* 3、實現InvocationHandler的invoke方法,這個方法就是在調用真實方法之前調用該方法,在創建代理對象時候將代理對象與InvocationHandler
* 進行關聯,相當於類成員變量proxyConnection,代理對象關聯本類的invoke方法,主要是爲判斷是否執行是close方法,執行
* close方法需要進行額外的操作
* @author Clinton Begin
*/
class PooledConnection implements InvocationHandler {
/**
* 關閉
*/
private static final String CLOSE = "close";
/**
* 連接類,創建代理對象用改的
*/
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
/**
* hashCode
*/
private final int hashCode;
/**
* 連接池數據源
*/
private final PooledDataSource dataSource;
/**
* 真實連接
*/
private final Connection realConnection;
/**
* 代理連接
*/
private final Connection proxyConnection;
/**
* 檢出時間戳
*/
private long checkoutTimestamp;
/**
* 創建的時間戳
*/
private long createdTimestamp;
/**
* 最後使用的時間戳
*/
private long lastUsedTimestamp;
/**
* 這個主要用於區分唯一性, 用戶名+密碼+url生成hashCode 確定唯一性
*/
private int connectionTypeCode;
/**
* 是否有效 valid?
*/
private boolean valid;
/**
* 使用連接對象和連接池數據源對象
* Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in.
*
* @param connection - the connection that is to be presented as a pooled connection
* @param dataSource - the dataSource that the connection is from
*/
public PooledConnection(Connection connection, PooledDataSource dataSource) {
//連接hashCode
//連接對象
//數據源
//創建時間戳
//最後使用時間戳
//初始化有效
//代理連接對象創建在執行proxyConnection的方法將會調用 當前invoke的方法
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);
}
/**
* Required for InvocationHandler implementation.
* 實現InvocationHandler的實現方法, 代理對象是爲了,判斷方法是不是close方法
* @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[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取Connection調用的方法的名稱是不是close方法
//是需要關閉連接操作
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
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);
}
}
}
3.1 總結
- 這裏關鍵地方應該是它創建一個代理Connection對象,主要攔截close方法,將用完連接對象放回池中
4、PooledDataSource
/**
* 這個是一個簡單、同步,線程安全數據連接池
* This is a simple, synchronous, thread-safe database connection pool.
* 1、PooledDataSource和UnpooledDataSource的區別主要前面比後面多一個方法, 從線程中取連接對象,popConnection, 連接集合對象的狀態
* 2、修改配置信息需要強制清空激活連接數集合和空閒連接對象集合
* 3、調用connection close的方法時候,它將用完的連接對象放回連接池,但是這個connection對象是不變,但是外面包PooledConnection變成最新的
* 4、完成線程池的功能主要利用PoolState類的中兩個集合,活動連接集合和空閒連接集合, 包括裏面一些統計操作
* @author Clinton Begin
*/
public class PooledDataSource implements DataSource {
/**
* 獲取日誌對象
*/
private static final Log log = LogFactory.getLog(PooledDataSource.class);
/**
* 根據當前對象創建連接池狀態
*/
private final PoolState state = new PoolState(this);
/**
* 非連接池數據源
*/
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
/**
* 連接池活動連接對象默認是10
*/
protected int poolMaximumActiveConnections = 10;
/**
* 空閒連接對象是5
*/
protected int poolMaximumIdleConnections = 5;
/**
* 連接池的檢出的時間是2s
*/
protected int poolMaximumCheckoutTime = 20000;
/**
* 連接池超時時間爲2s
*/
protected int poolTimeToWait = 20000;
/**
* 可以容忍最大本地連接失敗是3次
*/
protected int poolMaximumLocalBadConnectionTolerance = 3;
/**
* 連接池ping查詢
*/
protected String poolPingQuery = "NO PING QUERY SET";
/**
* 開始連接池ping查詢
*/
protected boolean poolPingEnabled;
/**
* 連接對象沒有被使用之後,需要ping 數據庫的時間間隔
*
*/
protected int poolPingConnectionsNotUsedFor;
/**
* 希望連接類型code,其實就是url+username+password 提取hashCode
*/
private int expectedConnectionTypeCode;
/**
* 創建一個無參函數
* PooledDataSource與UnpooledDataSource區別?
*
*/
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
/**
* 有一個參數的構造函數,UnpooledDataSource
* @param dataSource
*/
public PooledDataSource(UnpooledDataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 將用完的連接對象放回連接池
* @param conn
* @throws SQLException
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//從當前活動線程取出該連接對象
//如果有效
state.activeConnections.remove(conn);
if (conn.isValid()) {
//當前空閒連接對象的大小小於最大限制空閒連接數,且是當前用戶名和密碼登錄連接池
//統計累積檢出時間
//如果連接不是自動提交將手動調用rollback()方法
//重新創建連接對象,並將它加入到空閒集合中
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);
//設置當前時間戳爲上一個連接的時間創建時間戳
//最後使用的時間戳也是修改一下(其實應該說重新用PooledConnection 包一下老的Connection)
//設置老connection無效
//喚醒其他線程
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++;
}
}
}
/**
* 從數據庫中拿出一個連接代理Connection對象
* @param username 用戶名
* @param password 密碼
* @return
* @throws SQLException
*/
private PooledConnection popConnection(String username, String password) throws SQLException {
//計數等待
//定義變量conn
//本地失敗連接對象計數
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
//從連接池獲取連接對象
//同步state對象
while (conn == null) {
synchronized (state) {
//說明有空閒連接池對象
if (!state.idleConnections.isEmpty()) {
// 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
conn = new PooledConnection(dataSource.getConnection(), this);
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()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
/*
Just log a message for debug and continue to execute the following
statement like nothing happened.
Wrap the bad connection with a new PooledConnection, this will help
to not interrupt current executing thread and give current thread a
chance to join the next competition for another valid/good database
connection. At the end of this loop, bad {@link @conn} will be set as null.
*/
log.debug("Bad connection. Could not roll back");
}
}
// 獲取連接對象
// 設置當前時間戳
// 設置上次使用時間戳
// 標記無效
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
//連接池不夠用,需要等待了
// Must wait
try {
// 等待計數,需要等待poolTimeToWait(2s)時間
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) {
// ping to server and check the connection is valid or not
// 判斷連接對象是否有效
if (conn.isValid()) {
//如果不是自動提交模式,調用回滾
//
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//設置連接hashcode
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;
//本次失敗次數 > 最大空閒數量+最大單次失敗次數,拋出異常(默認是9次
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
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.");
}
}
}
}
}
// 爲啥conn會爲null
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;
}
/**
* 檢查是否可用, ping操作
* Method to check to see if a connection is still usable
*
* @param conn - the connection to check
* @return True if the connection is still usable
*/
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
//判斷connection本身是否關閉了
try {
result = !conn.getRealConnection().isClosed();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
result = false;
}
//未關閉操作
//是否開啓ping操作
if (result) {
if (poolPingEnabled) {
// 這個離上一次使用時間到現在時間段已經大於ping設置間隔時間
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
//獲取真實對象
//執行sql語句,然後關閉
//如果拋出異常說明ping有問題
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).close();
}
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
result = true;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
}
} catch (Exception e) {
log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
//關閉連接對象
try {
conn.getRealConnection().close();
} catch (Exception e2) {
//ignore
}
result = false;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
}
}
}
}
return result;
}
}
4.1、總結
- 這裏關鍵方法就是popConnection,從連接池中取出連接對象,pushConnection將用完連接對象放回連接池,pingConnection判斷連接對象是否有效