前言
上一篇【MyBatis源码学习】Sql解析中,我主要梳理了sql的源码解析过程,本章我通过同样的一个例子,来仔细瞧瞧sql是怎样执行下来的。主要分为三个步骤:
- 动态代理生成mapper
- sql执行
- 结果集处理
还是通过同样的例子来开始本章的源码sql执行过程跟踪。
@Test
// 程序员喜欢的风格
public void quickStart() throws IOException {
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
// 4.执行查询语句并返回单条数据
TUser user = mapper.selectByPrimaryKey(2);
System.out.println(user);
}
一、动态代理生成mapper
我们直接从这句开始跟下源码:
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
断点调式开始
我们看到SqlSession实例是DefaultSqlSession,点进去看看getMapper:
进入的是“大管家”Configuration的getMapper.
而真正给出接口代理类的是MapperRegistry.
点进去看看mapperProxyFactory.newInstance(sqlSession)具体生成实例的代码:
// MapperProxyFactory
// 生成一个 MapperProxy对象
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
发现,运用的正是JDK动态代理。
梳理下该过程的时序图,如下:
二、sql执行
通过一中的过程,我们可以得到一个接口的mapperProxy动态代理对象了。sql的执行过程就是从MapperProxy的invoke方法开始的。
先去缓存中看看是否已经有了现成的MapperMethod,没有才回去new一个出来。
接下来,调用 MapperMethod 中的execute方法。
本例中,执行的是一个查询语句,进入case SELECT分支
case SELECT: // 如果是查询语句
if (method.returnsVoid() && method.hasResultHandler()) { // 返回为void,且有结果处理器
// 使用结果处理器执行查询
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) { // 多条结果查询
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) { // map结果查询
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) { // 游标类型结果查询
result = executeForCursor(sqlSession, args);
} else { // 单条结果查询
// 将参数顺序与实参对应好
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
我们可以看到,根据返回的结果类型,进入不同的处理逻辑,主要有
- 返回为void
- 返回多条结果
- 返回map结果
- 返回单条结果
返回值为空的情况下,直接返回 result = null。其余几种情况内部都调用了sqlSession 中的selectList 方法。这里我们以selectOne为例。
convertArgsToSqlCommandParam()前面我们在【Mybatis源码学习】参数解析做过说明,主要进行参数的顺序映射解析。这里不做赘述。直接看看DefaultSqlSession.selectOne方法。
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
如果selectList查询返回1条,则直接返回;如果返回多条则抛出异常,否则直接返回null。
点进去看看selectList方法。
/**
* 查询结果列表
* @param <E> 返回的列表元素的类型
* @param statement SQL语句
* @param parameter 参数对象
* @param rowBounds 翻页限制条件
* @return 结果对象列表
*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取查询语句
MappedStatement ms = configuration.getMappedStatement(statement);
// 交由执行器进行查询
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();
}
}
这里说明下,mybatis默认是开启了二级缓存的,所以这里executor其实是CachingExecutor.我们可以在创建sqlSession中看看Configuration中创建执行器时的代码:
要关闭二级缓存,我们可以在mybatis-config.xml中显示配置,如下:
<!-- 二级缓存开关,默认为开启-->
<setting name="cacheEnabled" value="false"/>
关闭二级缓存,我们回到selectList方法中,继续跟下这段代码的执行过程:
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)
executor这里为SimpleExecutor,其首先调用的是BaseExecutor的query方法。
/**
* 查询数据库中的数据
* @param ms 映射语句
* @param parameter 参数对象
* @param rowBounds 翻页限制条件
* @param resultHandler 结果处理器
* @param key 缓存的键
* @param boundSql 查询语句
* @param <E> 结果类型
* @return 结果列表
* @throws SQLException
*/
@SuppressWarnings("unchecked")
@Override
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) {
// 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
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();
}
// issue #601
deferredLoads.clear();
// 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
先从一级缓存中查询是否已有结果,没有则才去查询数据库。我们直接看查询数据库的部分,也就是方法queryFromDatabase().
进入doQuery()方法,发现这里是个模版方法,由于前面提到我们此处的执行器为SimpleExecutor,所以这个方法来到了SimpleExecutor的doQuery()方法.
如上,该方法主要有三步:
- 新建一个StatementHandler
- 获取Statement
- StatementHandler.query(实际调用的是PreparedStatementHandler)获取查询结果。
我们看看prepareStatement方法。
//SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取数据库连接
Connection connection = getConnection(statementLog);
//创建Statement
stmt = handler.prepare(connection);
//为Statement设置IN参数
handler.parameterize(stmt);
return stmt;
}
经典的流程:获取数据库连接;创建Statement; 为Statement设置IN参数。再看第三步执行语句:
//PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行SQL
ps.execute();
// 处理执行结果
return resultSetHandler.<E> handleResultSets(ps);
}
查询完毕,总结一下这个过程的时序图,一目了然:
三、结果集处理
结果集的映射,这部分我会在下一篇详细的跟进。Game over.