MyBatis之SqlSessionFactory 的初始化流程

使用 XML 文件构建 SqlSessionFactory

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactoryBuilder:

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

解释:

1,通过输入流来构建一个 SqlSessionFactory 对象。new了一个 DefaultSqlSessionFactory 对象

build() 方法:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 根据我们传入的文件,创建一个能够解析我们当前文件的解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 构建一个 SqlSessionFactory 的对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

parser:

MyBatis之SqlSessionFactory 的初始化流程_第1张图片

调用 parser.parse() 方法进入到 XMLConfigBuilder 类中:

private final XPathParser parser  

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 调用解析配置的方法
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

解释:

1,调用 parser.evalNode("/configuration") 来获取 configuration 标签下的所有内容

2,调用  parseConfiguration(parser.evalNode("/configuration")); 来解析parser.evalNode("/configuration")得到的内容

首先调用  parser.evalNode("/configuration") 方法来到了 XPathParser 类中:

  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }

 解释:这个方法就是获取指定节点下的内容包括此节点。这个方法会返回一个 XNode 对象。

这个方法的作用大家可以记一下,后面的流程会多次调用此方法。

点这个view就可以看见 

MyBatis之SqlSessionFactory 的初始化流程_第2张图片

获取完内容之后,然后调用 parseConfiguration(XNode root) 来解析配置:

  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      //typeAliases 标签 别名
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //设置 settings 标签中能写的配置
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //环境标签
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // mappers 标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

 解释:这里获取的配置,就是我们 configuration 标签中能写的配置/标签。的这些方法都是解析标签的,没什么好说的。我们说一下这个方法 mapperElement(root.evalNode("mappers"));

先看一下 mappers 标签里面能写什么:

    
        
        
        
        
    

调用 mapperElement(root.evalNode("mappers")):

 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");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(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);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            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.");
          }
        }
      }
    }
  }

解释:

1,首先判断 parent 是否为null,parent就是 mappers 标签

2,然后调用 parent.getChildren() 来获取到 mappers 标签下的所有子标签,然后循环

3,获取到子标签的名字,判断名字是什么以此来决定进入到哪个判断里面。名字为 package 进入到 if 里面,否则就进入到 else 里面(mapper)。我写的是 mapper 标签。

4,通过 child.getStringAttribute("") 方法来获取 mapper 标签上的属性内容,然后就开始判断,你写了哪个属性它就会进入到哪个判断里面,写多个属性就会抛异常。判断里面调用的方法都是一样的。我写的是 resource。

5,InputStream inputStream = Resources.getResourceAsStream(resource) 通过 resource 拿到输入流

6,XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 又拿到一个 mapper 的解析器,然后调用这个解析器的 parse() 方法 mapperParser.parse();

XMLMapperBuilder 类中 mapperParser.parse() 方法: 

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 配置元素
      configurationElement(parser.evalNode("/mapper"));
      // 添加到 protected final Set loadedResources = new HashSet<>(); 中
      configuration.addLoadedResource(resource);
      // 通过 namespace 的值,为接口绑定映射文件
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

 configurationElement(parser.evalNode("/mapper")):

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置当前的命名空间
      builderAssistant.setCurrentNamespace(namespace);
      //缓存相关
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      //parameterMap标签
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //resultMap标签 结果集映射
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //sql标签 避免重复sql
      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);
    }
  }

解释:首先拿到 命名空间,如果没写,会抛一个异常。

下面这些方法都是拿到指定标签下的内容(包括指定标签),然后就设置进去。重点看一下这个方法: buildStatementFromContext(context.evalNodes("select|insert|update|delete"));:

首先调用 context.evalNodes("select|insert|update|delete") 来获取所有的增删改查标签:

  public List evalNodes(String expression) {
    return xpathParser.evalNodes(node, expression);
  }

  public List evalNodes(Object root, String expression) {
    List xnodes = new ArrayList<>();
    NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
    for (int i = 0; i < nodes.getLength(); i++) {
      xnodes.add(new XNode(this, nodes.item(i), variables));
    }
    return xnodes;
  }

解释:这个方法就是获取到 mapper文件下的所有增删改查标签添加进一个list集合里并返回。 

buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 通过前一个方法获取到节点内容来构建 sql 语句:

  private void buildStatementFromContext(List list) {
    if (configuration.getDatabaseId() != null) {
      //调用构建方法
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    //调用构建方法
    buildStatementFromContext(list, null);
  }
 
 private void buildStatementFromContext(List list, String requiredDatabaseId) {
    for (XNode context : list) {
      //获取到语句解析器 每一个标签都会有一个语句解析器
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析节点
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

解释:遍历刚才拿到的增删改查标签,然后又获取到一个语句解析器,通过语句解析器来解析语句节点。 

语句解析器:

MyBatis之SqlSessionFactory 的初始化流程_第3张图片

调用 statementParser.parseStatementNode(); 方法来到 XMLStatementBuilder 类中:

  public void parseStatementNode() {
    //获取到标签的id属性的值
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

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

    //获取到标签名
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

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

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

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

    // Parse the SQL (pre:  and  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 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");

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

解释:拿到增删改查标签能写的所有属性的值,然后把这些值全部传入一个方法中,并构建出 MappedStatement 对象。所以说:每一个增删改查标签就是一个 MappedStatement 对象。

调用 builderAssistant.addMappedStatement(); 方法来到 MapperBuilderAssistant 类中:

  public MappedStatement addMappedStatement(...) {
    ...

    //此方法用以构建id
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

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

    ...

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

解释:构建出一个 MappedStatement 对象,并通过 addMappedStatement() 方法放入到 configuration 对象的 mappedStatements 属性中:

  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

解释:这个方法把 MappedStatement 对象放入mappedStatements(Map) 中,拿id充当键,值为它本身。(id属性是由 namespace 的值+方法名组成的,这也就是 dao 接口中的方法不能重载的原因)。

到这儿基本就结束了,此时就一路返回。

你可能感兴趣的:(java,intellij-idea,开发语言)