likes
comments
collection
share

水煮MyBatis(三)- SQL解析

作者站长头像
站长
· 阅读数 24

前言

在Mapper接口中,有注解的方法,也有xml配置的方法,这一章我们主要介绍前者。

本文介绍的Mapper方法

    @Select("select * from tb_image where md5 = #{md5}")
    ImageInfo byMd5(@Param(value = "md5") String md5);

序列图

水煮MyBatis(三)- SQL解析

注册Mapper

注册Mapper,将Mapper注册到knownMappers中,注意后面的是一个代理类,后续再开个单章介绍。同时将Mapper接口解析,代码中的config参数是Configuration的实体,主要是mybatis的配置信息。


  public <T> void addMapper(Class<T> type) {
        // 注册
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // 主要代码就是这两行
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析
        parser.parse();
  }

水煮MyBatis(三)- SQL解析

解析Mapper

Mapper里的每个方法都会创建一个statement,供后续执行使用。

解析Mapper中的方法

  public void parse() {
    String resource = type.toString();
    // 加载xml配置,在XMLMapperBuilder中解析xml中的方法,这里忽略不讲
    loadXmlResource();
    // 每个Mapper加载一次
    configuration.addLoadedResource(resource);
    // 当前Mapper的命名空间
    assistant.setCurrentNamespace(type.getName());
    // 缓存注解解析,见后续单章
    parseCache();
    parseCacheRef();
    // 每个方法都需要生成一个statement
    for (Method method : type.getMethods()) {
      // 忽略不能解析的方法,比如抽象、静态等
      if (!canHaveStatement(method)) {
          continue;
      }
      // 生成statement,保存到configuration
      parseStatement(method);
    }
  }

主要流程分为三个步骤:

  • 解析Mapper对应的xml文件,并为xml文件里的每个方法都生成一个statement;
  • 解析缓存注解,后续介绍;
  • 解析注解方法;

生成statement

    public MappedStatement addMappedStatement(...) {
    // 是否查询方法
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 生成statement
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
    // 语句的参数配置
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    // Mapper里每个方法都会生成一个statement
    MappedStatement statement = statementBuilder.build();
    // 将statement放到mybatis上下文
    configuration.addMappedStatement(statement);
    return statement;
  }

将statement注册到Configuration里的mappedStatements结构中,后续执行的时候,直接从这里获取对应方法的statement

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

替换sql中的占位符

在SqlSourceBuilder类中,对sql中的参数占位符进行替换

  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;
    if (configuration.isShrinkWhitespacesInSql()) {
      sql = parser.parse(removeExtraWhitespaces(originalSql));
    } else {
      sql = parser.parse(originalSql);
    }
    // 从handler中获取请求参数map
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

例子中的请求参数: ParameterMapping{property='md5', mode=IN, javaType=class java.lang.String, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'} 替换效果如下: 水煮MyBatis(三)- SQL解析

最终Statement信息

水煮MyBatis(三)- SQL解析