聊聊 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()来完成的,具体就是从活跃连接集合中删除这个连接,然后放入空闲连接数集合中并把当前连接设置为无效。
评论