Reading the source code of mybatis: initialization of mybatis

Reprinted from Reading the source code of mybatis (2): initialization of mybatis

1. Initialization entry

//Mybatis obtains SqlSession through SqlSessionFactory, and then can interact with the database through SqlSession
private static SqlSessionFactory getSessionFactory() {
    SqlSessionFactory sessionFactory = null;
    String resource = "configuration.xml";
    try {
        sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return sessionFactory;
}

So, let's start with SqlSessionFactoryBuilder. Let's see how the source code is implemented

SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // Read profile
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //Parse the Configuration object and create the DefaultSqlSessionFactory object
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    //Close the input stream object that reads the configuration file
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

XMLConfigBuilder

XMLConfigBuilder is one of many subclasses of BaseBuilder. The core fields are as follows

//Indicates whether it has been resolved
private boolean parsed;
//Objects used to resolve configuration files
private final XPathParser parser;
//The name representing < environment > in the configuration file reads the default attribute by default
private String environment;
// Responsible for and creating Reflector objects
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

The XMLConfigBuilder.parse() method parses the mybatis-config.xml configuration file. It calls the parseConfiguration () method to implement the whole parsing process. The specific implementation is as follows:

/**
   * Entry for parsing configuration file
   * @return
   */
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

  /**
   * Specific parsing process for each node of the configuration file
   * configuration The node is the root node.
   * Under the configuration node, we can configure 11 child nodes,
   * They are: properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory
   * environments,databaseIdProvider,typeHandlers,mappers. 
   * @param root Root node
   */
private void parseConfiguration(XNode root) {
  try {
      // Resolving the properties node
    propertiesElement(root.evalNode("properties"));
      //Parse settings node
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);//Set vfsImpl field
      //Resolving typeAliases nodes
    typeAliasesElement(root.evalNode("typeAliases"));
      //Parsing plugins nodes
    pluginElement(root.evalNode("plugins"));
      //Resolving objectFactory nodes
    objectFactoryElement(root.evalNode("objectFactory"));
      //Resolving the objectWrapperFactory node
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //Resolving reflectorFactory nodes
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
      //Analyze the environments node
    environmentsElement(root.evalNode("environments"));
      //Parsing databaseIdProvider node
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //Parsing typeHandlers node
    typeHandlerElement(root.evalNode("typeHandlers"));
      //Parsing mappers node
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

The propertiesElement() method will parse the properties node in the configuration file and form a Java.util.Properties object. Then set the changed object to the variables fields of XpathParse and configuration. The placeholder is replaced by the information in properties. The specific implementation is as follows:

/**
   * Specific methods of resolving properties
   * @param context
   * @throws Exception
   */
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
      // set the name and value properties of the child node into the properties object
      // Note the order here. xml configuration takes precedence, and external properties configuration takes precedence
       Properties defaults = context.getChildrenAsProperties();
      // Get the value of the resource property on the properties node
      String resource = context.getStringAttribute("resource");
      // Get the value of the url property on the properties node. resource and url cannot be configured at the same time
      String url = context.getStringAttribute("url");
    if (resource != null && url != null) {//url and resource cannot be configured at the same time
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
      // set the resolved properties file into the properties object
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
      // Merge the configured Properties property in the configuration object with the one just resolved
      // The configuration object will load all the node elements of the mybatis configuration file to be parsed, and it will be mentioned frequently in the future
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
      // set the propertis object with resolution configuration into the parser, because it may be used later
    parser.setVariables(defaults);
      // set into configuration object
    configuration.setVariables(defaults);
  }
}

The accessories under the settings node are the global configuration of mybatis, and the properties of the configuration object are modified. Please refer to the official documents for details

/**
 * settings The tag is to set various properties of the configuration object,
 * Please refer to mybatis official document for specific attribute description
 * @param props
 * @throws Exception
 */
private void settingsElement(Properties props) throws Exception {
  configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
  configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
  configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
  configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
  configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
  configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
  configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
  configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
  configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
  configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
  configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
  configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
  configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
  configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
  configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
  configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
  configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
  @SuppressWarnings("unchecked")
  Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
  configuration.setDefaultEnumTypeHandler(typeHandler);
  configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
  configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
  configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
  configuration.setLogPrefix(props.getProperty("logPrefix"));
  @SuppressWarnings("unchecked")
  Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
  configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

Environment element node mainly configures database objects and data sources. Multiple environment subnodes can be configured. If the development environment of our system is different from the database used by the formal environment (this is certain), then two environments can be set, and two IDS correspond to the development environment and the final environment respectively. Then the corresponding environment can be selected by configuring the default attribute of environments, for example , I will configure the value of the default property of environments to development, and then I will select the environment of dev. The specific implementation is as follows

/**
   * The method of parsing the nodes of the environment elements
   * @param context
   * @throws Exception
   */
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //Get the default value in < environments default = "development" >
      environment = context.getStringAttribute("default");
    }
    // Children of circular environments
    for (XNode child : context.getChildren()) {
      // Get the ID in < environment id = "development" > Z
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {//Select the corresponding environment according to the default attribute of environments
        // There are two kinds of mybatis: JDBC and MANAGED. If JDBC is configured, transactions of JDBC will be used directly. If MANAGED is configured, transactions will be MANAGED to containers
        // <transactionManager type="JDBC"/>
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //Under the environment node is the dataSource node. Parse the dataSource node
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // Set dataSource to configuration object
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

The typeAliases node is mainly used to set aliases. In fact, this is a very useful function. By configuring aliases, we do not need to specify a complete package name

/**
 * Resolving typeAliases nodes
 * <typeAliases>
 *     <!--<package name="com.lpf.entity"></package>-->
 *     <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
 * </typeAliases>
 * @param parent
 */
private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //If the child node is package, get the name attribute of the package node, and mybatis will scan the specified package
      if ("package".equals(child.getName())) {
        String typeAliasPackage = child.getStringAttribute("name");
        //TypeAliasRegistry is responsible for managing aliases. This is to register aliases through TypeAliasRegistry
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
          //If the child node is a type alias node, get the alias attribute and the attribute value of type
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
            typeAliasRegistry.registerAlias(clazz);
          } else {
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

Specific alias registration class

public class TypeAliasRegistry {

  // Alias is implemented by a HashMap. key is the alias, and value is the type (class object) corresponding to the alias
  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  /**
   * mybatis By default, it is our registered alias
   */
  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

  /**
   * To process aliases, take them out of the hashMap where the aliases are saved
   */
  @SuppressWarnings("unchecked")
  // throws class cast exception as well if types cannot be assigned
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }


    /**
     * When the configuration file is set to package, scan the JavaBeans under the package, and then register the alias automatically
     * By default, Bean's initial lowercase unqualified class name is used as its alias
     * You can also annotate JavaBeans with @ Alias to define aliases, for example: @ Alias(user)
     */
  public void registerAliases(String packageName){
    registerAliases(packageName, Object.class);
  }

  public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for(Class<?> type : typeSet){
      // Ignore inner classes and interfaces (including package-info.java)
      // Skip also inner classes. See issue #6
      if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
        registerAlias(type);
      }
    }
  }

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }

    //Register alias with hashMap
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    TYPE_ALIASES.put(key, value);
  }

  public void registerAlias(String alias, String value) {
    try {
      registerAlias(alias, Resources.classForName(value));
    } catch (ClassNotFoundException e) {
      throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
    }
  }

    /**
     * Gets the HashMap where the alias is saved. The configuration object holds a reference to the TypeAliasRegistry,
     * Therefore, if necessary, we can get it through the Configuration object
     */
  public Map<String, Class<?>> getTypeAliases() {
    return Collections.unmodifiableMap(TYPE_ALIASES);
  }

}

The resolution of typeHandlers node is similar to that of typealiases node

/**
   * Parsing typeHandlers node
   * Whether MyBatis sets a parameter in the prepared statement,
   * When a value is taken from the result set, the type processor will be used to convert the obtained value to Java type in an appropriate way.
   * Mybatis By default, we implement many typehandlers. When we do not configure the specified TypeHandler,
   * Mybatis According to the different parameters or returned results, we will select the appropriate TypeHandler by default.
   * @param parent
   * @throws Exception
   */
private void typeHandlerElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
        //When the child node is package, get the value of its name attribute, and then automatically scan the custom typeHandler under the package
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
          //When the child node is typeHandler, you can specify javaType attribute, JDBC type or both
          //javaType is the specified java type
          //jdbc type is the specified jdbc type (database type: such as varchar)
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
          //handler is our configured typeHandler
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
          //The JdbcType is an enumeration type, and the resolveJdbcType method gets the value of the enumeration type
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          //Register typehandler. Typehandler is managed by TypeHandlerRegistry
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

The plug-in is an extension mechanism provided by mybatis. Users can add a custom plug-in to intercept at a certain stage during the execution of SQL statements. The custom plug-in in mybatis only needs to implement the Interceptor interface, and specify the signature of the intercepting method by annotation. This is described in detail later.

/**
   * Parsing plugins Tags
   * mybatis The plugin in is actually an interceptor,
   * It can intercept some methods of Executor, ParameterHandler, ResultSetHandler and StatementHandler to process our own logic.
   * @param parent
   * @throws Exception
   */
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      // When we define an interceptor, we need to implement it
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      interceptorInstance.setProperties(properties);
      // Register interceptors with the configuration object
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

During the initialization of mybatis, the global configuration file to load mybatis-config.xml and all mapping configuration files will be loaded, that is, the mapper configured by the mappers node

/**
   * Parse mapper file, mapper can be understood as the realization of dao
   * @param parent
   * @throws Exception
   */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
        //If the children of the mappers node are package, the files under the package will be scanned and injected into the configuration
      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");
          //Resource, URL and class
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
            //mapper mapping files are all parsed through XMLMapperBuilder
          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);
          //Ditto
          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.");
        }
      }
    }
  }
}

This is the parsing process of mybatis-config.xml configuration file during the initialization of mybatis. The next step is the parsing process of mapper configuration file.

354 original articles published, 522 praised, 1.28 million visitors+
His message board follow

Keywords: Mybatis Attribute JDBC xml

Added by Pioden on Mon, 27 Jan 2020 07:10:47 +0200