MyBatis原理--配置解析

MyBatis解析配置文件的入口在:

public class SqlSessionFactoryBuilder {

    //一般单独使用MyBatis的话配置文件解析的入口是这个方法,reader是对配置文件的封装,一般environment和properties都是null
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //parse.parse()返回的是一个Configuration对象,Configuration对象中包含了所有的配置信息
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //如果使用spring + mybatis,SqlSessionFactoryBean会调用这个方法完成配置文件的初始化。
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

继续看XMLConfigBuilder.java

public class XMLConfigBuilder entends BaseBuilder  {
    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    //XPathParser主要是解析XML文档,将其转化为一个Document对象
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

  //这边可以看到parse方法的本体了,
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each MapperConfigParser can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

  //解析configuration节点,下面这个方法根据名字基本上都能判定是做什么事情的了。(论名字的重要性)
    private void parseConfiguration(XNode root) {
      try {
        propertiesElement(root.evalNode("properties")); //issue #117 read properties first
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        settingsElement(root.evalNode("settings"));
        environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
      } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
      }
  }
}

关于MyBatis配置相关的一些解释可以参考这里,看初始化配置这部分代码的时候,最好心里对各种配置有个概念。

下面主要看一下对于mappers的解析:

public class XMLConfigBuilder extends BaseBuilder {
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //将包下面所有的父类是Object的类(除去接口,虚拟类),都添加到configuration.mappers中。
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          //resource,url,class三个属性必定有一个要存在,resource或者url都是指明mapper文件的位置,获取到文件之后交给XMLMapperBuilder处理。
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            //将mapperClass对应的class对象添加到configuration.mappers中。
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

}

看一下XMLMapperBuilder的内容:

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

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  public void parse() {
    //首先会判断一下是否以及load过该resource
    if (!configuration.isResourceLoaded(resource)) {
      //主要逻辑在这个里面
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    //如果已经解析过,看是否还有解析失败的
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      builderAssistant.setCurrentNamespace(namespace);
      //可以看到,这边分别解析不同节点
      //cache-ref,cache,parameterMap(官方表示已过时),resultMap这几个基本上都是获取到各个参数,然后将其赋值给对象的响应属性。
      //最后会lock一下对象中的collection,将其变为不可修改的。然后再将其放入configuration。
      //sql的解析也差不多是这样,不过sql的解析结果最后会让如sqlFragments中。
      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 RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

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

  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.inCompleteStatement中,等着后面再去处理。
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

}

XMLStatementBuilder具体内容:

public class XMLStatementBuilder extends BaseBuilder {
  //构造方法也只是赋值而已
  public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
    super(configuration);
    this.builderAssistant = builderAssistant;
    this.context = context;
    this.requiredDatabaseId = databaseId;
  }

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

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

    //获取各种参数,并将部分参数转化为具体的Java对象
    Integer fetchSize = context.getIntAttribute("fetchSize", null);
    Integer timeout = context.getIntAttribute("timeout", null);
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    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
    //如果包含了inculde,用原始sql语句进行替换
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes,
    // in case if IncompleteElementException (issue #291)
    //解析selectKey,根据代码来看,如果你在同一个语句中声明了多个selectKey,那么只有最后一个会生效。
    //selectKey的解析过程跟具体sql语句的解析过程比较相像,selectKey最后也会变为一个MappedStatement对象
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //selectKey和include解析之后已经被移除了,具体逻辑后面有
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);


    //设置id生成器
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    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))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    //这里就是给MappedStatement设置各种属性了,其中resultMap,parameterMap会根据名字,找到对应的对象,如果名字为空那么会根据resultType, parameterType来处理
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver);

    //至此,一个statement节点的解析就完成了
  }

//解析selectKey的过程
  public void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
      //这个地方可以看出来,解析过程中所有selectKey节点用的id都是一样的(父节点的id+一个常量)
      String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      String databaseId = nodeToHandle.getStringAttribute("databaseId");
      if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
        parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
      }
    }
  }

  public void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    //defaults
    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = new NoKeyGenerator();
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;

    //这里返回的是DynamicSqlSource对象,sql语句在这里被转化为了SqlSource对象
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;

    //这里就是给MappedStatement设置各种属性了,其中resultMap,parameterMap会根据名字,找到对应的对象,如果名字为空那么会根据resultType, parameterType来处理
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, null, databaseId, langDriver);

    id = builderAssistant.applyCurrentNamespace(id, false);

    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    //添加key生成器
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
    nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
  }
  //到这里selectKey的解析完成了,可以回头继续看了。
}

SqlSource的生成过程这里看一下XMLLanguageDriver怎么做的:

public class XMLLanguageDriver implements LanguageDriver {
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script);
    return builder.parseScriptNode();
  }
}


public class XMLScriptBuilder extends BaseBuilder {

  private XNode context;

  public XMLScriptBuilder(Configuration configuration, XNode context) {
    super(configuration);
    this.context = context;
  }

  public SqlSource parseScriptNode() {
    //主要解析在这个方法里面
    List<SqlNode> contents = parseDynamicTags(context);
    //简单的封装
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    return sqlSource;
  }

  //该方法里面将sql语句封装成了SqlNode,像动态语句支持的<if>,<where>等,不同的节点被封装成了不同的SqlNode,所以返回会是一个list
  private List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      String nodeName = child.getNode().getNodeName();
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
          || child.getNode().getNodeType() == Node.TEXT_NODE) {
      //如果是内容节点或者CDATA节点
        String data = child.getStringBody("");
        contents.add(new TextSqlNode(data));
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE && !"selectKey".equals(nodeName)) { // issue #628
        //如果是元素节点(<if>,<where>这样就作为一个元素节点)

        //这里根据不同的节点名称返回不同的handler对象,handler对象有WhereHandler, SetHandler, IfHandler, ForEachHandler等
        NodeHandler handler = nodeHandlers.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
      }
    }
    return contents;
  }
}

解析的主要逻辑就是上面这些,继续看一下其他的:
还是XMLMapperBuilder.parse方法

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //上面的都是这个方法的内容
      configurationElement(parser.evalNode("/mapper"));

      // 将resource添加到已解析的列表中
      configuration.addLoadedResource(resource);
      //将该resource的namespace对应的class添加到已解析列表中(如果对应class不存在,则忽略)
      bindMapperForNamespace();
    }

    //重新解析之前由于异常而暂时没有处理的resultMap,cache,statement。
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

解析完毕!

配置解析其实没那么复杂,主要是需要对配置文件的格式有概念,具体代码不要担心第一遍看完不知其所以然,多看两遍就OK了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值