源码中经常会出现很多的设计模式来实现组件与组件之间的解藕,比如工厂模式(常见)用于对象的创建。 在现在信息爆炸的时代,耐心的咀嚼源码,体会其设计思想对我们的思维有很大的提升与帮助
Mybatis池化数据源的设计
使用代理模式的PooledDataSource来增强java.sql中的Connection类功能,
- 使其能够统计checkoutTime
- 使其能够在close的时候,能够与dataSource进行通信,调用dataSource.pushConnection()方法将自身进行关闭或者包装成新的连接。
- 再每次对Connection的方法调用时都先通过checkConnection()方法来检测自身的状态,来避免非法访问数据源。(PoolConnection会在DataSource调用forceCloseAll(),pushConnection())
通过单独的PoolState来进行数据源的管理
- 统计如requstConut、badConnectionCount,accumulatedWaitTime等数据
- 通过使用两个List来存储idleConnections和activeConnections。
池化数据源的两个关键操作
PooledDataSource类中popConnection和pushConnection是池化数据源的核心操作方法(对应着getConnection()和close,通过state(PoolState)对象来判断数据池当前的状态决定是否要增加数据源或减少数据源。
pushConnection()用于关闭一个连接,在一个连接自身调用了close的时候会由pooledConnection的invoke方法进行触发。
protected void pushConnection(PooledConnection conn) throws SQLException {
lock.lock();
try {
state.activeConnections.remove(conn);
// 连接是否有效
if (conn.isValid()) {
// 池中空闲数量是否小于设定的最大空闲连接数量, 并且connectionType是否一致,这里的connectionTypeCode
// 是由url + user + password来决定的 以此来判断数据源和连接是否指向同一个库
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
// 如果设置了不自动提交,就先回滚一下,中断该连接的操作,因为要关闭了。
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 创建一个新的连接,因为空闲连接池还没满,需要让池中的连接保持在poolMaximumIdelConnections
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.");
}
condition.signal();
} else {
// 如果池中空闲数据已满则直接关闭,其实这里代码写的不太好? 在一段if-else中出现了5行相同的代码
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中记录一下
state.badConnectionCount++;
}
} finally {
lock.unlock();
}
}
popConnection用于创建一个连接,在被调用DataSource.getConnection()时触发,用于从池中拿取连接。
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) {
lock.lock();
try {
// 如果池中有空闲连接,直接从头拿取一个即可
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 {
// 否则当前活跃连接数少于配置的最大活跃连接数,就添加创建新连接
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 {
// 否则不创建新连接了,开始检查池中最老的活跃连接的检查时间
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// 如果该连接的检查时间已经过期了,先进行一波统计,再将最老的连接重新用pooledConnection包装一次
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 {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
condition.await(poolTimeToWait, TimeUnit.MILLISECONDS);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
// set interrupt flag
Thread.currentThread().interrupt();
break;
}
}
}
}
// 此时已经走出循环,拿到了一个pooledConnection
if (conn != null) {
// ping to server and check the connection is valid or not
// 检查一下连接能否使用
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 {
// 如果连接不能使用,就统计badConnection
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
// 如果拿到的坏连接太多了,就抛出SQL异常,无法拿到一个可以使用的数据库
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.");
}
}
}
} finally {
lock.unlock();
}
}
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;
}