前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis学习笔记(二)- Sql的执行过程

Mybatis学习笔记(二)- Sql的执行过程

作者头像
写一点笔记
发布2020-12-31 10:18:14
4000
发布2020-12-31 10:18:14
举报
文章被收录于专栏:程序员备忘录程序员备忘录

在之前的分析中,我们基本明白了mybatis对接口和xml的sql文件的组装拼接的原理。但是我们执行sql又是如何实现的,或者说sql的执行到底走了哪些流程。在上次的分析中我们知道mybatis采用了动态代理的方式,而且的pagehelper分页的时候也是动态代理。那么这之间到底是怎么执行的,除此之外我们也应当考虑mybatis提供的四大拦截器的具体执行顺序。所以这是我们今天的主要工作。

首先我们知道,我们通过mybatis执行sql大概是这样的。

 /**
     * 项目表
     */
    @Autowired
    private ProjectInfoPoMapper projectInfoPoMapper;

    /**
     * 添加一个项目
     * @param request 添加监控项目
     * @return
     */
    @Override
    public ResponseResult addMonitorProject(ProjectAddRequest request) {
        ProjectDomain projectDomain=new ProjectDomain();
        BeanUtils.copyProperties(request,projectDomain);
        return projectDomain.insert(projectInfoPoMapper);
    }

    /**
     * 添加到数据库
     * @param projectInfoPoMapper
     * @return
     */
    public ResponseResult insert(ProjectInfoPoMapper projectInfoPoMapper) {
        ProjectInfoPo po=new ProjectInfoPo();
        BeanUtils.copyProperties(this,po);
        po.setCreateTime(new Date());
        if (projectInfoPoMapper.insert(po)>0){
            return ResponseResult.success(true);
        }
        return ResponseResult.error("add fail");
    }

显然这里的@Auwired我们已经解决了。而insert方法也是在接口中定义的。projectInfoPoMapper实体其实也是在spring启动的时候创建好了。但是我们好奇的是底层是如何实现的。所以我们还是跟踪一下。

在上期文章中,作者说过knowsmapper的map结构的代理mapper缓存。其中的元素就是proxymapperfactory。

也就是说我们的sql执行肯定是通过这里的proxymapper来执行的。那么我们重点看一下这里的proxymapper。因为这里是jdk动态代理,所以我们找一下proxymapper的代码。

jdk动态代理,就是说我们autowried注入的是动态代理生成的对象。我们在调用的时候其实只调用了接口,但是最后执行了被代理类的方法。这块的method就是接口法发起的。

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
//如果是Object类,那么直接诶执行。
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
//从缓存中获取映射的方法。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

在此我们还看到这里做了一个方法的缓存
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

其中对sql的属性进行分析。
    public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
//获取方法名称
      final String methodName = method.getName();
      final Class declaringClass = method.getDeclaringClass();
//解析statement的映射关系
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
//获取sql的类型
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

我们继续跟进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()) {
//返回的值是list
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
//返回的值是mapper
          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;
      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;
  }

可以看到上述操作对我们要执行的sql进行分类,然后去执行。我们在此大概得分析一下传入参数的解析,然后将重点放在下游调用链上。
在对sql进行解析的时候,将其参数转换为map
    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }
传入参数,返回map
  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 Mapparam = new ParamMap<>();
      int i = 0;
      for (Map.Entryentry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

在我们执行列表查询的时候执行了executeformany方法

private Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
//参数转为map
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
    //通过sqlsession查询数据库
      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;
  }

继续跟踪代码,执行了参数的前置处理,并执行了查询方法

其中方法configuration.getMappedStatement是从xml文件的类中找到参数映射关系。warpcollection方法主要是对传入的参数进行map化,我们也看到这里默认collection、list、array等。也就是说我们传入这些值得时候其实是不用标记的。

 private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap map = new StrictMap<>();
      map.put("array", object);
      return map;
    }
    return object;
  }

代码跟踪到这里的时候,作者发现在query方法上居然有缓存。如图所示

@Override
  public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//参数绑定处理
    BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在创建缓存的时候,其实也是一组list

@Override
  public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
//先从缓存中查询
        List list = (List) tcm.getObject(cache, key);
        if (list == null) {
//缓存中为空的,开始查库
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//添加到缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

通过这些时间的学习,我们也能感觉到,在写代码的时候一般将重要的操作放到抽象类或者父类中,子类其实是对父类的修正。我们继续看查库操作

  @SuppressWarnings("unchecked")
  @Override
  public Listquery(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 list;
    try {
      queryStack++;
//尝试从缓存中查询
      list = resultHandler == null ? (List) 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;
  }

在查库的时候,mybatis进行了如下操作

private ListqueryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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;
  }

而这里的preparestatement就是获取数据库连接的地方。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
//设置事务时间
    stmt = handler.prepare(connection, transaction.getTimeout());
//将传入的参数和数字关联
    handler.parameterize(stmt);
    return stmt;
  }

在最终执行的时候,作者发现调用的是数据库连接的执行。

分析到这里,我们可能有点疑问,我们的executor是在哪里进行初始化的,不是说好的有拦截器么,怎么分析的过程中并没有执行?怀着这种疑问我们再来看看。

我们发现executor在初始化的时候就已经创建了。

作者通过代码跟踪,发现sqlSessionFactory中具有创建的相关代码。

创建的细节为,具体的实现类为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);
    }
//添加插件,这里的插件是通过jdk代理生成的。也就是说executor最后还是动态生成的
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

而要使用的executor的初始化则是通过configuration来产生的。configuration的注入如下所示

通过上述分析,我们得出的结论是我们使用注解@Autowired注入的时候是通过MapperFactoryBean注入的,而mapperFactory在初始化的时候注入了sqlsessionfactory然后初始了sqlsessiontemplate,而sqlsessiontemplate就是sqlsession的代理类。sqlsessionfactory的初始化则如上所示。sqlsessionfactory的初始化直接就已经生成了configuration,configuration在sqlsessionfactory创建的时候会进行创建 ,在之后sqlsessiontemplate调用的时候就没有后顾之忧。同时在getmapper注入sqlsession的时候其实也是注入的会话代理,同样是jdk动态代理,最终实现的是defaultsqlsession,defaultsqlsession执行方法的时候则会按照我们configuration设置的executortype来决定具体的执行器,其中的cachexecutor我们下次分析。而sqlsession的执行获取连接的部分最后就交给了数据库连接池。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员备忘录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档