Mybatis源码(二)mybatis的mapper文件解析

由于上篇博客,介绍了主配置文件的加载过程,至于mapper的文件的解析,也是讲了但是不够详细,这篇博客我决定重写梳理一遍,首先我们先看我们运行代码的以及对应的mapper文件,具体的内容如下:

public class Test {
  public static void main(String[] args) throws Exception {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //xml解析完成
    //其实我们mybatis初始化方法 除了XML意外 其实也可以0xml完成
    //new SqlSessionFactoryBuilder().b
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    Configuration configuration = sqlSessionFactory.getConfiguration();
    //使用者可以随时使用或者销毁缓存
    //默认sqlsession不会自动提交
    //从SqlSession对象打开开始 缓存就已经存在
    SqlSession sqlSession = sqlSessionFactory.openSession();
    sqlSession.close();
  }
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <!-- 使用jdbc事务管理 -->
      <transactionManager type="JDBC" />
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver" />
        <property name="url"
                          value="jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf-8" />
        <property name="username" value="root" />
        <property name="password" value="5201314ysX@" />
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mapper/DemoMapper.xml"></mapper>
  </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.DemoMapper">
  <select id="selectAll" parameterType="String" resultType="Map">
    select * from test
    <where>
      1=1
      <if test="param2 != null and param2 != ''">
        and age = #{param2}
      </if>
      and id =${param2}
    </where>
  </select>
</mapper>
public interface DemoMapper {
  List<Map<String,Object>> selectAll(String name,String age);
}

上面的mapper文件基本上涵盖了所有应该有的情况了,废话不多说,我们直接上代码,具体的代码如下:

public class XMLMapperBuilder extends BaseBuilder {
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //绑定Namespace里面的Class对象
      bindMapperForNamespace();
    }
    //重新解析之前解析不了的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
}

上面的代码走来会判断Set<String> loadedResources集合中又没有mapper/DemoMapper.xml这个值,这个是主配置文件中配置,判断这个mapper文件有没有加载,如果加载了这儿就不会执行if中代码,由于我们这儿是第一次执行所以这个值返回的true,我们继续看后买呢重要的方法configurationElement(parser.evalNode("/mapper"));这个方法就是解析DemoMapper.xml文件,同时将mapper这个节点传入进去,我们跟进对应代码,具体的代码如下:

public class XMLMapperBuilder extends BaseBuilder {
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      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);
    }
  }
}

走来先获取namespace的属性,这儿的namespacecom.DemoMapper,然后设置到builderAssistantcurrentNamespace属性中去。后面的两个方法都是解析缓存的节点,后面我会着重说明一下。然后解析parameterMap节点,这个时候这个节点中内容会创建一个ParameterMapping对象,最终会添加到configuration中的parameterMaps Map<String, ParameterMap>变量中去,然后再看解析resultMap节点,最终会创建一个ResultMap对象,然后加他加入到configuration中的resultMaps Map<String, ResultMap>,然后再解析sql节点,最终是将这个节点下的内容填充到configuration的sqlFragments Map<String, XNode>。 这儿上篇博客,已经讲了,但是下面的buildStatementFromContext(context.evalNodes("select|insert|update|delete"));方法,讲的不够详细,今天着重讲这个方法。

public class XMLMapperBuilder extends BaseBuilder {
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
}

上面的databaseId是在配置多数据源的时候有用,根据对应的databaseId调用不同的数据源。这儿只有一个数据源,就直接传null,默认的就可以了。我们继续跟进 buildStatementFromContext(list, null);方法,具体的代码如下:

public class XMLMapperBuilder extends BaseBuilder {
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //解析xml节点
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        //xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

这儿做了一个如果解析不了直接将其添加到incompleteStatements变量中去,这个是一个链表,因为有些东西可能是先使用,后定义的,就会导致报错,这儿直接添加到这个链表中去,然后后面再解析一次,我们继续跟进 statementParser.parseStatementNode();方法。具体的代码如下:

public class XMLStatementBuilder extends BaseBuilder {
	public void parseStatementNode() {
    //这儿获取的selectAll
    String id = context.getStringAttribute("id");
    //空的
    String databaseId = context.getStringAttribute("databaseId");
    //不会进入此判断
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //select
    String nodeName = context.getNode().getNodeName();
    //SELECT
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //true
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //是否刷新缓存 默认值:增删改刷新 查询不刷新 false
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //是否使用二级缓存 默认值:查询使用 增删改不使用 true
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    
    // 三组数据 分成一个嵌套的查询结果 false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    //替换Includes标签为对应的sql标签里面的值
    includeParser.applyIncludes(context.getNode());

    //解析parameterType属性
    String parameterType = context.getStringAttribute("parameterType");
    //然后从typeAliasRegistry中取,如果没有直接从工作空间加载
    Class<?> parameterTypeClass = resolveClass(parameterType);

    //解析配置的自定义脚本语言驱动 mybatis plus
    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;
    }
    //解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态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");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}

上面的代码代码都是解析对应的属性,然后进行设置,但是最核心的方法还是解析sql语句,对应的方法是langDriver.createSqlSource(configuration, context, parameterTypeClass);,具体的代码如下:

public class XMLLanguageDriver implements LanguageDriver {
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
  
  public SqlSource parseScriptNode() {
    //# $
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      //不解析
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      //用占位符方式来解析
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
}

上面的代码会调用parseDynamicTags(context);方法,也是解析的核心方法,我们跟进去看看,具体的代码如下:

public class XMLScriptBuilder extends BaseBuilder {
  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    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("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }
}

这时候取出来的节点类型Node.TEXT_NODE类型然后会取出data的值,data的值是select * from test,然后又是核心的方法,就是调用textSqlNode.isDynamic()方法,具体的代码如下:

public class TextSqlNode implements SqlNode {
	public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    //创建后面匹配的匹配字符
    GenericTokenParser parser = createParser(checker);
    parser.parse(text);
    return checker.isDynamic();
  }
  
  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }
  
  public String parse(String text) {
    //如果上面传进来的值为空,直接返回
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token 这个时候由于上面创建的所以这儿是${
    int start = text.indexOf(openToken);
    //没有匹配到,直接返回
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    //遍历里面所有的#{} select ?  ,#{id1} ${}
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

先是创建了一个GenericTokenParser对象并指定"${", "}"为匹配到字符,这儿如果没有找到直接返回,很明显这儿没有找到,直接返回text,这儿就是select * from test,然后调用checker.isDynamic();的值,这儿就是false。然后返回到原来的代码的执行的地方。具体的代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  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("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

这儿返回的值是false,所以会执行else中代码,创建一个StaticTextSqlNode对象,然后添加到contents集合中去。这个时候会遍历第二个标签标签,这个时候上面的那个if就不会进去了。而是直接会执行else if中的代码。这个时候获取到nodeName的值是where。这个时候会从nodeHandlerMap中取出where键对应的值,nodeHandlerMap中有九个键,分别是otherwiseforeachsetbindtrimwherechooseifwhen。这个时候取出来的类就是WhereHandler,然后执行WhereHandler中的handleNode的方法,具体的代码如下:

private class WhereHandler implements NodeHandler {
  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
    targetContents.add(where);
  }
}

你会发现又执行到原来的代码,这儿是递归调用,做好准备,和我一起绕。具体执行下面的代码,传入的参数为:and age = #{param2}

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  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("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

这个时候由于where标签中1=1所以直接将这个值加入contents,然后继续解析if标签,这个时候第一个if判断又是不会进,而是直接进入else if中的判断,然后取出nodeName的值为if,然后取出的handler的值为IfHandler,这个时候会调用IfHandler中的handleNode方法,具体的代码如下:

private class IfHandler implements NodeHandler {
	@Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    String test = nodeToHandle.getStringAttribute("test");
    IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
    targetContents.add(ifSqlNode);
  }
}

你会发现又进行了一层俄罗斯套娃,这个时候将 and age = #{parame2} 直接添加到contents中去。这个这个for到循环已经结束了,这个时候返回MixedSqlNode对象,只是将contens的值赋值给MixedSqlNode,这个时候返回上面的代码的执行的地方,然后获取test的属性,这儿的test的值等于param2 != null and param2 != ''这个时候会创建IfSqlNode对象,这个对象中有三个值,分别是testcontentsevaluator=ExpressionEvaluator,然后将这个对象添加到上一个调用他的contents中。这个返回到原来的调用地方,具体的代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  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("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      //从if进去的,这个时候if执行结束,从这返回
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

if的标签执行结束返回到,这儿的contents的值应该是两个,一个是StaticTextSqlNode的对象,中间的值是1=1,然后还有一个是IfSqlNode,就是刚才添加的。这个时候将isDynamic的值改成true,这个时候where标签中的值还没有执行结束,这个时候取出来的值是and id =${param2},注意这儿是$符号,所以我们要跟进去看看parse方法,具体的代码如下:

public String parse(String text) {
    //如果上面传进来的值为空,直接返回
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token 这个时候由于上面创建的所以这儿是${
    int start = text.indexOf(openToken);
    //没有匹配到,直接返回
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    //遍历里面所有的#{} select ?  ,#{id1} ${}
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

最终变成了and id =null,等你发现,你返回的时候,这个返回值没有东西接收,你会发现刚才的修改是无效,有点搞不懂。这个时候这个动态的值,就是true,因为前面再if执行的时候,已经替换过了。这个时候会将and id =${param2}的值添加到contents中去。然后将isDynamic的值设置为true,这个时候这个循环会结束。这个时候会返回MixedSqlNode对象,MixedSqlNode中的contents现在是三个值分别是一个是StaticTextSqlNode,它的值是1=1,然后就是IfSqlNode里面存的是testcontents,还有一个值是TextSqlNode存的值是and id =${param2}。这个时候执行完了where标签了,然后就是下面的代码

private class WhereHandler implements NodeHandler {
  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
    targetContents.add(where);
  }
}

这个时候会执行创建WhereHandler对象的方法,具体的代码如下:

public class WhereSqlNode extends TrimSqlNode {
  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, "WHERE", prefixList, null, null);
  }
}

然后调用的是父类的方法,具体的代码如下:

protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
  this.contents = contents;
  this.prefix = prefix;
  this.prefixesToOverride = prefixesToOverride;
  this.suffix = suffix;
  this.suffixesToOverride = suffixesToOverride;
  this.configuration = configuration;
}

然后将这个where对象添加到contents中返回。具体的代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  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("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      //从if进去的,这个时候if执行结束,从这返回
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

注意这儿的contents中只有两个对象,一个是StaticTextSqlNode其中的值是 select * from test,然后就是WhereSqlNode对象,这个对象中contents中有三个对象,一个StaticTextSqlNode,它的值是1=1,然后就是IfSqlNode对象,它其中也有一个contents集合,它的值是and age = #{param2},然后就是TextSqlNode对象,它其中的值是and id =${param2},记住只要是动态的标签中都有一个contents集合。然后将isDynamic设置为true,最后再将这个总的contents包装到MixedSqlNode对象中去,然后返回,然后执行如下的代码

public class XMLScriptBuilder extends BaseBuilder {
  public SqlSource parseScriptNode() {
    //# $
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      //不解析
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      //用占位符方式来解析
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
}

这个时候isDynamic的值为true,所以会创建DynamicSqlSource对象,具体的代码如下:

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
  this.configuration = configuration;
  this.rootSqlNode = rootSqlNode;
}

可以发现我们就是将这两个属性赋值给DynamicSqlSource对象。然后返回复制给sqlSource变量,最后再返回。上面的代码执行是isDynamic的值为true的情况,那什么时候isDynamic的值为false?为false又会执行什么代码呢?解答第一个问题,我们看了解析的代码当出现$和动态标签时候都会将这个isDynamic变成true,其他的情况都是false。为false的时候执行的代码如下:

public class RawSqlSource implements SqlSource {
  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
	
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
  
  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);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
}

上面的代码就会将sql中的#{}转成,同时将返回StaticSqlSource对象。

最后执行的代码如下:

public class XMLStatementBuilder extends BaseBuilder {
	public void parseStatementNode() {
    //这儿获取的selectAll
    String id = context.getStringAttribute("id");
    //空的
    String databaseId = context.getStringAttribute("databaseId");
    //不会进入此判断
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //select
    String nodeName = context.getNode().getNodeName();
    //SELECT
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    //true
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //是否刷新缓存 默认值:增删改刷新 查询不刷新 false
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //是否使用二级缓存 默认值:查询使用 增删改不使用 true
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    
    // 三组数据 分成一个嵌套的查询结果 false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    //替换Includes标签为对应的sql标签里面的值
    includeParser.applyIncludes(context.getNode());

    //解析parameterType属性
    String parameterType = context.getStringAttribute("parameterType");
    //然后从typeAliasRegistry中取,如果没有直接从工作空间加载
    Class<?> parameterTypeClass = resolveClass(parameterType);

    //解析配置的自定义脚本语言驱动 mybatis plus
    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;
    }
    //解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态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");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}

然后将所有的属性读出来,最终创建一个MappedStatement对象,添加到configurationmappedStatements变量中去,至此整个mapper文件解析已经讲完了。后面会讲mybatis的执行流程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值