在数据库连接池的使用过程中,一直有一个疑问:
在数据库连接的使用过程中,必须要满足独占性要求(其他线程不能访问),使用完成后关闭数据库连接,那么问题来了,数据库都已经连接关闭了,那其他线程又怎么使用呢?
带着这样的疑问,我们一起阅读了“org.apache.commons.dbcp2.BasicDataSource”的实现源码,发现从数据源中获取连接的调用顺序依次如下:
1. BasicDataSource.getConnection();
2. PoolingDataSource.getConnection();
3. GenericObjectPool.borrowObject();
4. PoolableConnectionFactory.makeObject()
现在连接的产生就在于PoolableConnectionFactory是如何产生数据连接的(makeObject),源码如下(为省略篇幅,所以代码为节选):
// _connFactory为DriverConnectionFactory的实例,创建数据库连接的方法为Driver.connect(),所以这是真正的数据库连接
Connection conn = _connFactory.createConnection();
// 如果此连接创建失败,后面的连接也没用了
if (conn == null) {
throw new IllegalStateException("Connection factory returned null from createConnection");
}
// 初始化配置代码略
// 返回连接的包装结果
PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName,
_disconnectionSqlCodes, _fastFailValidation);
return new DefaultPooledObject<>(pc);
从这里可以看出,在项目的实际运行过程中,我们从数据库连接池获取的连接类型都是PoolableConnection,其类的声明如下:
public class PoolableConnection extends DelegatingConnection<Connection>
implements PoolableConnectionMXBean {
}
而DelegatingConnection实现了Connection接口,所以PoolableConnection也实现了Connection接口。
public class DelegatingConnection<C extends Connection> extends AbandonedTrace
implements Connection {
}
现在,我们关注重点在于PoolableConnection的close()方法,如下:
public synchronized void close() throws SQLException {
// 只有这里才是真正的关闭,原生Connection.close()
if (isClosedInternal()) {
return;
}
boolean isUnderlyingConectionClosed;
try {
// 获取真正的数据库连接对象,判断原生数据库连接是否真的关闭了
isUnderlyingConectionClosed = getDelegateInternal().isClosed();
} catch (SQLException e) {
// 销毁数据库连接池中的无效对象
try {
_pool.invalidateObject(this);
} catch(IllegalStateException ise) {
// 如果连接池已关闭,则销毁对象
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
}
throw new SQLException("Cannot close connection (isClosed check failed)", e);
}
/* 在数据库连接校验完成之前,不能关闭连接
* 数据库连接已归还到池中的时候,也不能关闭连接
*/
if (isUnderlyingConectionClosed) {
try {
_pool.invalidateObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch (Exception e) {
throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
}
} else {
try {
_pool.returnObject(this);
} catch(IllegalStateException e) {
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw new SQLException("Cannot close connection (return to pool failed)", e);
}
}
}
从上面的代码可以看出,所谓的关闭只是把数据库连接还会给数据库连接池,并没有真正地关闭数据库连接(调用原生数据库连接的关闭方法),大量的逻辑都在判断原生连接是否真的有效(未关闭状态)。
结论
我们所使用的数据库连接执行的关闭,并不是真正意义上的断开数据库连接,而只是做了一个可用性标记(如额外添加一个boolean字段,用于判断状态),从而利于连接池对有效连接的判断。
也因为这样的原因,数据库连接池也有缺点,如在开发过程中,如果代码设置的初始化连接数量过大,开发服务器的真实连接数量可能不够用,从而出现没有可用数据库连接的错误。