主要记录Mybatis数据库连接池实现原理、如何使用连接池来管理数据库连接的、连接池如何向外提供数据库连接、当外部调用使用完成之后是如何将数据库连接放回数据库连接池的。
有前面的相关文章的铺垫、这里就不再从Mybatis数据库相关信息的初始化以及何时创建一个真正的数据库连接并且向外提供使用的。这两方面的过程可以参见Mybatis深入之DataSource实例化过程 和Mybatis深入之获取数据库连接 两篇文章。
了解Mybatis数据库连接池如何配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
environment>
environments>
指明使用Mybatis自带数据库连接池
指明使用JDBC形式管理事务、参见 Mybatis深入之事务管理 当Mybatis初始化完成之后、根据上面的配置以及前面的文章知道、 环境中的DataSource实例是PooledDataSource、环境中的Transaction实例是JdbcTransaction。
Mybatis深入之获取数据库连接 中知道、当执行第一个sql语句时才会尝试获取数据库连接详细的可以看前面的文章、这里直接上获取数据库连接的关键代码:
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
当尝试获取数据库连接的时候会使用数据库连接池功能、详细获取数据库连接的代码Mybatis深入之获取数据库连接 有简单介绍。
在想要了解数据库连接池原理之前需要了解一个Mybatis用来存放数据库连接池状态的类:PoolState、PooledConnection以及PooledDataSource类属性以及之间的关系:
List idleConnections
用于存放空闲状态的数据库连接。List activeConnections
用于存放活动状态的连接、也就是正在使用的数据库连接。PooledConnection内部持有一个PooledDataSource、同样在PooledConnection被构造时实例化PooledDataSource、其中有两个属性private long createdTimestamp;
用来标识当前PooledConnection的创建时间和最后使用时间、用来判断此连接是否过期。
private long lastUsedTimestamp;
PooledDataSource 简单的线程安全的数据库连接池、对外提供数据库连接池功能。
从上面可以知道程序中使用了数据库连接池之后、获取的数据库连接是从PooledDataSource中方法popConnection(String username, String password)获取的、获取代码:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
//恒成立、直到上面定义的PooledConnection被正确实例化或者程序异常中止
while (conn == null) {
//synchronized (state)是保证PooledDataSource是一个线程安全的数据库连接池的原因。
synchronized (state) {
//如果当前数据库连接池中有空闲状态的数据库连接、则直接取出一个作为当前方法执行结果返回。
if (state.idleConnections.size() > 0) {
// 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
//创建一个内部持有真正数据库连接的PooledConnection
conn = new PooledConnection(dataSource.getConnection(), this);
@SuppressWarnings("unused")
//used in logging, if enabled
//真正的数据库连接
Connection realConn = conn.getRealConnection();
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()) {
oldestActiveConnection.getRealConnection().rollback();
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
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();
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.");
}
}
}
}
}
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;
}
总结上述步骤
下面是从网络上摘抄的一个流程图:
通过PooledConnection.getProxyConnection()来获取最终数据库连接。
public Connection getProxyConnection() {
return proxyConnection;
}
这里只要弄清楚proxyConnection是什么就行,在上面一节获取PooledDataSource时候的popConnection方法中知道PooledConnection是在这个方法里面创建的、调用的都是相同的PooledConnection构造方法:
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);
}
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
所以最终返回的是真正的Connection的代理类PooledConnection。至于为什么要这样做、接着看。
不使用数据库连接池时、正常使用数据库连接的情况下、当使用完毕之后我们就会调用其close()方法来关闭连接、避免资源浪费。但是当使用了数据库连接池之后、一个数据库连接被使用完之后就不再是调用其close方法关闭掉、而是应该将这个数据库连接放回连接池、那么我们就要拦截Connection.close()方法、将这个Connection放回连接池、而不是关闭。
使用动态代理的方式实现上述功能、PooledConnection实现了InvocationHandler接口、并提供了invoke方法的实现。当调用Connection的方法时会执行PooledConnection的invoke方法:
/*
* Required for InvocationHandler implementation.
* * @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[])
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
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);
}
}
}
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包含真正Connection重新创建一个PooledConnection并放到连接池中
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++;
}
}
}
到这里、数据库连接池原理就结束了。
Mybatis的数据库连接池简单、线程安全。当与spring结合的时候通常也可以使用第三方数据库连接池、将有关数据库连接、事务都交由spring去管理。
更多内容:Mybatis 目录