Mybatis + Druid 数据库连接池的连接缓存原理
Mybatis 默认数据库连接池缓存原理
Mybatis 默认数据库连接池缓存原理,和为什么要使用连接池, 网站上较多文章可以通过这个链接查看https://www.cnblogs.com/yougewe/articles/10061276.html
Mybatis+Druid 连接池原理
在Druid中定义了DruidDataSource
类来维护数据库连接池状态,定义了connections
数组来保存已经建立的数据库连接。
private volatile DruidConnectionHolder[] connections;
driud 将与数据库的物理连接(最原始的数据库
Connection
)保存在DruidConnectionHolder
中其中,DruidConnectionHolder
保存了数据库连接Connection
和连接监控信息。
Druid 连接池初始化和连接获取
Mybatis+Druid 数据库连接池一起使用时,首先是通过SqlSessionFactory.openSession获取一个SqlSession的实例,执行数据库操作时又通过SqlSession获取对应的Mapper,调用Mapper的方法,实际数据库连接Connection
的创建是在执行数据库操作的时候建立的。获取数据库连接的逻辑是在DruidDataSource中
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
//按配置初始化数据库连接池
init();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis); //从数据库连接池中获取数据库连接
}
}
在调用DruidDataSource.geConnection
时首先会调用init方法,init方法按照mybatis的配置进行数据库连接池的初始化,初始化之后getConnectionDirect
再从连接池中返回数据库连接。
其中init方法中创建数据库连接池的部分逻辑如下:
//..校验配置信息合法性,通过配置信息创建加载驱动类
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); //通过读取的配置创建于数据库的连接
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); //用DruidConnectionHolder保存的连接和连接信息
connections[poolingCount++] = holder; //将DruidConnectionHolder保存到连接池中
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
init() 方法中按配置的初始池连接数初始化数据库连接,前后部分还的校验配置的和合法性、统计活跃的连接和僵尸连接、设置连接状态回调处理器等操作,这写内容不展示。
然后下一步getConnectionDirect方法就是直接从连接池中获取上一步初始化的数据库连接,关键逻辑如下:
if (maxWait > 0) {
holder = pollLast(nanos);//从connections数组中获取数据库连接
} else {
holder = takeLast();
}
if (holder != null) {
activeCount++;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
}
getConnectionDirect
的主要工作就是通过pollLast
方法从connections
数组中获取数据库连接,并返回,实现从连接池中获取数据库连接的效果。
Druid 连接回收重用
在Mybatis中SqlSession在非线程安全的,不能被共享的, 所以在每一次业务调用触发并返回响应之后,就应该调用sqlSession的close()方法关闭它。如果不使用数据库连接池调用sqlSession.close()方法就会连通对应的数据库连接一同关闭掉。
SqlSession.close的调用链
//1. DefaultSqlSession.java
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
//2. CachingExecutor.java
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
//3. JdbcTransaction.java
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close(); //关闭数据库连接
}
}
如代码所示最后调用connection.close()
了,在没有数据库连接池的情况下与数据库的连接connection
就是关闭了,但是在使用Druid数据库连接池时connection
的是com.alibaba.druid.pool.DruidPooledConnection
类的实例,DruidPooledConnection
类实现了java.sql.Connection
接口,并重写了close()方法,在重写的close方法中并没有直接关闭掉数据库连接,而是将数据库连接回收到了DruidDataSource中的connections数组中。回收方法是DruidDataSource的recycle方法中,处理逻辑如下:
lock.lock();
try {
activeCount--;
closeCount++;
result = putLast(holder, currentTimeMillis);//调用putLast方法将DruidConnectionHolder回收到connections中
recycleCount++;
} finally {
lock.unlock();
}
putLast逻辑如下:
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis;
connections[poolingCount] = e;//将当前关闭sqlSession对应的数据库连接DruidConnectionHolder放回connections中
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = lastActiveTimeMillis;
}
notEmpty.signal();
notEmptySignalCount++;
return true;
}
就是这样的流程在使用Mybatis+Druid的时候关闭SqlSession
,对应的连数据库连接不会直接关掉,而是重新保存到DruidDataSource.connections
中,在下一次执行数据库操作时可以直接从DruidDataSource.connections
中直接获取已建立的连接,从而优化创建数据库连接带来的效率影响。