Mybatis与数据库建立连接
由于mybatis是在jdbc的基础上进行封装的, 所以jdbc执行流程 获取连接->创建statements->resultSet这些步骤mybatis都是存在的, 本篇学习一下Mybatis获取Connection的步骤
jdbc执行流程图
Mybatis初始化
由于我这里是在springboot中整合使用的mybatis并且在springboot启动类中加了@MapperScan, 所以Mybatis的配置是springboot通过yml并且自动配置的,
我们可以看到在MybatisAutoConfiguration这个类中, 伴随着springboot的启动, 这个类中第一个执行的方法就是sqlSessionFactory, 顾名思义, 这个方法是创建一个工厂类来管理sql会话, 源码如下
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 这里就是我们这次的重点, 配置DataSource
factory.setDataSource(dataSource);
/**
* 这里我们省略其他的配置
*/
return factory.getObject();
}
这里我们追踪一下java的方法栈, 找一下这个dataSource是从哪里得到的
首先追踪到了ConstructorResolver这个类中的531行
这个argsToUse是一路传下去的我们使用的DataSource在这里定义
我们继续追踪一下这个argsHolder, 发现在这个类中的509行会进入一个createArgumentArray得到arguments属性, 到了最后我们会进入到spring的AbstractBeanFactory中
由于是自动装配的, 我们并没有指定数据库的连接池, 所以springboot会给根据dataSource这个name来自动装配一个数据库连接池Hikari
这样一来我们就清楚的明白了以下几点:
- 我们并没有配置数据库的连接池类型, 可是为什么用到了连接池
- Mybatis的SqlSessionFactory是什么时候生成的(在Builder中92行打断点之后可以查看java的方法栈, 可以看见是在第一次springboot @Resouce mapper的时候调用的)
Hikari连接池的启动
既然我们知道springboot的默认配置是使用Hikari连接池来实现数据库的连接的, 那么问题就很好解决了, 直接上一个断点
(ps: 其实细心的朋友应该很早就发现了, 因为每次在第一次执行sql的时候console面板都会弹出来下图这么一句话)
下面是Hikari的getConnection方法
private volatile HikariPool pool;
// xxxx
HikariPool result = pool;
// 不难发现这个线程池采用的是懒汉的单例模式, 并且使用的是双检锁
// 第一次获取的时候, pool为空就实例化一次
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
validate();
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
}
catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException) pie.getCause();
}
else {
throw pie;
}
}
LOGGER.info("{} - Start completed.", getPoolName());
}
}
}
每次都从线程池中取出一个数据库连接, 就不需要每次都创建, 从而节省时间提高性能
Mybatis获取数据库连接
在jdbc的流程中, 创建数据库连接是先于创建statement的, 所以我们找到SImpleExecutor中的prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 可以看见在这里我们获取了connection, 而一路下去则会发现, 这个连接是在连接池中取出的
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
后记
学会善用idea在调试时左下角的java方法调用栈