聊聊 Mybatis 的数据源之 PooledDataSource
上篇文章中我们介绍了 Mybatis 的数据源模块的 DataSource 接口和它对应的实现类 UnpooledDataSource、PooledDataSource,这篇文章详细介绍一下 PooledDataSource
PooledDataSource 使用了数据库连接池可以实现数据库连接池的重复利用,还能控制连接数据库的连接上限,实现数据库连接的统一管理,缓存数据连接信息还能防止流量突发连接数据库不及时
PooledDataSource 有个 PoolState 状态,PoolState 里保存着数据库连接信息 PooledConnection,PooledConnection 实现 InvocationHandler 接口,重写 invoke 方法,显然这是一个代理类,使用了 JDK 的动态代理
class PooledConnection implements InvocationHandler {
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
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);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
复制代码
我们看一看到构造方法中调用了 Proxy.newProxyInstance()方法来生成代理类,而重写 invoke 方法中如果是 close()就调用 pushConnection()方法直接把它放入连接池而不是关闭连接,其他情况调用 checkConnection()检查连接信息,代理类调用 realConnection()方法
下面就看一下 pushConnection()方法
PooledDataSource 的 pushConnection()方法
方法的功能就是把数据库连接放入连接池中
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++;
}
}
}
复制代码
从活跃连接集合中删除该连接
如果 PooledConnection 有效,并且空闲连接数小于最大空闲连接数,就利用当前 PooledConnection 创建 PooledConnection,放入空闲连接数集合中,方便下次使用,关闭当前 PooledConnection 对象的数据库连接,并对当前 PooledConnection 对象设置无效,最后唤醒其他等待的线程。如果空闲连接数大于最大空闲连接数了就关闭连接,设置当前连接无效
如果 PooledConnection 无效,badConnectionCount 加一,这个 badConnectionCount 是记录无效的数据库连接信息的
总结
本篇文章主要介绍了 PooledConnection 和 PooledDataSource 的 pushConnection()方法,PooledConnection 用到了 jdk 的动态代理,生成 Connection 的实现类的代理类,拦截的逻辑中对于 close()方法没有真正关闭,而是把数据库连接信息放入连接池中供下次再使用,数据库连接信息放入连接池的过程是通过调用 PooledDataSource 的 pushConnection()来完成的,具体就是从活跃连接集合中删除这个连接,然后放入空闲连接数集合中并把当前连接设置为无效。
评论