mybatis-初始化(三)statement解析

概述

Statement是我们平时sql的载体,一条sql代表一个Statement,来看下mybatis如何解析Statement。

接着上篇最后的入口

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      //解析异常的,记录下来
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

XMLStatementBuilder

public class XMLStatementBuilder extends BaseBuilder {

  /**
   * mapper构造助手
   */
  private final MapperBuilderAssistant builderAssistant;
  /**
   * statement节点
   */
  private final XNode context;
  /**
   * 要求的数据库id(忽略)
   */
  private final String requiredDatabaseId;

  public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
    super(configuration);
    this.builderAssistant = builderAssistant;
    this.context = context;
    this.requiredDatabaseId = databaseId;
  }

这里元数据是,mapper构造助手、statement节点。

parseStatementNode 解析xml

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  // ①include sql片段解析,替换<include />
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  // 解析<selectKey />
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  // 获取主键生成器
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  // 创建sqlSource,sql载体
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  // 属性解析
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  // ②使用xml构造助手生成MappedStatement
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

statement标签上定义的属性比较多,一些简单的属性解析之后,直接保存结果。

对某些标签会做稍复杂一点的处理。

include解析

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 应用include标签
includeParser.applyIncludes(context.getNode());
public void applyIncludes(Node source) {
  // properties读取
  Properties variablesContext = new Properties();
  Properties configurationVariables = configuration.getVariables();
  Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);
  // 应用
  applyIncludes(source, variablesContext, false);
}
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
  if (source.getNodeName().equals("include")) {
    //通过refid 查找出sql节点,include实际是引入sql标签
    Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
    Properties toIncludeContext = getVariablesContext(source, variablesContext);
    // 递归处理嵌套<include />
    applyIncludes(toInclude, toIncludeContext, true);
    if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
      toInclude = source.getOwnerDocument().importNode(toInclude, true);
    }

    // 将<include /> 替换成 <sql />
    source.getParentNode().replaceChild(toInclude, source);
    while (toInclude.hasChildNodes()) {
      toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
    }
    toInclude.getParentNode().removeChild(toInclude);
  } else if (source.getNodeType() == Node.ELEMENT_NODE) {
    if (included && !variablesContext.isEmpty()) {
      // replace variables in attribute values
      NamedNodeMap attributes = source.getAttributes();
      for (int i = 0; i < attributes.getLength(); i++) {
        Node attr = attributes.item(i);
        // 占位符替换
        attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
      }
    }
    NodeList children = source.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      applyIncludes(children.item(i), variablesContext, included);
    }
  } else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)
      && !variablesContext.isEmpty()) {
    // 占位符替换
    source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
  }
}

include标签解析实际是查找当前namespace下的sql片段,并在真正解析Statement之前,将其替换成Sql标签。

然后会将整个sql,可能是动态的可能是静态的转为SqlSource。

MappedStatement 构造

MappedStatement主要是包含了对Statement标签解析的结果,完完整整的描述个Statement。

public final class MappedStatement {

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}

构造过程:

public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }

  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

  // 属性挨个设置到builder中
  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)
      // ①resultMap解析
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);

  // ②转换ParameterMap
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }

  // 构造实例
  MappedStatement statement = statementBuilder.build();
  //保存到configuration中
  configuration.addMappedStatement(statement);
  return statement;
}

其实构造过程也比较单纯,就是将xml的上定义的标签解析并保存下来。

resultMap解析

①处,Statement上ResultMap解析展开一下。

private List<ResultMap> getStatementResultMaps(
    String resultMap,
    Class<?> resultType,
    String statementId) {
  // 在当前命名空间下查找
  resultMap = applyCurrentNamespace(resultMap, true);

  /*
   * 存储ResultMap结果
   */
  List<ResultMap> resultMaps = new ArrayList<>();
  if (resultMap != null) {
    /*
     * 支持多个 ,分隔,存储过程才会涉及多个ResultMap
     * 普通sql考虑一个的情况即可
     */
    String[] resultMapNames = resultMap.split(",");
    for (String resultMapName : resultMapNames) {
      try {
        /*
         * 根据resultMap的id从当初Configuration中获取ResultMap实例
         * 在解析xml的时候Configuration就已经将所有ResultMap存下来了
         */
        resultMaps.add(configuration.getResultMap(resultMapName.trim()));
      } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
      }
    }
  }
  /*
   * 如果使用的ResultType,那么会自动生成一个ResultMap
   *
   */
  else if (resultType != null) {
    ResultMap inlineResultMap = new ResultMap.Builder(
        configuration,
        statementId + "-Inline",
        resultType,
        new ArrayList<>(),
        null).build();
    resultMaps.add(inlineResultMap);
  }
  return resultMaps;
}

这里看到当配置了ResultType时也会为其创建一个ResultMap。

ParameterMap解析(由于官方正在渐渐废弃,所以略过)

总结

解析的过程比较简单,其中包含了一些实体的转换,最终解析后构造MappedStatement存储。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章