什么是连接池
存储连接的容器
为什么要使用连接池
由于每次执行sql语句都去创建connection和使用完销毁耗时,使用连接池来管理连接,提高连接的使用率(跟java线程池的概念类似)
源码分析
mybatis提供三种数据库连接池的选择
配置文件的enviroment节点的datasource子标签
1. type="POOLED",对应org.apache.ibatis.datasource.pooled.PoolDataSource连接池;
2. type="UNPOOLED, 对应org.apache.ibatis.datasource.unpooled.UnpooledDataSource,表示不使用连接池,每次执行sql语句都新建连接,使用完后销毁连接;
3. type="JDNI", 对应org.apache.ibatis.datasource.jndi.JndiDataSourceFactory连接池,表示使用外置连接池。
一般常用设置type="POOLED"使用内置PoolDataSource连接池
加载mybatis配置文件获取输入流,通过SqlSessionFactioryBuilder创建SqlSessionFactory的过程中对配置文件解析每个节点,包括environment,封装成Configuration对象,从SqlSessionFactory中获取SqlSession对象时将Configuration对象传递给了SqlSession对象。
断点调试可以看到,SqlSession对象Configuration属性中的environment属性中的dataSource对象是一个PooledDataSource实例,也就是说,连接池在加载配置文件创建SqlSessionFactory的时候就已经初始化,等到SqlSession对象或mapper代理对象执行sql的时候,再从连接池中去拿空闲连接,使用完后再归还连接。
org.apache.ibatis.datasource.pooled.PoolDataSource源码分析
PooledDataSource中定义了一个PollState类型的state对象,维护了两个List
获取连接
方法:private PooledConnection popConnection(String username, String password) throws SQLException;
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) {
// 读写PoolState对象中的idleConnections空闲连接,为保证线程安全,加同步锁
synchronized(this.state) {
PoolState var10000;
if (!this.state.idleConnections.isEmpty()) {
// 如果idleConnections空闲连接数连接不为空,则将第一个空闲连接拿出来
conn = (PooledConnection)this.state.idleConnections.remove(0);
// 省略代码
} else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
// idleConnections为空,没有可用的空闲连接,PoolState对象中的activeConnections活动连接数小于最大活动连接数(默认10)
// 创建新的连接(同时将新连接注册到activeConnections)
conn = new PooledConnection(this.dataSource.getConnection(), this);
// 省略代码
} else {
// idleConnections为空,没有可用的空闲连接,并且PoolState对象中的activeConnections活动连接数大于或等于最大活动连接数
// 从activeConnections中拿到第一个连接(最早使用的连接)
PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
// 省略代码
// 如果连接超时,删除该连接
this.state.activeConnections.remove(oldestActiveConnection);
// 省略代码
// 然后就可以创建新的连接来执行任务
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
// 省略代码
} else {
// 等待
}
}
if (conn != null) {
if (conn.isValid()) {
// 如果连接不为空并且有效
// 省略代码
//每一个连接获取唯一的类型hash值,由url\username\password计算得到
conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
// 设置最后一次检查超时和最后一次使用时间戳(刚获取的连接都设置当前系统时间)
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
// 将该连接注册到activeConnections活动连接列表中
this.state.activeConnections.add(conn);
// 省略代码
} else {
//连接不为空但是无效
// 省略代码
// 坏连接数+1,并将该连接置空
++this.state.badConnectionCount;
++localBadConnectionCount;
conn = null;
// 如果坏连接数超过最大空闲连接数+最大可容忍坏连接数,则抛出异常
if (localBadConnectionCount > this.poolMaximumIdleConnections + this.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.");
}
}
}
}
}
//最后检查一次,如果连接为空抛出异常
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.");
} else {
return conn;
}
}
归还连接
方法:protected void pushConnection(PooledConnection conn) throws SQLException;
protected void pushConnection(PooledConnection conn) throws SQLException {
// 加锁
synchronized(this.state) {
// 将已使用完的连接从activeConnections中移除
this.state.activeConnections.remove(conn);
if (conn.isValid()) {
PoolState var10000;
if (this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {
// 如果idleConnections空闲连接数小于最大空闲连接数(默认5个),并且连接属性相同(conn.getConnectionTypeCode(),得到每个pooledConnection的属性,使用url+username+password,进行hashcode算法,获得的int类型数据)
// 省略代码
// 使用该连接初始化一个新的连接
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
// 将该连接注册到idleConnections空闲连接中
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.");
}
this.state.notifyAll();
} else {
// 省略代码
// 关闭该连接的原连接,并使该连接无效
conn.getRealConnection().close();
conn.invalidate();
}
} // 省略代码
}
}
总结
popConnection
获取连接时,会从连接池的空闲连接列表中进行获取空闲连接,
如果连接池中没有可用的空闲连接, 判断活动连接列表中连接数是否达到上限,
如果未达到, 则创建新的连接
如果已经达到上限,判断活动连接列表中最先使用的连接是否过期,
如果过期,则活动连接列表删除该连接,然后创建新的连接
如果没有过期,则需要等待设置的等待时间,然后再进行循环获取.
获取连接后,对连接进行校验,会调用pingConnection()方法,如果连接不可用则为坏连接,并将连接置为null,继续进行获取连接,当坏连接的数量>最大空闲连接数量+3时,抛出异常,
最后对连接进行!null判断,如果获得的连接为空,则抛出异常.
pushConnection
将连接从活动连接列表中移除,
如果空闲连接列表中连接数未达到上限,并且连接属性和预期一致,则使用该连接初始化一个新连接,将新连接注册到空闲连接列表,并使原连接无效,
否则直接关闭原连接
PoolConnection
org.apache.ibatis.datasource.pooled.PooledConnection实现了InvocationHandler接口,用户拿到的是PoolConnection的代理对象,实现invoke方法中,如果执行close()方法,并不是将连接关闭,而是调用pushConnection方法将连接归还给连接池
class PooledConnection implements InvocationHandler {
// ...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("close".equals(methodName)) {
// 当执行sqlSession.close();关闭各种资源,其中就会关闭数据库连接,如果是POOLED方式使用连接池,关闭连接的方法在这里被拦截执行归还连接的方法
this.dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
this.checkConnection();
}
return method.invoke(this.realConnection, args);
} catch (Throwable var6) {
throw ExceptionUtil.unwrapThrowable(var6);
}
}
}
}