Mybatis(五) - Mybatis是如何执行一条sql

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 ,因为我执行的是 selectsql 语句,
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() 方法,很符合我的猜想逻辑嘛,只不过是强制转换了 StatementPreparedStatement
@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(),就这么简单。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值