Mybatis source code initialization of mybatis

Keywords: Programming xml SQL Mybatis Java

The initialization of Mybatis is completed by SqlSessionFactoryBuilder. The main work is to parse the XML file, encapsulate the parsed class contents into the Configuration class, and finally encapsulate the Configuration class into SqlSessionFactory and return it. Since then, the initialization is completed.

Three classes, XMLConfigBuilder, XMLMapperBuilder and XMLStatementBuilder, are used to complete the parsing of XML files:

  • XMLConfigBuilder: responsible for parsing the mappers node in the global configuration file (mybatis-config.xml).
  • Xmlmaperbuilder: copy and parse the cache ref, cache, parameterMap, resultMap and sql nodes in the xxxMapper.xml mapping file.
  • XMLStatementBuilder: responsible for parsing the SQL statement nodes in the xxxMapper.xml mapping file, such as select, insert, update and delete.
  • XMLScriptBuilder: responsible for parsing SQL scripts and then encapsulating them as SqlSource.

Mybatis initialization process

SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // Delegate to XMLConfigBuilder to parse configuration files
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // Start parsing
    return build(parser.parse());
  } 
  ...
}

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

XMLConfigBuilder

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    // Resolving < Properties > nodes
    propertiesElement(root.evalNode("properties"));
    // Analyze < Settings > nodes
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    // Analyze < typealiases > nodes
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    // Parsing < mappers > nodes
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

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);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // Delegate XMLMapperBuilder to parse the mapping file
          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);
          // Delegate XMLMapperBuilder to parse the mapping file
          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.");
        }
      }
    }
  }
}

XMLMapperBuilder

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);
    // Parsing < cache ref > nodes
    cacheRefElement(context.evalNode("cache-ref"));
    // Parsing < cache > nodes
    cacheElement(context.evalNode("cache"));
    // Parsing < parametermap > nodes
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // Parsing < resultmap > nodes
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    // Parse the < select|insert|update|delete > node
    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);
  }
}

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) {
    // Parse the < select|insert|update|delete > node and delegate it to XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

XMLStatementBuilder

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

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

  // Judge the type of SQL statement according to the node name (select|insert|update|delete)
  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
  // Analyze < include > nodes
  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.
  // Parse the < selectkey > node and delete the < selectkey > node in the XML
  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;
  }

  // Parse the SQL statement and encapsulate it as SqlSource
  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);
}

Mybatis initialization flowchart

Core data structure class

Configuration

Configuration is actually the Java form of XML configuration file. Configuration is a single example, and its life cycle is application level.

ResultMap

Corresponds to the resultMap node in the xxxMapper.xml mapping file. The child nodes in the < resultMap > node are encapsulated with ResultMapping, such as: < ID >, < result > and other nodes.

MappedStatement

It corresponds to the < Select >, < Insert >, < update > and < delete > nodes in the xxxMapper.xml mapping file.

SqlSource

Corresponding to the sql statement in the xxxMapper.xml mapping file, after parsing the statement contained in SqlSource, it finally only contains the following statements: Placeholder, which can be directly submitted to the database for execution;

MapperRegistry

It is the registry of Mapper interface dynamic proxy factory classes. In MyBatis, the InvocationHandler interface is implemented by MapperProxy, and the instance object of dynamic proxy is generated by MapperProxyFactory.

MyBatis builder class diagram

The whole initialization process of Mybatis uses builder mode. Builder mode is more suitable for generating complex objects, mainly focusing on the specific details of object instantiation.

Posted by zigojacko on Tue, 22 Oct 2019 06:03:10 -0700