mybatis数据库连接池分析介绍
一、数据库连接池
1、定义
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。 (百度百科)
2、核心原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
2、java常用连接池
c3p0、dbcp、druid 等
二、mybatis数据库连接池
1、核心工作流程
mybatis所有的数据库操作都是通过执行器去执行的,在基础执行器BaseExecutor获取连接时最终会调用PooledDataSource的popConnection方法获取一个数据库连接。当然使用mybatis自带的数据库连接池的前提是mybatis配置的数据源类型配置的是POOLED类型,如果是别的类型或者换成了别的数据源是走不到PooledDataSource的popConnection方法的。
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
【1】获取数据库连接的核心类和方法
从连接池获取连接的方法
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) {
synchronized (state) {
// 执行完,connection是何时挪到idleConnections集合中的--在连接关闭的时候
// 如果空闲连接列表中存在空闲的连接,直接拿出来使用
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
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();
// 最早的活动连接当前以执行的时间 > 最大检出时间(20S)
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
// 池子里面超时的连接个数+1
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) {
// 池子中阻塞的数量+1
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);
// 请求数量+1
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;
// 本次请求连续8次都连接失效抛异常
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;
}
【2】关闭数据库连接时的操作
mybatis采用了jdk动态代理的方式在connection.close方法执行的时候实际上是调用的PooledDataSource的pushConnection方法,该方法没有直接关闭连接而是将连接加入到空闲连接的集合中以便后面复用。
/**
* 功能描述: sql操作完后,关闭连接时的操作
*
* @param conn
* @return void
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
// 从活跃的连接列表中移除
state.activeConnections.remove(conn);
// 判断连接是否有效
if (conn.isValid()) {
// 空闲的连接数量 < 最大空闲数量
if (state.idleConnections.size() < poolMaximumIdleConnections
&& conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// 累计sql执行检出时间
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++;
}
}
}
怎么看它采用的是动态代理呢?PooledDataSource中getConnection方法拿到的是连接的代理对象,代理对象去执行connection接口的close方法的时候并不是真正执行的close方法而是执行的PooledConnection的invoke方法
public Connection getConnection() throws SQLException {
// 获取的是proxyConnection
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
invoke方法中针对Connection接口中的方法进行代理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
// 如果是关闭连接没有直接close
dataSource.pushConnection(this);
return null;
} else {
// 不是关闭连接而是connection的其他操作
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);
}
}
}
2、图解连接池工作流程
今天没时间了,后面再画下更新
三、总结
mybatis数据库连接池我们可以看到每次获取连接时一上来就把整个池子给锁了,这样的性能肯定有所降低,可以用常用的主流数据库连接池方案替换。