前言
回顾下之前映射文件的解析,最终会生成一个sqlSource,而一个sqlSource中有一个sqlNode的集合,而sqlNode封装的就是每一个sql脚本,同时不同动态标签对应不同的sqlNode。而sqlSource提供了一个getBoundSql的方法,返回的是一个可以由jdbc直接执行的sql语句。对sql中#{}和${}的解析在对应的sqlSource中,#{}由RawSqlSource而${}由DynamicSqlSource解析。本篇文件我们就来看下具体sql的执行流程。
MybatisSql的执行流程
阅读源码之前我们先看下mybatis的一个执行过程:
我们都知道sql的执行都是从sqlSession开始的,提供最顶层的api接口,而执行器Ececutor是比较重要的一部分,主要用于缓存的查询和动态sql的生成。最终执行数据库查询是在StatementHandler中。下面我们看下Executor的一个继承体系。
- CachingExecutor:主要是用来处理二级缓存的执行器,如果没有二级缓存就会通过委托模式把具体的执行过程给BaseExecutor执行。下
- BaseExecutor:主要用来处理一级缓存,如果没有缓存就将查询任务交给他的实现类去执行。
清楚了这些之后我们开始进入具体的源码阅读过程。因为SqlSession是一个接口,因此我们从我们从它的默认实现类DefaultSession中去找到查询的入口,找到DefaultSqlSession中的selectList方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从configuration通过id获取我们解析好的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用执行器的查询方法
// RowBounds是用来处理分页逻辑,在内存中分页
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
而这个执行器是从构造方法传入的,我们可以从DefaultSqlSessionFactory的openSessionFromDataSource方法看到其实执行器是由configuration创建出来的
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取数据源环境
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 获取JdbcTransaction或者ManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建Executor执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
接着我们可以再跟进去看下configuration是怎么去创建执行器的
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 批处理执行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 可重复用执行器
executor = new ReuseExecutor(this, transaction);
} else {
// 简单执行器
executor = new SimpleExecutor(this, transaction);
}
// 如果开启缓存(默认是开启的),则使用缓存执行器
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 插件执行
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
在这里我们就可以清楚的看到mybats是怎么创建具体的执行器,通过我们配置的执行器类型和是否开启缓存去判断使用哪个执行器。我们接下来带大家看的是CachingExecutor的一个处理过程。下面是CachingExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取mybatis的二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 从二级缓存查询数据
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 如果二级缓存没有数据就去查询数据库
if (list == null) {
// 委托给BaseExecutor执行
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 委托给BaseExecutor执行
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
从这段代码我们可以很清晰的看到二级缓存的一个执行流程,就是先从缓存查,如果没有查到就去委托BaseExecutor去查询,下面我们看下BaseExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 先从一级缓存中查询数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 若一级缓存中没有查询到数据,那么就查询数据库数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
从这段代码我们就能看到mybatis对一级缓存的使用,其实一级缓存localCache里面封装的就是一个map,所以一级缓存就是通过map实现的。最终如果没有从缓存中查询到数据,那么mybatis就会去查询数据库。接着我们看下具体的queryFromDatabase方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
通过doQuery方法查询数据库,查询完之后加到一级缓存中。而这个doQuery方法是BaseExecutor子类实现的方法。我们这次看的是SimpleExecutor的doQuery方法。
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
resultHandler, boundSql);
// 设置参数
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行SQL语句,并且映射结果集
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
这里通过configuration获取StatementHandler,我们知道jdbc有多种statement,而不同的statement有不同的处理,因此mybatis通过创建RoutingStatementHandler的方法去解决这个问题。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建路由功能的StatementHandler,根据MappedStatement中的StatementType
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
其实这个RoutingStatementHandler里面什么都没有做,只是在创建的时候确定了具体的statementHandler。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
而最终是通过类似委托的方式,调用具体的statementHandler去执行。上面的query方法就是通过具体的statementHandler去执行的,这里我们看下PreparedStatementHandler的query方法。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行PreparedStatement,也就是执行SQL语句
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
这里我们就看到了这个execute就是真正的查询数据库的方法。然后通过DefaultResultSetHandler去处理结果集并返回。至此我们mybatis的执行流程的阅读就结束了,其实整个流程也是比较清晰的。
最终,我们通过这三篇文章我们把mybatis的源码主流程基本上都阅读完毕了,其中还有一些其他的分支,比如,通过注解加载的过程,还有最后结果集的封装过程,大家都可以自行往下阅读。mybatis结束之后,接下来会跟大家分享下spring的源码阅读,谢谢观看。