使用mybatis自带的连接池获得数据库连接,对数据库进行操作.
获得连接和关闭连接都是用mybatis自带的连接池,节省资源.
获取连接
获得连接.在使用mapper进行数据库操作时,会使用JdbcTransaction获得连接.
JdbcTransaction
protected DataSource dataSource;
Connection connection = dataSource.getConnection();
获取连接.PooledDataSource.popConnection().
while (conn == null) {} 当获得的连接不为空时返回,否则一直执行.
1.当空闲连接不为空时,从空闲连接中获取连接,并将连接从空闲连接中去除.
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.");
}
2.当空闲连接为空时,判断正在使用的连接的数量是否小于设置的连接池最大使用连接数,如果小于,则新建连接
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() + ".");
}
3.当空闲连接为空,正在使用连接等于连接池最大连接时,不能创建新的连接,只能等待旧的连接释放
获得最先使用的连接,判断被检出的时间,即使用的时间是否超过设置的最大被检出时间.
如果大于.
声明逾期连接数量 +1
逾期连接累计被检出时间+ 此连接被检出时间
累计被检出时间 + 此连接被检出时间
从使用的连接中移除此连接
如果连接不是自动提交,则回滚
以旧的连接的数据创建新的连接
将旧连接状态置为false.
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() + ".");
}
4.当空闲连接为空,使用连接等于最大连接,最先使用的连接没有过期,则只能等待连接过期
等待设置的等待时间
累计等待时间 += 等待了的时间
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;}
5.当获得连接,且连接部位null,则跳出了while循环
5.1.判断连接是否可用
如果可用,对连接进行参数设置
设置连接代码,是由url+username+password,进行hashcode获得的int类型的值.
设置被检出时间,和最后的使用时间,都是当前时间.
添加到使用的连接中
请求连接的计数器+1
累计获得请求耗费的时间 += 此方法执行开始到此行代码执行时耗费的时间.
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;
}
5.2.如果连接不可用.
坏连接数量+1
本地坏连接数量+1
连接位置null,会继续获取连接,
else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
5.2.2.如果连续获取连接都是坏连接.且坏连接的数量>设置的空闲连接的最大值+3
则报错,扔出异常
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.");
}
6.获得连接后,对连接进行非空判断,
如果为空,则抛出异常
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.");
}
7.返回获得的连接,pooledConnection.
return conn;
从上述方法可以看出,
- 获取连接时,会从连接池进行获取,
- 如果连接池中没有连接,会判断使用的连接数量是否大于设置的总数
- 如果小于总数,则创建新的连接
- 如果不小于总数,则需要复用连接,
- 判断最先使用的连接是否过期,如果过期,则直接以过期的连接创建新的连接进行使用,过期的连接置为invalid
- 如果没有过期的连接,则需要等待设置的等待时间,然后在进行获取连接
- 如果连接为null,则会一直进行循环获取.
- 获取连接后,对连接进行校验,会调用pingConnection()方法
- 如果连接不可用则则为坏连接,并将连接置为null,继续进行获取
- 当坏连接的数量>最大空闲连接数量+3时,抛出异常,
- 最后对连接进行!null判断,如果获得的连接为空,则抛出异常.
关闭连接
- 单独的mybatis框架使用的是SqlSession的默认实现类,DefaultSqlSession,
- 这个类不是线程安全的,他有成员属性,而且有方法可以对这个属性进行修改,有的方法需要使用这个属性,就造成,如果是多个线程同时使用,会不安全.
- 所以,我们单独使用mybatis时,是直接从Factory中new一个SqlSession,然后使用完成后,要关闭.每次都是用新的来操作.
- 当使用spring和mybatis整合的时候,可以使用SqlSessionTemplate来实现对session的代理.
- 当SqlSessionTemplate交给spring管理的时候,会在全局创建一个session,单例的,所有的dao公用同一个session.
- 因为SqlSessionTemplate是线程安全的,
- 且,SqlSessionTemplate在代理方法执行完成后,会有一个session.close().的操作.
具体的源码及执行流程,以后会梳理.
当使用完连接后,要进行关闭,一直搞不明白,为什么需要关闭,对于这些不是很理解.
而且,关闭的话,是关闭session还是关闭connection,对于两者的关系,还在梳理中.
使用spring整合的时候,不需要手动关闭session,可能是底层有类会对session进行关闭.
梳理完mybatis后,要梳理spring的源码,把和mybatis的整合看一下.
当关闭连接时,会使用代理,判断调用的方法,如果是ConnectionClosed(),就会调用pushConnection()方法,将连接放回连接池中,并不是关闭连接.如果是别的方法,则还是会正常的执行.
class PooledConnection implements InvocationHandler
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 (method.getDeclaringClass() != Object.class) {
// issue #578.
// toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
PooledConnection实现了InvocationHandler接口,方法直接使用此类进行代理.
当调用invoke方法后,会根据方法名进行判断,如果是close()方法,则将连接放回连接池,不会直接关闭连接.
PooledDataSource.pushConnection
protected void pushConnection(PooledConnection conn) throws SQLException {
1.将连接从使用连接移除
state.activeConnections.remove(conn);
2.判断连接是否可用
if (conn.isValid()) {
2.1.当空闲连接的数量小于设置的最大的空闲连接的数量,且,连接的连接类型代码和记录的连接类型代码相同时
//conn.getConnectionTypeCode(),是每个pooledConnection的属性,使用url+username+password,进行hashcode算法,获得的int类型数据,每个conn的typecode应该是唯一的,typecode属性实在popConnection时设置的.
//expectedConnectionTypeCode是pooledConnection的属性,在创建pooledConnection时赋值
//同一个DataSource的typecode应该是一致的,通过同一个DataSource获得的connection的typecode也是相同的
//typecode = ("" + url + username + password).hashCode()
//同一个连接设置获得的typecode是相同的
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();
}
//此处是真正的关闭连接,使用的是connection,不是pooledConnection.
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
}
3.当连接不可用时,直接丢弃连接,不把连接放入到连接池中
else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
总结,pushConnection
- 用户获得连接时获得的是poolConnection中的proxyConnection,即为代理连接.
- return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
- ProxyConnection在pooledConnection中创建,是pooledConnection的属性,在pooledConnection创建时即创建代理.
- 且pooledConnection中还有一个属性是Connection,
- 当调用close()方法时,会使用代理,判断方法,如果是close(),则将连接放回连接池,如果是别的方法,则执行connection的方法
- return method.invoke(realConnection, args);
测试连接
在pop和push时都会对连接的可用性进行判断,会调用pingConnection方法.
PooledDataSource.pingConnection();
protected boolean pingConnection(PooledConnection conn) {
//判断是否开启了验证,这个属性是在配置文件中设置,
if (poolPingEnabled) {
//poolPingConnectionsNotUsedFor是配置文件中的属性.
//conn.getTimeElapsedSinceLastUse() = System.currentTimeMillis() - lastUsedTimestamp
//即当前时间 - 连接创建的时间.如果> 我们配置文件中设置的间隔时间,就进行测试,如果不大于,就不测试
if (poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
//执行配置文件中的sql语句,进行测试
Connection realConn = conn.getRealConnection();
Statement statement = realConn.createStatement();
ResultSet rs = statement.executeQuery(poolPingQuery);
rs.close();
statement.close();
log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
//如果测试不成功,则报错,并把连接关闭,并返回false
//如果为false则表示连接不可用,在pop中,会将conn=null,会继续获取连接
//在push中,开始时就从active中移除了conn,如果conn不可用,就不把conn放入idle中.
catch (Exception e) {
log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
conn.getRealConnection().close();
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());