美文网首页
myabtis源码解析三(sql语句的执行过程)

myabtis源码解析三(sql语句的执行过程)

作者: 为梦想前进 | 来源:发表于2019-10-16 09:47 被阅读0次

前两期,分别研究了下mybatis配置文件的加载源码分析和mybatis的映射文件源码分析,当两个文件加载完毕后,会通过MapperRegister的addMappers方法为每一个mapper接口生成一个代理对象,
这样,前期工作就准备就绪了,接下来,就是我们开始执行业务逻辑啦,当我们开始访问的service调用mapper接口的时候首先回去调用代理对象的invoke方法,执行相关的逻辑,接下来,我们从源码角度去分析下
之前,在分析加载映射文件的时候,已经说过,在为mapper绑定命名空间的时候,会调用MapperRegister的addMapper方法,再来重温下下这个方法
方法很简单主要做了以下几件事
1:判断maper是不是接口,
2:为mapper接口生成代理对象存入到map集合
3:处理mapper接口方法上注解sql流程

 public <T> void addMapper(Class<T> type) {
    //判断mapper是不是接口
    if (type.isInterface()) {
    //knowMappers是否已经存在这个类型的mapper接口
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //将mapper对应的类型作为key,mapper代理对象作为value存入到map集合中
        knownMappers.put(type, new MapperProxyFactory<T>(type));

        //以下代码为处理mapper接口上使用sql注解的语句解析,都是差不多的,就不做分析了
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
接下来,进入主题,咱们开始为mapper生成代理对象逻辑,为mapper生成代理对象的逻辑封装在MapperRegster的getMapper中,看源码
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从KnowMappers中获取对应的类型的代理对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    //如果代理对象为空,抛出异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    //创建代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  在获取到 MapperProxyFactory 对象后,即可调用工厂方法为 Mapper 接口生成代理对象了,继续看源码,这里使用了jdk的动态代理
 @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //创建mapper代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //创建mapperPrxy代理对象
    return newInstance(mapperProxy);
  }
  接下来进入创建mapperProxy代理对象的方法,查看invoke方法的执行逻辑
   @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
            //如果该方法是object类型,直接执行
        if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
          return invokeDefaultMethod(proxy, method, args);
        }
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
      //1:获取mapperMethod对象
      final MapperMethod mapperMethod = cachedMapperMethod(method);
      //2:执行sql
      return mapperMethod.execute(sqlSession, args);
    }
来看一下方法一:主要作用是获取mapperMethod对象,不存在,就创建后,添加到methodCache集合中,比较简单,就不做分析了
 private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  我们进入到MapperMethod的创建逻辑中
 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
     //1:创建sql语句
    this.command = new SqlCommand(config, mapperInterface, method);
    //2:创建方法签名
    this.method = new MethodSignature(config, mapperInterface, method);
  }

1:以下是构建sqlCommond的方法.构建sql语句
 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
        //获取方法名
      final String methodName = method.getName();
      //获取类型
      final Class<?> declaringClass = method.getDeclaringClass();
      //获取映射语句
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        //如果映射语句为空,查看是否存在Flush注解,这里简单说一下这个注解的作用
        //当存在事物的时候,这个注解起到了一种预插入的作用,当你需要插入一行记录,这个时候由于事物没有结束,你想使用插入的这个对象的主键,是拿不到的,这个时候加入这个注解
        //就会拿到这个主键,这时候,就可以执行其他操作了
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
      //映射语句不为空的时候,获取name属性和type属性
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
2:MethodSignatured的主要作用是用于检测目标方法的返回类型,以及解析目标方法参数列表
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
        // 通过反射解析方法返回类型
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      获取 RowBounds 参数在参数列表中的位置,如果参数列表中 包含多个 RowBounds 参数,此方法会抛出异常
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      // 获取 ResultHandler 参数在参数列表中的位置
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 解析参数列表
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }
//接下来看看参数列表的解析过程
public ParamNameResolver(Configuration config, Method method) {
   // 获取参数类型列表
    final Class<?>[] paramTypes = method.getParameterTypes();
    //获取参数注解
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    // 检测当前的参数类型是否为 RowBounds 或 ResultHandler
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
           // 获取 @Param 注解内容
          name = ((Param) annotation).value();
          break;
        }
      }
      // name 为空,表明未给参数配置 @Param 注解
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          // 检测是否设置了 useActualParamName 全局配置
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }
解析完毕后,可得到参数下标到参数名的映射关系,这些映射关系存储在names集合中,
也就是说,加入有一个方法是getUser(@Param("id")Integer id,@Param("name")String name,RowBounds rb,User user)
那样names集合中最终存储的值为 names:{0=id,1=name,3=user}

  接下来进入重头戏了看看eecute方法,return mapperMethod.execute(sqlSession, args);经过漫长的路程,终于感觉要看到希望了,继续往下走
  有没有一种很熟悉的感觉,看到了INSERT,UPDATE,DELETE,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);
            result = sqlSession.selectOne(command.getName(), param);
          }
          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;
    }

    不难看出,INSERT,UPDATE,DELETE三个sql语句都是两个方法,
    //构建参数
    Object param = method.convertArgsToSqlCommandParam(args);
    //返回结果
    result = rowCountResult(sqlSession.insert(command.getName(), param));
    咱们来一个个分析下,从insert开始
    INSERT:
    进入method.convertArgsToSqlCommandParam(args);方法
    主要逻辑封装在ParamNameResolver的getNamedParams中
    public Object getNamedParams(Object[] args) {
        //获取mapper接口中动态参数的个数
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
          return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
            //没有Param注解,并且参数个数是1个的情况直接返回这个参数
          return args[names.firstKey()];
        } else {
            //处理多个参数的情况
          final Map<String, Object> param = new ParamMap<Object>();
          int i = 0;
          for (Map.Entry<Integer, String> entry : names.entrySet()) {
          //将值和对应的索引添加到param集合中
            param.put(entry.getValue(), args[entry.getKey()]);
            // 添加通用参数名称 (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;
        }
      }
     rowCountResult方法比较简单,就是返回具体影响的行数,代码比较简答就不做具体的分析了,
      private Object rowCountResult(int rowCount) {
          final Object result;
          if (method.returnsVoid()) {
            result = null;
          } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            result = rowCount;
          } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            result = (long)rowCount;
          } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            result = rowCount > 0;
          } else {
            throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
          }
          return result;
        }

 后面的DELETE,UPDATE,都是差不多的,咱们这里就不做具体的分析了,接下来,我们主要分析下SELECT语句的,查询也是在平时的业务中使用额最平凡的语句
 相对来说,也比较复杂,涉及到结果集的分装,也是一个难点,虽然咱们用起来比较简单,但是myabtis处理起来可是一点都不简单,接下来,我们一起来学习下
 SELECT中涉及到的方法比较多:有如下四个方法,select方法中返回值类型的不同,导致对每种返回结果的处理也不一样,
 executeWithResultHandler
 executeForMany
 executeForMap
 executeForCursor
       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);
           result = sqlSession.selectOne(command.getName(), param);
         }

咱们分析个selectOne方法吧,通过源码可以看出,selectOne和selectList是一样的,只不过,返回值不一样,selectOne是从selectList里面取一行数据,所以,咱们分析
selectOne就是分析了selectList方法,这些具体的方法被封装在DefaultSqlSession类中
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    //调用重载方法
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
       //获取MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调用querry方法
      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();
    }
  }
  我们发现这里多了个executor变量,这是一个接口,有很多实现类,我们先去看一下这个接口,我们可以看一下他的调用关系,一共有BaseExecuter和BatchExecuter两个
  实现了Executer接口,有三个是实现类实现了基类,分别是BatchExecutor(批量执行器),ReuseExecutor(重复执行器),SimpleExecutor(简单执行器.mybatis的默认执行器)
  这里简单说一下,这三个执行器,后续还会深入去研究下这三个执行器的
  BatchExecutor(批量处理sql语句,性能会提升)
  ReuseExecutor()大致意思是说:定义了一个map<String,Statement>,key为sql语句,value为执行的statement,这样执行相同的sql的时候,就不用重复创建了.
  SimpleExecutor():myabtis默认的执行器,一条条执行sql语句执行处理.
  简单说道这里,我会专门准备一期写一下这三个执行器的用法,下面,我们一起看看默认执行器的创建过程,SimpleExecutor作为mybatis的默认执行器,是在哪里创建的那
  经过一系列分析,原来是在DefaultSqlSessionFactoy的openSession方法中创建的,咱们看看源码
   private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
      try {
        boolean autoCommit;
        try {
          autoCommit = connection.getAutoCommit();
        } catch (SQLException e) {
          // Failover to true, as most poor drivers
          // or databases won't support transactions
          autoCommit = true;
        }
        final Environment environment = configuration.getEnvironment();
        //获取事物工厂
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        //获取事物
        final Transaction tx = transactionFactory.newTransaction(connection);
        //获取Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
      } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    configuration.newExecutor:

     public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        //定义了defaultExecutorType变量 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
        executorType = executorType == null ? defaultExecutorType : executorType;
        /**
        *ExecutorType是一个枚举,定义了三个枚举类型,这里默认就是SimpleExecutor
        /
        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);
        }
        //启用缓存的话,默认为CacheExecuter
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

      我们从CachingExecutor进行分析,我们分析的是select,所以分析querry方法
       @Override
        public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
           //获取BoundSql
          BoundSql boundSql = ms.getBoundSql(parameterObject);
          //获取缓存
          CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
          //执行querry重载方法
          return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        }

        @Override
          public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
              throws SQLException {
              //获取MappedStatement中的缓存,如果映射文件中没有配置的话,cache为null
            Cache cache = ms.getCache();
            if (cache != null) {
              flushCacheIfRequired(ms);
              //判断是否在缓存中存在
              if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, parameterObject, boundSql);
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                //缓存中不存在.调用BaseExecuter的querry方法
                if (list == null) {
                  list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                  tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
              }
            }
            调用BaseExecuter的querry方法
            return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          }

这里提到了BaseExecuter,咱们再跳转到BaseExecuter中的querry方法看看

          @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) {
                  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;
            }

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 {
        //调用doQuery执行查询
      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,它是一个抽象方法,由于之前默认的执行器是SimpleExecuter,所以跳转到SimpleExecuter的doQuerry中看到这里,突然想起了一种之前写jdbc源码的感觉,一起来复习下,
      1:加载jdbc所需的四个参数
      2:加载驱动
      3:创建数据库连接
      4:创建perparedStatement
      5:执行sql语句
      6:遍历结果集
      7:关闭连接
  @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();
        //创建StatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        //4:创建Statement
        stmt = prepareStatement(handler, ms.getStatementLog());
        //执行数据库查询
        return handler.<E>query(stmt, resultHandler);
      } finally {
        //7:关闭Statement
        closeStatement(stmt);
      }
    }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    //5执行sql语句
    statement.execute(sql);
    //6:遍历结果集
    return resultSetHandler.<E>handleResultSets(statement);
  }
和以上的七个步骤就算对应上啦!接下来,咱们来分析下BondSql,在上一篇中,我们在分析映射文件的时候,说道mybatis在解析映射文件的sql的时候会根据不同的关键字解析sql
那马接下来,我们一起看下,mybatis的到底是怎样将参数赋值到到sql语句中的,我们一起看一下CaxcheExecuter中的getBoundSql,bondSql中存在5个参数,

  private final String sql;(处理动态内容后,获取的实际sql字符串)
  private final List<ParameterMapping> parameterMappings;(解析sql中的每一个占位符,并设置到parameterMappings)
  private final Object parameterObject;(运行时参数,比如传入的对象信息)
  private final Map<String, Object> additionalParameters;(每个参数的附加信息)
  private final MetaObject metaParameters;(additionalParameters 的元信息对象)

public BoundSql getBoundSql(Object parameterObject) {
    //获取BoundSql对象
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    //获取boundSql中的参数映射列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      //不存在参数映射list,创建boundSql对象
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // 处理嵌套查询逻辑
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }
sqlSource.getBoundSql一共有四个实现类,分别是
DynamicSqlSource
RawSqlSource
StaticSqlSource
ProviderSqlSource

我们先看一下DynamicSqlSource
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    //创建DynamicContext对象
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //解析sql
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    //创建staticSqlSource并将 sql 语句中的占位符 #{} 替换为问号 ?
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
     // 调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

接下来看下rootSqlNode.apply(context);这个方法有多个实现类,针对映射文件中的不同的标签,定义了不同的解析方法,我们看一下uml图
image.png
StaticTextSqlNode 用于存储静态文本,
TextSqlNode 用于存储带有 ${} 占位符的文本,
IfSqlNode 则用于存储 <if> 节点的内容。
MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode。
public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 遍历 SqlNode 集合
        for (SqlNode sqlNode : contents) {
            // 调用 salNode 对象本身的 apply 方法解析 sql
            sqlNode.apply(context);
        }
        return true;
    }
}
MixedSqlNode 可以看做是 SqlNode 实现类对象的容器,凡是实现了 SqlNode 接口的类都可以存储到 MixedSqlNode 中,
包括它自己。MixedSqlNode 解析方法 apply 逻辑比较简单,即遍历 SqlNode 集合,并调用其他 SalNode 实现类对象的 apply 方法解析 sql
public class StaticTextSqlNode implements SqlNode {

    private final String text;

    public StaticTextSqlNode(String text) {
        this.text = text;
    }

    @Override
    public boolean apply(DynamicContext context) {
        context.appendSql(text);
        return true;
    }
}
下面分析下TextSqlNode
public class TextSqlNode implements SqlNode {

    private final String text;
    private final Pattern injectionFilter;

    @Override
    public boolean apply(DynamicContext context) {
        // 创建 ${} 占位符解析器
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        // 解析 ${} 占位符,并将解析结果添加到 DynamicContext 中
        context.appendSql(parser.parse(text));
        return true;
    }

    private GenericTokenParser createParser(TokenHandler handler) {
        // 创建${}占位符解析器,
        return new GenericTokenParser("${", "}", handler);
    }

    private static class BindingTokenParser implements TokenHandler {

        private DynamicContext context;
        private Pattern injectionFilter;

        public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
            this.context = context;
            this.injectionFilter = injectionFilter;
        }

        @Override
        public String handleToken(String content) {
            Object parameter = context.getBindings().get("_parameter");
            if (parameter == null) {
                context.getBindings().put("value", null);
            } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
                context.getBindings().put("value", parameter);
            }
            // 通过 ONGL 从用户传入的参数中获取结果
            Object value = OgnlCache.getValue(content, context.getBindings());
            String srtValue = (value == null ? "" : String.valueOf(value));
            // 通过正则表达式检测 srtValue 有效性
            checkInjection(srtValue);
            return srtValue;
        }
    }
}
GenericTokenParser 是一个通用的标记解析器,用于解析形如 ${xxx},#{xxx} 等标记。GenericTokenParser 负责将标记中的内容抽取出来,并将标记内容交给相应的 TokenHandler 去处理。
BindingTokenParser 负责解析标记内容,并将解析结果返回给 GenericTokenParser,用于替换 ${xxx}
通常情况下,${}的解析是不经过转义的,而是传什么,解析成什么这就是为什么我们不应该在 SQL 语句中是用 ${} 占位符,风险太大
其他的就不做分析了,接下来,主要分析下#{}的解析
#{} 占位符的解析逻辑是包含在 SqlSourceBuilder 的 parse 方法中,该方法最终会将解析后的 SQL 以及其他的一些数据封装到 StaticSqlSource 中。下面,一起来看一下 SqlSourceBuilder 的 parse 方法。

 SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    //创建 #{} 占位符处理器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    //创建#{}解析器
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 解析 #{} 占位符,并返回解析结果
    String sql = parser.parse(originalSql);
     // 封装解析结果到 StaticSqlSource 中,并返回
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
这里就进行具体的分析了,大概说下意思,就是会将#{}替换为?,然后将里面的内容解析成为map并存入到parmeterMappng中,
解析 content
解析 propertyType,
构建 ParameterMapping 对象,
以上就是sql的解析,
我们来总结下以上步骤(以查询语句为例)
1:加载完映射文件后,MapperRegister为每一个mapper接口生成一个代理对象
2:当执行调用的时候,会执行代理对象的invoke方法,
3:获取sql命令,是查询语句,还是update语句或者是insert语句
4:解析方法的返回类型
5通过ParamNameResolver方法解析传递的参数,并设置到map集合中
6:执行execute方法(mybatis有三种执行器方法,默认为simple)
7:根据之前的sqlCommond确定sql的语句类型(select,insert,update).执行不同的方法
8:执行cacheingExecuter方法
9:解析sql中的各个标签,比如${}直接解析为字符串,#{}先解析为?,然后将内容一一解析后封装到map中,并将结果封装到bondSql中
10:创建statement对象
11:执行sql语句
12:处理返回的结果集
13:关闭连接

现在解析完了sql,剩余额工作就是创建statement对象,对返回结果集的处理,我们下一期继续接着分析
Thanks!

相关文章

网友评论

      本文标题:myabtis源码解析三(sql语句的执行过程)

      本文链接:https://www.haomeiwen.com/subject/ixoqmctx.html