『手撕 Mybatis 源码』03 - 解析映射配置文件

解析映射配置文件

  1. SQL 映射文件只有很少的几个顶级元素(按照定义顺序列出)
    在这里插入图片描述
  • select 元素允许你配置很多属性来配置每条语句的行为细节
<select
  id="select"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
  • insert、update 和 delete
<insert
  id="insert"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="update"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="delete"
  parameterType="com.itheima.pojo.User"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
  • 动态sql,借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

问题:映射配置文件中标签和属性如何被解析封装的?

  1. 继续回到 XMLConfigBuilder 代码中,这里先通过解析 /mappers 节点,然后遍历下面的子标签 /mapper
public class XMLConfigBuilder extends BaseBuilder {
  ...
  protected final Configuration configuration; // 这个是 BaseBuilder 里面的属性
  ...
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 获取 <mappers> 标签的子标签
      for (XNode child : parent.getChildren()) {
        // <package> 子标签
        if ("package".equals(child.getName())) {
           ...
        } else {// <mapper> 子标签
          ...
          // 它们是互斥的
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              // 专门用来解析 mapper 映射文件
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              ...
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ...
          } else if (resource == null && url == null && mapperClass != null) {
            ...
          } else {
            ...
          }
        }
      }
    }
  }
}
  1. 这时候也先创建一个 XMLMapperBuilder,然后把 UserMapper.xml 解析成 document 对象,封装到 XPathParser 里面
public class XMLMapperBuilder extends BaseBuilder {
  ...
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }
}
  1. 然后通过 XMLMapperBuilder 开始 parse()
public class XMLConfigBuilder extends BaseBuilder {
  ...
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 获取 <mappers> 标签的子标签
      for (XNode child : parent.getChildren()) {
        // <package> 子标签
        if ("package".equals(child.getName())) {
           ...
        } else {// <mapper> 子标签
          ...
          // 它们是互斥的
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              ...
              // 通过 XMLMapperBuilder 解析 mapper 映射文件
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ...
          } else if (resource == null && url == null && mapperClass != null) {
            ...
          } else {
            ...
          }
        }
      }
    }
  }
}
  1. 进入 parse() 中,先判断资源对象 resource,即 mapper/UserMapper.xml 是不是已经加载过,没有则加载,并且从文件的根节点 /mapper 开始解析,解析完之后标记解析完成
public class XMLMapperBuilder extends BaseBuilder {
  ...
  public void parse() {
    // mapper 映射文件是否已经加载过 resource = "mapper/UserMapper.xml"
    if (!configuration.isResourceLoaded(resource)) {

      // 从映射文件中的<mapper>根标签开始解析,直到完整的解析完毕
      configurationElement(parser.evalNode("/mapper"));
      // 标记已经解析
      configuration.addLoadedResource(resource);
      // 为命名空间绑定映射
      bindMapperForNamespace();
    }
}
  1. 解析的核心代码就在 configurationElement() 里面,就是解析 /mapper 中各种专属标签
public class XMLMapperBuilder extends BaseBuilder {
  ...
  protected final Configuration configuration; // 这是 BaseBuilder 的属性
  private final MapperBuilderAssistant builderAssistant;
  ...
  private void configurationElement(XNode context) {
    try {
      // 获取 <mapper> 标签的 namespace 值,也就是命名空间
      String namespace = context.getStringAttribute("namespace");
      // 命名空间不能为空
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // MapperBuilderAssistant:构建 MappedStatement 对象的构建助手,设置当前的命名空间为 namespace 的值
      builderAssistant.setCurrentNamespace(namespace);
      // 解析 <cache-ref> 子标签
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 <cache> 子标签
      cacheElement(context.evalNode("cache"));

      // 解析 <parameterMap> 子标签
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 <resultMap> 子标签
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 <sql> 子标签,也就是 SQL 片段
      sqlElement(context.evalNodes("/mapper/sql"));
      // 按顺序解析 <select>\<insert>\<update>\<delete> 子标签
      // 将 cache 对象封装到 MappedStatement 中
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
}
  1. 现从解析 select|insert|update|delete 语句 开始看看,首先判断有没有配置过 databaseId,现在是没配置过的,然后直接解析下面的 buildStatementFromContext() 来获取 MappedStatement
public class XMLMapperBuilder extends BaseBuilder {
  ...
  private void buildStatementFromContext(List<XNode> list) {
     // 判断是否配置过 databaseId,现在是没的,所以不走这行
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 构建 MappedStatement
    buildStatementFromContext(list, null);
  }
}
  1. 在执行 buildStatementFromContext() 时,会把解析出来的 select|insert|update|delete 语句 交给 XMLStatementBuilder 进行处理(到这发现了,myBatis 所有的具体解析都交给 XML**Builder 来处理),通过 XMLStatementBuilder 解析出来 MappedStatement 对象
public class XMLMapperBuilder extends BaseBuilder {
  ...
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // MappedStatement 解析器
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析 select 等 4 个标签,创建 MappedStatement 对象
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
}
  1. 通过 parseStatementNode(),可以看到里面开始解析像 id 属性databaseId 属性flushCache 属性 等,然后通过 builderAssistant 对象,构建 MappedStatement 对象
public class XMLStatementBuilder extends BaseBuilder {
  ...
  protected final Configuration configuration; // 这是 BaseBuilder 的属性
  private final MapperBuilderAssistant builderAssistant;
  ...
  public void parseStatementNode() {
    // 获取statement的id属性(特别关键的值)
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 通过 builderAssistant,获取到 key:user.findUserById!selectKey
    // 判断是不是 configuration 中,已经包含了这个 MappedStatement 对象,是就返回
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    String nodeName = context.getNode().getNodeName();
    //  解析 SQL 命令类型是什么?确定操作是 CRUD 中的哪一种,否则不会知道是哪个操作
    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> 标签解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 获取入参类型
    String parameterType = context.getStringAttribute("parameterType");
    // 别名处理,获取入参对应的 Java 类型
    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,封装 SQL 语句(未参数绑定)和入参信息
    // 问题:sql 占位符如何进行的替换?动态 sql如何进行的解析?
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    // 设置默认 StatementType 为 Prepared,该参数指定了后面的 JDBC 处理时,采用哪种 Statement
    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");
    // 别名处理,获取返回值对应的 Java 类型
    Class<?> resultTypeClass = resolveClass(resultType);
    // 获取 ResultMap
    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");

    // 通过构建者助手,创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}
  1. builderAssistant 对象,把 MappedStatement 对象放入 Configuration
public class MapperBuilderAssistant extends BaseBuilder {
  protected final Configuration configuration; // 这个是 BaseBuilder 的属性
  ...
  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) {
    ...
    // id 就是文件的命名空间 + 方法名,这里是 user.findUserById
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //利用构建者模式,去创建MappedStatement.Builder,用于创建MappedStatement对象
    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);// 将 cache 对象存入到 MappedStatement 中
    ...
    // 将 MappedStatement对象存储到 Configuration 中的 Map 集合中
    // key 为 statement 的 id,value 为 MappedStatement 对象,
    // 现在 debug 的 id 是 user.findUserById
    configuration.addMappedStatement(statement);
    return statement;
  }
}
  1. 其中,Configuration 就会用 Map 来存储
public class Configuration {
  ...
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  ...
  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms); //# id 就是 user.findUserId
  }
}
  1. 总结
    在这里插入图片描述

问题:sql 占位符如何进行的替换?动态 sql 如何进行的解析?

  1. 继续来看 XMLStatementBuilder 是怎么解析 <select>、<insert>、<update>、<delete> 标签的,首先先获取一个能解析内容(是 xml 还是其他语言的写的)的驱动器
public class XMLStatementBuilder extends BaseBuilder {
  ...
  public void parseStatementNode() {
    ...
    LanguageDriver langDriver = getLanguageDriver(lang);
    ...
  }
}
  • 驱动器如下
    在这里插入图片描述
  1. 因为初始化Configuration 已经对这些驱动器初始化过,就从里面获取对应驱动器
public class XMLStatementBuilder extends BaseBuilder {
  ...
  private LanguageDriver getLanguageDriver(String lang) {
    Class<? extends LanguageDriver> langClass = null;
    if (lang != null) {
      langClass = resolveClass(lang);
    }
    return configuration.getLanguageDriver(langClass);
  }
}
  1. 从代码层面输入是 null,就获取默认的内容驱动器。即 XMLLanguageDriver
public class Configuration {
  ...
  public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
    if (langClass == null) {
      return languageRegistry.getDefaultDriver();
    }
    languageRegistry.register(langClass);
    return languageRegistry.getDriver(langClass);
  }
}
  1. 拿到驱动驱动器以后,开始解析 Xnodecontext 内容了
public class XMLStatementBuilder extends BaseBuilder {
  ...
  public void parseStatementNode() {
    ...
    // *******创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
    // 问题:sql占位符如何进行的替换?动态 sql 如何进行的解析?
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    ...
  }
}
  • 其中 context 封装的是例如下面的这样的语句,parameterTypeClass 就是参数的类型
   <select resultType="com.itheima.pojo.User" parameterType="int" id="findUserById">
    SELECT id, name FROM  user WHERE id = #{id}
  </select>
  1. XMLLanguageDriver 首先创建一个 XMLScriptBuilder 来辅助解析
public class XMLLanguageDriver implements LanguageDriver {
  ...
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 初始化了动态 SQL 标签处理器
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    // 解析动态 SQL
    return builder.parseScriptNode();
  }
}
  1. 在创建 XMLScriptBuilder 时,先初始化各种动态 sql 标签的处理器,然后返回对象
public class XMLScriptBuilder extends BaseBuilder {
  ...
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
  ...
  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    // 初始化动态SQL中的节点处理器集合
    initNodeHandlerMap();
  }
  
  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }
}
  1. XMLLanguageDriver 就会利用 XMLScriptBuilder 解析 context 内容,首先先解析出动态节点
public class XMLScriptBuilder extends BaseBuilder {
  ...
  /**
   * 解析 select\insert\ update\delete 标签中的SQL语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 集合中
   * @return
   */
  public SqlSource parseScriptNode() {
    // ****将带有 ${} 号的 SQL 信息封装到 TextSqlNode
    // ****将带有 #{} 号的 SQL 信息封装到 StaticTextSqlNode
    // ****将动态 SQL 标签中的 SQL 信息分别封装到不同的 SqlNode中
    MixedSqlNode rootSqlNode = parseDynamicTags(context); // context 就是 /select标签
    SqlSource sqlSource;
    // 如果SQL中包含 ${} 和动态 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
    if (isDynamic) {
      ...
    } else {
      ...
    }
    return sqlSource;
  }
}
  1. parseDynamicTags() 中,会遍历 context 的子节点,包括判断子节点是 CDATA_SECTION_NODE 类型、还是纯文本 TEXT_NODE 还是动态那种 ELEMENT_NODE 类型,分别进行不同解析。因为现在的子节点就 <#text> SELECT id, name FROM user WHERE id = #{id} </#text> 这样的文本内容,同时没带有 ${} 这样的替换符就只能能表示静态节点,直接加到 contents 中,完成后封装成 MixedSqlNode 返回
public class XMLScriptBuilder extends BaseBuilder {
  ...
  private final XNode context;
  private boolean isDynamic;
  private final Class<?> parameterType;
  ...
  /**
   * 解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 集合中
   *
   * - 将带有 ${}号的 SQL 信息封装到 TextSqlNode
   * - 将带有 #{}号的 SQL 信息封装到 StaticTextSqlNode
   * - 将动态 SQL 标签中的 SQL 信息分别封装到不同的 SqlNode 中
   * @param node
   * @return
   */
  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    // 获取 <select>\<insert> 等4个标签的子节点,子节点包括元素节点和文本节点
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      // 处理文本节点
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");// 获取到 SELECT id, name FROM  user WHERE id = #{id}
        // 将文本内容封装到SqlNode中
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // SQL 语句中带有 ${} 的话,就表示是 dynamic 的
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          // SQL语句中(除了 ${} 和下面的动态SQL标签),就表示是 static 的
          // StaticTextSqlNode 的 apply 只是进行字符串的追加操作
          contents.add(new StaticTextSqlNode(data));
        }
        //处理元素节点
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        // 动态SQL标签处理器
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        // 动态SQL标签是dynamic的
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }
}
  1. 获取 **MixedSqlNode ** 之后,如果是节点包含 ${} 或者动态 sql 还得进一步解析获取 DynamicSqlSource,现在只是简单文本类型直接获取 RawSqlSource 即可
public class XMLScriptBuilder extends BaseBuilder {
  ...
  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context); // 解析完成
    SqlSource sqlSource;
    
    // 如果 SQL 中包含 ${} 和动态 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 如果 SQL 中包含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
}
  1. 其中,创建 RawSqlSource 时,里面会创建 SqlSourceBuilder 对象,来解析 SQL 语句 SELECT id, name FROM user WHERE id = #{id},并传入对应参数类型。主要是为了替换 #{id}?,然后那到 id 属性,以及标记它的类型,存到 ParameterMapping
public class RawSqlSource implements SqlSource {
  ...
  private final SqlSource sqlSource;
  ...
  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    // 先调用 getSql(configuration, rootSqlNode) 获取 sql,再走下面的构造函数
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
  
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    // 解析 SQL 语句
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    // 获取入参类型
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    // 开始解析
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
}
  1. 下面就是一些解析工作原理,先创建一个 ParameterMappingTokenHandler,处理分词后的 token,然后利用 GenericTokenParser 解析#{} 以及内容,最后封装到 StaticSqlSource
public class SqlSourceBuilder extends BaseBuilder {
  ...
  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);
    }
    // 将解析之后的 SQL 信息,封装到 StaticSqlSource 对象中
    // SQL 字符串是带有?号的字符串,? 相关的参数信息,封装到 ParameterMapping 集合中
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
}
  1. 其中,分析器先拆分工作,先找到对应的 ${},并把分离出来的 id 文本,交给 ParameterMappingTokenHandler 处理
public class GenericTokenParser {
  ...
  public String parse(String text) {
    ...
    do {
        ...
        if (end == -1) {
          ...
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    } while (start > -1);
    ...
    return builder.toString();
  }
}
  1. ParameterMappingTokenHandler 会把 id 这个文本存入自己的 parameterMappings 属性中,然后返回替换文本 ?,最后把预编译的 sql 和 parameterMappings 一起封装到 StaticSqlSource
public class SqlSourceBuilder extends BaseBuilder {
  ...
  private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
    ...
    @Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }
  }
}
  1. 得到替换后的 sqlSource 后,同时检查 Xnodecontext 节点属于哪种 StatementType ,然后借助 builderAssistant 创建 MappedStatement 对象
public class XMLStatementBuilder extends BaseBuilder {
  ...
  public void parseStatementNode() {
    ...
    // 问题:sql 占位符如何进行的替换?动态 sql 如何进行的解析?
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    // 设置默认 StatementType 为 Prepared,该参数指定了后面的JDBC处理时,采用哪种 Statement
    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");
    // 别名处理,获取返回值对应的 Java 类型
    Class<?> resultTypeClass = resolveClass(resultType);
    // 获取ResultMap
    String resultMap = context.getStringAttribute("resultMap");
     ...
    // 通过构建者助手,创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    ...
  }
}
  1. 总结
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值