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

news2024/11/23 21:59:49

解析映射配置文件

  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");

    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) {
    ...
    // 将 MappedStatement对象存储到 Configuration 中的 Map 集合中
    // key 为 statement 的 id,value 为MappedStatement 对象
    configuration.addMappedStatement(statement);
    return statement;
  }
}
  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. 总结
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/549832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt之界面 自定义标题栏、无边框、可移动、缩放

实现效果 注意&#xff1a;由于需要调用 Windows 上的头文件与库&#xff0c;所以不能跨平台&#xff0c;只支持 Windows 系统。如果想要跨平台&#xff0c;可以使用鼠标等事件实现&#xff0c;具体百度搜索参考下 自定义标题栏 titleBar.h #ifndef TITLEBAR_H #define TITL…

[Nacos] Nacos Client向Server发送注册请求和心跳请求 (二)

文章目录 1.Nacos Client的自动注册原理和实现2.Naocs Client向Server发送注册请求3.Nacos Client向Server发送心跳请求 Nacos Client的任务: 向Server发送注册请求, 向Server发送心跳请求, Client获取所有的服务, Client定时更新本地服务, Client获取要调用服务的提供者列表 …

Robot Dynamics Lecture Notes学习笔记之关节空间动力学控制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 关节空间动力学 关节空间动力学控制关节阻抗调节重力补偿逆动力学控制 关节空间动力学控制 目前的工业机器人几乎完全依赖于关节位置控制的概念。它们建立在PID控制器的基础…

MySQL数据库期末实验报告(含实验步骤和实验数据)

MYSQL实验 实验步骤 1.创建数据库salesmanage 2.创建数据表&#xff1a;员工表&#xff0c;部门表&#xff0c;销售表&#xff1b; &#xff08;1&#xff09;员工表&#xff08;(员工号(CHAR)&#xff0c;员工姓名(CHAR)&#xff0c;性别(CHAR)&#xff0c;年龄(INT)&…

前端部署项目后nginx转发接口404(页面正常)

目录 1.前言 2. 场景复现&#xff1a; 3.问题的原因&#xff1a; 4.使用nginx一般要注意的小细节&#xff1a; 1. location / 写在下面&#xff0c;其他的转发如/v1写在上面​编辑 2.如何查看nginx转发请求到哪里了&#xff1f; 3.怎么写自己的前端路径&#xff1f; 5.使…

实验六 自动驾驶建模与仿真

【实验目的】 了解Matlab/Simulink软件环境&#xff0c;熟悉Simulink建模步骤&#xff1b;了解车辆运动控制的基本原理&#xff0c;学会简单的车辆运动控制建模及仿真&#xff1b;了解自动驾驶建模的基本过程&#xff0c;了解典型ADAS系统模型的应用特点。了解自动驾驶相关函数…

【SpringCloud组件——Nacos】

前置准备&#xff1a; 分别提供订单系统&#xff08;OrderService&#xff09;和用户系统&#xff08;UserService&#xff09;。订单系统主要负责订单相关信息的处理&#xff0c;用户系统主要负责用户相关信息的处理。 一、服务注册与发现 1.1、在父工程当中引入Nacos依赖 …

JavaScript实现输入数值判断是否为质数、合数的代码

以下为实现输入数值判断是否为质数、合数的程序代码和运行截图 目录 前言 一、输入数值判断是否为质数、合数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博文代码可…

通讯录实现的需求分析和架构设计

本文实现的是通讯录产品的需求分析和架构设计&#xff0c;重点在于结构层次的设计&#xff0c;方便代码阅读和维护。 一、通讯录实现的需求分析 1、通讯录的功能清单 添加一个人员打印显示所有人员删除一个人员查找一个人员保存文件加载文件 2&#xff0c;数据存储信息 人员…

实际开发中一些实用的JS数据处理方法

写在开头 JavaScript 是一种脚本语言&#xff0c;最初是为了网页提供交互式前端功能而设计的&#xff0c;而现在&#xff0c;通过 Node.js&#xff0c;JavaScript 还可以用于编写服务器端代码。 JavaScript 具有动态性、基于原型的面向对象特性、弱类型、多范式、支持闭包执行…

Golang每日一练(leetDay0072) 课程表 I\II Course Schedule

目录 1. 课程表 Course Schedule I &#x1f31f;&#x1f31f; 2. 课程表 Course Schedule II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一…

电子邮件协议(SMTP,MIME,POP3,IMAP)

SMTP 关键词&#xff1a; 电子邮件协议:SMTP简单邮件传输协议&#xff0c;负责将邮件上传到服务器&#xff0c;采用TCP的25端口&#xff0c;C/S工作。仅传送ASCII码文本 详细介绍&#xff1a; SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上…

学系统集成项目管理工程师(中项)系列23b_信息系统集成及服务管理(下)

1. 信息技术服务 1.1. 供方为需方提供如何开发、应用信息技术的服务&#xff0c;以及供方以信息技术为手段提供支持需方业务活动的服务 1.2. 信息技术咨询服务、设计与开发服务、信息系统集成服务、数据处理和运营服务及其他信息技术服务 2. 信息系统审计 2.1. 收集并评估证…

Golang中的协程(goroutine)

目录 进程 线程 并发 并行 协程(goroutine) 使用sync.WaitGroup等待协程执行完毕 多协程和多线程 进程 进程就是程序在操作系统中的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;进程是一个动态概念&#xff0c;是程序在执行过程中分配和管理…

C语言_用VS2019写第一个C语言或C++程序

接上一篇&#xff1a;C语言简述、特点、常用编译器&#xff0c;VS2010写第一个C语言程序 本次来分享用VS2019来写C语言或C程序&#xff0c;也是补充上一篇的知识&#xff0c;话不多说&#xff0c;开始上菜&#xff1a; 此博主在CSDN发布的文章目录&#xff1a;我的CSDN目录&…

微信小程序nodejs+vue+uniapp超市网上购物商城系统

超市购物系统用户端要求在系统的安卓手机上可以运行&#xff0c;主要实现了管理端&#xff1b;首页、个人中心、用户管理、商品分类管理、商品信息管理、商品入库管理、订单信息管理、订单配送管理、订单评价管理、退货申请管理、换货申请管理、系统管理&#xff0c;用户端&…

总结857

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

4-《安卓进阶》

4-《安卓进阶》 1 Okhttp2 Retrofit3 Android常用图片库对比4 Glide原理手写图片加载框架思路5 Rxjava6 Android IPC机制&#xff08;面试八股文之一&#xff09;6.1.Android中进程和线程的区别6.2.IPC概念6.3.Android序列化与反序列化6.3.Android如何开启多进程&#xff1f;多…

MDIO总线

基于linux-3.14.16 首先要搞清楚总线的位置&#xff0c;即硬件上的位置 如上图&#xff0c;mdio总线是mac和phy之间的连接方式&#xff0c;主要用于配置配置phy的寄存器&#xff0c;所以phy应该是器的一类物理设备&#xff0c;mdio总线驱动和总线设备都是围绕phy工作的。 一…

一图看懂 async_timeout 模块:异步 I/O 的超时设置,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 async_timeout 模块&#xff1a;异步 I/O 的超时设置&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x1f9ca;类关系图…