Continue to “decrypt”, Mybatis is divided into two parts: XML and each mapper. XML (annotation method is slightly different), and generate memory configuration information for later use. This article will introduce the configuration information parsing process of Mybatis.

Start with the initialization entry

We also need to be clear that the primary purpose of initialization is to generate an SqlSessionFactory from an XML configuration file, and then generate an SqlSession from that factory when used. Of course, the specific execution of Sql is delegated to the Executor. Let’s take a look at the previous demo:

// Initial configuration phase
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// Use phase
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.listUsers();
Copy the code

Read the mybatis-config. XML configuration file as an input stream and hand it to the SqlSessionFactoryBuilder constructor to build the sqlSessionFactory.

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {...// 1. Create XMLConfigBuilder based on the configuration file
  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 2. Perform the parsing process to obtain the Configuration global Configuration object
  // 3. Instantiate DefaultSqlSessionFactory based on the configuration object
  returnbuild(parser.parse()); . }Copy the code

Parse () calls the parseConfiguration() method to parse the mybatis-config.xml configuration file in a flow:

  // step by step
  //1.properties
  propertiesElement(root.evalNode("properties"));
  //2. Type aliases
  typeAliasesElement(root.evalNode("typeAliases"));
  / / 3. The plug-in
  pluginElement(root.evalNode("plugins"));
  //4. Object factory
  objectFactoryElement(root.evalNode("objectFactory"));
  //5. Object packaging factory
  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  / / 6. Set up
  settingsElement(root.evalNode("settings"));
  // read it after objectFactory and objectWrapperFactory issue #631
  Configure the database environment
  environmentsElement(root.evalNode("environments"));
  //8. DatabaseIdProvider Specifies the database provider
  databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  // type handler
  typeHandlerElement(root.evalNode("typeHandlers"));
  //10
  mapperElement(root.evalNode("mappers"));
Copy the code

Let’s skip the analysis and focus on the mappers parsing process, which is important and complex. Ok, let’s move on to the mapperElement method:

private void mapperElement(XNode parent) throws Exception {
    if(parent ! =null) {
      for (XNode child : parent.getChildren()) {
        // In package mode, xxXMaper. Java must have the same name as xxxmapper. XML and be in the same path
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // Add to the Map
      
       , MapperProxyFactory
        > knownMappers
      >
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // Load the XML file from the resource path for parsing
          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());
            // Perform parsing
            mapperParser.parse();
          } else if (resource == null&& url ! =null && mapperClass == null) {
            // Load in URL mode
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // Perform parsing
            mapperParser.parse();
          } else if (resource == null && url == null&& mapperClass ! =null) {
            // The class mode is similar to the package mode, but instead of parsing a single fileClass<? > 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.");
          }
        }
      }
    }
  }
Copy the code

By analyzing the last file, we know that the main purpose of this stage is to deposit all mappers into knownMappers, where the key is the specific mapper interface name. For convenience, two copies are stored internally. Fully qualified interface name (” com. Boykait. User. Mapper. UserMapper “) and the specific file name (” UserMapper “), the time corresponding to the specific MapperProxyFactory xxxMaper, do this mapping? You want to know that we have no specific xxxMaper interface implementation class, with what can be used? So, its actual work is completed through the dynamic proxy, and the details will be discussed later. Now we know that the Maper interface performs the corresponding CURD operation through the proxy. Parse () : mapperParser.parse() :

//1. XMLMapperBuilder.parse()
public void parse(a) {
	if(! configuration.isResourceLoaded(resource)) {/ / the key
	  configurationElement(parser.evalNode("/mapper"));
	  configuration.addLoadedResource(resource);
	  bindMapperForNamespace();
	}
	// Handle the previous semi-finished product
	parsePendingResultMaps();
	parsePendingCacheRefs();
	parsePendingStatements();
}
//2. XMLMapperBuilder.configurationElement()
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);
      // Handle the dependency cache. In global Configuration, Map< local namespace, dependency namespace > cacheRefMap
      cacheRefElement(context.evalNode("cache-ref"));
      // Handle Cache global configuration Map< local namespace, Cache object > caches
      cacheElement(context.evalNode("cache"));
      / / invalid
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      ResultMap < namespace of the mapping Configuration resultMapId, ResultMap> resultMaps
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // Process SQL fragments xmlmapperBuilder. Map< namespace. sqlId, root node number of Xnodes > sqlFragments
      sqlElement(context.evalNodes("/mapper/sql"));
      // Create MappedStatement for CURD
      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); }}// 3. XMLMapperBuilder.buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        / / the key
        statementParser.parseStatementNode();
      } catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code

In the development process of concrete, such as query, we usually use the include | if | foreach | where | tirm condition such as to create to meet the needs of the query, so to track the above key to continue, I swear, this is the last time pasting the source code:

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

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

    String nodeName = context.getNode().getNodeName();
	// Specify the CURD operation command
    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
	// Replace the SQL fragment
    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: <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;
    }
    / / which is key to judge a SqlSource DynamicSqlSource | ProviderSqlSource | RawSqlSource | StaticSqlSource
    ${} = DynamicSqlSource #{} = RawSqlSource
    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");
    // Create Map
      
        mappedStatements and store them in the global configuration
      ,>
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
Copy the code

Comb summary

Or lazy, this article ends at this point, here, a brief summary:

  1. The mainline process for initialization
  2. You should know the priority of mybatis Mapper file loading mode
  3. XxxMaper interface method needs to be used by proxy (detailed study later)
  4. Determining and creating SqlSource details (explore later)