美文网首页
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