Mybatis是如何执行一条sql
本篇文章是通过看视频学习总结的内容, 如有错误的地方请谅解,并联系博主及时修改,谢谢您的阅读.
注意:
本篇博客接着前四篇博客,主要是从第一篇博客的测试例子中开始延申,直到到源码的分析。
源码地址: mybatis 中文注释版
前四篇博客地址:
Mybatis(四) - Mybatis是如何对Mapper接口进行代理的
Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的
Mybatis(二) - Mybatis是如何创建出SqlSessionFactory的
Mybatis(一) - Mybatis 最原始是使用方式
前言: 在前四篇博客中,简单的阐述 mybatis 初始化过程、创建 SqlSession
对象、mapper
接口的代理,在本文中要正式开始简单阐述 mybatis
执行 sql
的 部分逻辑。
一、源码跟进
- 1.1 上一篇博客讲到了
mapper
接口的动态代理,实际上根据第一篇博客中得到的mapper
接口代理类,去执行mapper.getUserPage("10086");
方法,这里实际上是执行的动态代理的类实现的方法,那么一旦调用执行了代理后对象的方法,那么代理中InvocationHandler
中的invoke()
将会被执行,在上一篇博客中讲到了代理的类是org.apache.ibatis.binding.MapperProxy
,那么本文主要就围绕着这个来开始讲解,下面是org.apache.ibatis.binding.MapperProxy#invoke()
方法代码:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// toString hashCode equals getClass 等方法,无需走到执行SQL的流程
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
// 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
- 1.1.1 这里重点关注
cachedInvoker(method).invoke(proxy, method, args, sqlSession)
,这是MapperProxy
类中的内部接口,实现类则是内部私有的静态类PlainMethodInvoker
,这个方法却调用了MapperMethod
类中execute()
,那么继续进入
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// SQL执行的真正起点
return mapperMethod.execute(sqlSession, args);
}
- 1.2 该方法中,主要对
SqlCommand
类中type
属性进行判断,那么先瞧瞧SqlCommand
类,里面主要包含了name、type
两个属性,而type
则是SqlCommandType
类型,里面有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
六种枚举类型,这里先不做解释,到后续再解释,先关注excute
方法。在这几个条件中,最满足我们需求的就是SELECT
,因为我执行的是select
的sql
语句,
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// **** 此次查询走的是这个方法 ****
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 普通 select 语句的执行入口 >>
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// SqlCommand 类是 MapperMethod 类的静态内部类
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
...
}
// SqlCommandType 枚举
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}
- 1.2.1 那么我们继续进入
org.apache.ibatis.binding.MapperMethod#executeForMany()
,这里面依旧是调用了method.convertArgsToSqlCommandParam(args);
这个方法,那么继续进入该方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
- 1.2.2 进入
method.convertArgsToSqlCommandParam(args);
方法,这里可能需要参加一次 debugg 来对代码进行调试,最终返回的结果,则是将原有的参数进行封装一次,放入到map中进行返回,如图:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
- 1.2.3 返回到
1.2.1
的代码中,method != null
则进入else
逻辑,执行sqlSession.selectMap(command.getName(), param, method.getMapKey())
这句代码,那么继续进入该方法,(这里一直是使用的 DefaultSqlSession 对象)
,这里一直寻找到org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
这个方法,这里才马上就接近真相了,那么在 selectList() 方法内部调用了 executor#query(),那么继续进入该方法,该方法存在与BaseExecutor
中,为什么在BaseExecutor
中,而不是在CachingExecutor
中,后续再做解释
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
// 这里调用了 下面selectMap 方法
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
// 这里调用了下面 selectList方法
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
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();
}
}
- 1.2.4 进入
org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
,下面继续分析:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
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()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
进入
query()
后,看到 try 代码块内,先从 一级缓存中取了一次(这里是第一次启动整个上下文,所以缓存中肯定不会被命中)
, 这里就会执行queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)
这句代码,这里进入到org.apache.ibatis.executor.BaseExecutor#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 {
// 三种 Executor 的区别,看doUpdate
// 默认Simple
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()
那么继续进入。首先创建一个StatementHandler
对象,然后的到java.sql.Statement
对象,注意啦,这里的Statement
不是mybatis
的,这么一说,离真相越来越近了,最后是通过得到的StatementHandler
对象执行query()
方法。很明显这里都能猜到里面做了什么事情,肯定是通过Statement.query()
执行JDBC
的查询逻辑,那么,继续进入该方法。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 注意,已经来到SQL处理的关键对象 StatementHandler >>
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 获取一个 Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
}catch (Exception ex) {
ex.printStackTrace();
return null;
} finally {
// 用完就关闭
closeStatement(stmt);
}
}
- 1.2.5 进入
org.apache.ibatis.executor.statement.PreparedStatementHandler#query()
方法,很符合我的猜想逻辑嘛,只不过是强制转换了Statement
为PreparedStatement
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 到了JDBC的流程
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
二、步骤分析
- 2.1 在
1.2
为什么SqlCommandType
有6种类型?
INSERT, UPDATE, DELETE, SELECT
: 这4种,不做解释
UNKNOWN
:异常情况
FLUSH
:是mybatis中的一个注解,对Mapper接口方法返回值(List) 进行置空。
- 2.2 在
1.2.3
为什么BaseExecutor
而不是CachingExecutor
中执行query()
?
这里要追溯到 文章 《Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的》,
Executor
是跟随 session 创建的,所以回到 session创建,则能找到答案。
在 方法org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
中,从代码注释即可看出答案
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 1. 首先我没有配置,任何一个执行器类型,所以,这里只会创建 SimpleExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
}
// 默认会走这里的逻辑
else {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 2. 二级缓存开关,这里我的全局二级缓存开关是 false,所以不可能会创建 用缓存执行器来装饰
if (cacheEnabled) {
// 这里使用了装饰器模式,来增强执行器对象
executor = new CachingExecutor(executor);
}
// 3. 这里我并没有任何一个插件注入,所以,也没有给我装饰其他的插件
executor = (Executor) interceptorChain.pluginAll(executor);
// 4. 最终返回最基础的执行器
return executor;
}
- 2.3
sql
语句在哪里被获取到的?
方法全路径:
org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 这里获取到 sql 语句,
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
上面的方法只是从
MapperStatement
对象中获取到BoundSql
对象,那么在哪里获取到的MapperStatement
对象呢?
方法路径:org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
进入方法:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 这里就很明显看到,是通过接口全路径加上被执行方法,
// 然后在初始化阶段完成注册的容器中获取到MappedStatement对象,然后得到内部存储的sql语句。
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();
}
}
- 2.4 查询过程简短描述
首先通过动态代理的方法,执行代理的
InvocationHandle的invoke
方法,然后获取到执行的sql
,然后对传入的参数进行处理,最终调用执行器的query()
,就这么简单。