XMLConfigBuilder Parsing Configuration File Started by MyBatis

Posted by shibobo12 on Mon, 14 Oct 2019 07:17:50 +0200

Preface

XMLConfigBuilder is one of the subclasses of BaseBuilder, which parses MyBatis's XML and related configurations and saves them in Configuration. In this paper, the analytic process is analyzed according to the execution order, and the analytic principle of common configuration is mastered.

<!-- more -->

Use

Calling XML ConfigBuilder for parsing requires two steps, as mentioned in the previous article, [MyBatis Startup Analysis (I)].

Instantiate the XMLConfigBuilder object.

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        // Call the construction method of the parent class
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }

Instantiate Configuration

Instantiate by new Configuration():

TypeeAlias Registry is a type alias registry. Its implementation principle is to maintain a HashMap with aliases as key s and fully qualified class names as value s. Here, the classes used in the framework are registered in the type alias register.
The code for TypeAlias Registry.registerAlias is as follows:

    public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //  Unify key to lowercase before verifying the existence of key and saving kv
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      // Throw an exception when the registered type already exists
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // A HashMap Defined by TYPE_ALIASES
    TYPE_ALIASES.put(key, value);
    }

In the process of instantiating the Configuration class, in addition to instantiating the TypeAlias Registry, another class is instantiated as follows:

    // Type Processor Register
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

The instantiation logic of TypeHandler Registry is similar to that of TypeAlias Registry, which registers some commonly used types and processors, and the code is easy to understand.
Attributes of TypeHandler Registry

    // The mapping relationship between jdbc type and TypeHandler, key must be the enumeration type of JdbcType. When reading the result set data, jdbc type is converted to java type.
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    // There is a one-to-many mapping relationship between Java type and JdbcType type key-value pairs
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
    // Processors used when there is no corresponding type of processor
    private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    // Mapping Relationships between Type Processor Class Type and Type Processor
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
    // Values of empty processors for validation
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // Default Enumeration Type Processor
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

TypeHandler Registry constructor:

    public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
    
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
    
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
    
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
    
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
    
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
    
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
    
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
    
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
    
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
    
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
    
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    
        // mybatis-typehandlers-jsr310
        // Whether to include date and time-related Api, by judging whether to load java.time.Clock as a basis
        if (Jdk.dateAndTimeApiExists) {
          this.register(Instant.class, InstantTypeHandler.class);
          this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
          this.register(LocalDate.class, LocalDateTypeHandler.class);
          this.register(LocalTime.class, LocalTimeTypeHandler.class);
          this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
          this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
          this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
          this.register(Month.class, MonthTypeHandler.class);
          this.register(Year.class, YearTypeHandler.class);
          this.register(YearMonth.class, YearMonthTypeHandler.class);
          this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
        }
    
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
    }

There are two register() overload methods called, TypeHandler Registry. register (Class < T > javaType, TypeHandler <? Extends T > type Handler) with type + jdbc type + handler and TypeHandler Registry. register (Class < T > type, JdbcType, TypeHandler <? Extends T > handler)

    // java type + handler
    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
    }
    
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
    }
    
    // java type + jdbc type + handler
    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
    }
    
    // Type + handler and type + jdbc type + handler eventually call this method
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
          // When the java type is not empty, get the mapping of the java type
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            // If the mapping is empty, create a new mapping relationship
            map = new HashMap<JdbcType, TypeHandler<?>>();
            // Save to type processor mapping relationships
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          // Save the relationship between jdbc type and processor, and complete the registration among java type, jdbc type and processor.
          map.put(jdbcType, handler);
        }
        // Save Processor Information
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }
           
    // MappedJdbcTypes annotation        
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MappedJdbcTypes {
      JdbcType[] value();
      boolean includeNullJdbcType() default false;
    }
  • Type + handler method: First get the processor's Mapped JdbcTypes annotation (custom processor annotation). If the value of the annotation is not empty, because the value is JdbcType [] type, for loop javaType+jdbcType+TypeHandler registration, if includeNullJdbcType (whether or not jdbcType contains null) is true, the default value is false, registered in the corresponding mapping. If the value of the annotation is null, the registration operation will be called directly, and the type + jdbc type + handler relationship will not be registered.
  • type + jdbc type + handler method: this method coercion Java class into java.lang.reflect.Type type, and then call the final registration method.

Calling the Construction Method of the Parent BaseBuilder

BaseBuilderDefinition has three attributes

    protected final Configuration configuration;
    // Type Alias Register
    protected final TypeAliasRegistry typeAliasRegistry;
    // Type Processor Register
    protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder Construction Method

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

The attributes here are explained above.

Call XMLConfigBuilder.parse() as the parsing entry.

Whether parse() implementation profile has been parsed

    public Configuration parse() {
        // If parsed is true, the configuration file has been parsed
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // The logo has been parsed
        parsed = true;
        // Parsing begins with root node configuration
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

configuration in Resolution/configuration

    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          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"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

From the above source code, it is not difficult to see that here are the various sub-nodes in the parsing/configuration.

Properrties Node Resolution

properties configuration
    <!-- Method 1 -->
    <properties>
        <property name="username" value="${jdbc.username}" />
    </properties>
    
    <!-- Method two -->
    <properties resource="xxxConfig.properties">
    </properties>
    
    <!-- Method three -->
    <properties url="file:///D:/xxxConfig.properties">
    </properties>
propertiesElement() method
    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          // Get the property node and save it in Properties
          Properties defaults = context.getChildrenAsProperties();
          // Get the value of resource
          String resource = context.getStringAttribute("resource");
          // Get the value of the url
          String url = context.getStringAttribute("url");
          if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
          }
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          // Save parsed values to XPathParser
          parser.setVariables(defaults);
          // Save parsed values to Configuration
          configuration.setVariables(defaults);
        }
    }

From the source code above, the configuration form of resource and url does not allow coexistence, otherwise the BuilderException exception is thrown. The configuration value of property is parsed first, and then the value of resource or url is parsed.
When property has the same key as resource or url, the configuration of property will be overwritten. The principle that should be implemented for propertie s is implemented by inherited Hashtable classes.

settings Node Resolution

settings configuration
    <settings>
        <setting name="cacheEnabled" value="true" />
        ......
    </settings>

Intention, default values of items in the settings chart (Reference source: w3cschool)

Setting parameters describe Effective value Default value
cacheEnabled This configuration affects the global switch for caching configured in all mappers. true,false true
lazyLoadingEnabled Global switch with delayed loading. When opened, all associated objects are lazily loaded. The switch status of the item can be overwritten by setting the fetchType attribute in a particular association relationship. true,false false
aggressiveLazyLoading When enabled, the invocation of arbitrary delay attributes loads the object with delay loading attributes in its entirety; conversely, each attribute will be loaded on demand. true,false,true
multipleResultSetsEnabled Whether a single statement is allowed to return multiple result sets (compatible drivers are required). true,false true
useColumnLabel Use column labels instead of column names. Different drivers will behave differently in this respect. Specifically, you can refer to the relevant driver documentation or observe the results of the driver by testing the two different modes. true,false true
useGeneratedKeys Allowing JDBC to support automatic generation of primary keys requires driver compatibility. If set to true, this setting forces the use of auto-generated primary keys, which work well even though some drivers are incompatible (such as Derby). true,false False
autoMappingBehavior Specifies how MyBatis should automatically map columns to fields or properties. NONE denotes the cancellation of automatic mapping; PARTIAL only automatically maps result sets that do not define nested result set mappings. FULL automatically maps arbitrarily complex result sets, whether nested or not. NONE, PARTIAL, FULL PARTIAL
defaultExecutorType Configure the default executor. SIMPLE is a common executor; REUSE executors reuse prepared statements; BATCH executors reuse statements and perform batch updates. SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout Sets the timeout time, which determines the number of seconds the driver waits for the database response. Any positive integer Not Set (null)
safeRowBoundsEnabled Paging (RowBounds) is allowed in nested statements. true,false False
mapUnderscoreToCamelCase Whether to turn on the camel case mapping, which is a similar mapping from the classical database column name A_COLUMN to the classical Java attribute name aColumn. true, false False
localCacheScope MyBatis uses Local Cache to prevent circular references and to speed up repeated nested queries. The default is SESSION, which caches all queries executed in a SESSION. If the value is set to STATEMENT, the local SESSION is only used for statement execution, and different calls to the same SqlSession will not share data. SESSION,STATEMENT SESSION
jdbcTypeForNull When no specific JDBC type is provided for the parameter, the JDBC type is specified for the null value. Some drivers need to specify the JDBC type of the column, and in most cases use the general type directly, such as NULL, VARCHAR, or OTHER. JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER OTHER
lazyLoadTriggerMethods The method of specifying which object triggers a lazy load. A method name list separated by commas equals,clone,hashCode,toString
defaultScriptingLanguage Specifies the default language for dynamic SQL generation. A type alias or fully qualified class name. org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls Specifies whether to call the setter (put) method of the mapping object when the result set value is null, which is useful when Map.keySet() dependencies or null values are initialized. Note that the basic types (int, boolean, etc.) cannot be set to null. true,false false
logPrefix Specify that MyBatis is added to the prefix of the log name. Any String Not set
logImpl Specify the specific implementation of the logs used by MyBatis, which will be automatically searched when not specified. SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING Not set
proxyFactory Specify the proxy tool Mybatis uses to create objects with delayed loading capabilities. CGLIB JAVASSIST CGLIB
Settings AsProperties () method
    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        // Get the name and value of the setting node and save it back to Properties
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        // Create MetaClass for Configuration
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        // Check if there is a name value set in Configuration
        for (Object key : props.keySet()) {
          if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
          }
        }
        return props;
    }

Here we get the setting value and return the Properties object. Then configure the name to be legitimate.
The org.apache.ibatis.reflection.MetaClass class holds a class information obtained by reflection. MetaConfig. hasSetter (String. valueOf (key) is used to determine whether the metaConfig object contains key attributes.

vfsImpl() method
    private void loadCustomVfs(Properties props) throws ClassNotFoundException {
          String value = props.getProperty("vfsImpl");
        if (value != null) {
          String[] clazzes = value.split(",");
          for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
              @SuppressWarnings("unchecked")
              Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
              configuration.setVfsImpl(vfsImpl);
            }
          }
        }
    }

This method parses the virtual file system configuration and loads the resources of the custom virtual file system. Classes are stored in Configuration.vfsImpl.

settingsElement() method

The purpose of this method is to set parsed settings to configuration

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

TypeeAliases Node Resolution

Configuration of type Aliases
    <typeAliases>
        <package name="com.ytao.main.model"/>
        // or
        <typeAlias type="com.ytao.main.model.Student" alias="student"/>
        <typeAlias type="com.ytao.main.model.Person"/>
    </typeAliases>

The node is the relationship between the configuration class and the alias

  1. The package node is the class that configures the entire package
  2. TypeeAlias node is a specified configuration of a single class, type is a required value and a fully qualified class name, and alias is an optional one.

After configuration, aliases can be used directly when this class is in use.

typeAliasesElement() method
    private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              // Configuration in package mode
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              // Configuration in alias mode
              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);
              }
            }
          }
        }
    }
Using package configuration

When scanning the package, get the type Alias Registry. registerAliases (type Alias Package) after the package name

    public void registerAliases(String packageName){
        registerAliases(packageName, Object.class);
    }

    public void registerAliases(String packageName, Class<?> superType){
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        // Get all files under package that end with. class
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // Get the scanned class
        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
          // Filter class
          if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
          }
        }
    }

Scan to all classes that end in. class under the specified package and save them in the Set collection, then traverse the collection, filtering out unnamed, interface, and underlying specific classes.
Finally, TypeAlias Registry. registerAlias (Class <?> type) is registered in the alias register.

    public void registerAlias(Class<?> type) {
        // Use the class simpleName as an alias, which is the default alias naming rule
        String alias = type.getSimpleName();
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
          alias = aliasAnnotation.value();
        } 
        // The final registration method analyzed above
        registerAlias(alias, type);
    }

When registered in the registry through a class, if the registered class has @Alias (org.apache.ibatis.type.Alias) annotations, the aliases configured in the XML configuration will be overwritten by the annotation configuration.

Using typeAlias configuration

If alias of typeAlias has a set value, register with a custom name, otherwise register with the default method, that is, simpleName of the class as an alias.

plugins node parsing

plugins configuration
    <plugins>
        // Configure a custom plug-in to specify a point for interception
        <plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
            // Current plug-in properties
            <property name="name" value="100"/>
        </plugin>
    </plugins>

Custom plug-ins need to implement the org.apache.ibatis.plugin.Interceptor interface and specify interception methods on annotations.

pluginElement() method
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // Get the class name of the custom plug-in
            String interceptor = child.getStringAttribute("interceptor");
            // Get plug-in properties
            Properties properties = child.getChildrenAsProperties();
            // Instantiate Interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // Setting plug-in properties to plug-ins
            interceptorInstance.setProperties(properties);
            // Save the plug-in in configuration
            configuration.addInterceptor(interceptorInstance);
          }
        }
    }

The interceptor for the < plugin > node can be set with an alias. resolveClass method from source code

    // 
    protected Class<?> resolveClass(String alias) {
        if (alias == null) {
          return null;
        }
        try {
          return resolveAlias(alias);
        } catch (Exception e) {
          throw new BuilderException("Error resolving class. Cause: " + e, e);
        }
    }
    
    // 
    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }
    
    // 
    public <T> Class<T> resolveAlias(String string) {
        try {
          if (string == null) {
            return null;
          }
          // issue #748
          // Unified transformation of incoming class names
          String key = string.toLowerCase(Locale.ENGLISH);
          Class<T> value;
          // Verify that there is currently an incoming key in the alias
          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);
        }
    }

The above source code is an alias parsing process, and the parsing of other aliases also calls this method. First, we go to the saved aliases to find if there are aliases, and if not, we generate instances through Resources.classForName.

objectFactory, objectWrapperFactory, ReforFactory Node Resolution

All of the above implementations are extensions to MyBatis. The parsing method is similar, and is finally saved in configuration.

    // objectFactory parsing
    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties properties = context.getChildrenAsProperties();
          ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
          factory.setProperties(properties);
          configuration.setObjectFactory(factory);
        }
    }
    
    // objectWrapperFactory parsing
    private void objectWrapperFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
          configuration.setObjectWrapperFactory(factory);
        }
    }
    
    // Reforfactory Analysis
    private void reflectorFactoryElement(XNode context) throws Exception {
        if (context != null) {
           String type = context.getStringAttribute("type");
           ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
           configuration.setReflectorFactory(factory);
        }
    }
    

The above is to parse the source code of objectFactory, objectWrapperFactory, reflectorFactory. After the previous analysis, it is easier to understand here.

Environment Node Resolution

Environment Configuration
    <environments default="development">
        <environment id="development">
            <!-- transaction management -->
            <transactionManager type="JDBC">
                <property name="prop" value="100"/>
            </transactionManager>
            <!-- data source -->
            <dataSource type="UNPOOLED">
                <!-- JDBC drive -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- Database url -->
                <property name="url" value="${jdbc.url}"/>
                <!-- Database login name -->
                <property name="username" value="${jdbc.username}"/>
                <!-- Database login password -->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!-- An environment corresponds to an environment. environment -->
        ......
    </environments>

The node can be set up in multiple environments and configured separately for different environments. The default attribute of environments is the default environment, which corresponds to the value of the attribute id of an environment.

  • Transaction Manager is transaction management and attribute type is transaction management type. The new Configuration() introduced above has defined types: JDBC and MANAGED transaction management type.
  • Data Source is the data source, type is the data source type, and the same as transactionManager. It can be seen that the built-in data source types are: JNDI, POOLED, UNPOOLED data source type.
environmentsElement() method
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            environment = context.getStringAttribute("default");
          }
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            // Verify id
            if (isSpecifiedEnvironment(id)) {
              // Parse the transactionManager and instantiate the TransactionFactory
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              // Parse dataSource and instantiate DataSourceFactory
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              // Get dataSource
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
    }
    
    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
          throw new BuilderException("No environment specified.");
        } else if (id == null) {
          throw new BuilderException("Environment requires an id attribute.");
        } else if (environment.equals(id)) {
          return true;
        }
        return false;
    }

If the environment environment environment is not configured or the environment does not give id attributes, an exception is thrown. If the current id is to be used, it returns true, otherwise it returns false.
The instantiation process of TransactionFactory is relatively simple, similar to creating DataSourceFactory.

Data Source Acquisition

To get the data source, you first need to create a DataSourceFactory, which is created using DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource").

    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties props = context.getChildrenAsProperties();
          DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
          factory.setProperties(props);
          return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }

This is where you get the type of the data source and use the resolveClass() method mentioned above to get the DataSourceFactory.
Taking UNPOOLED as an example, the corresponding DataSourceFactory implementation class is Unpooled DataSourceFactory. In the instantiation process, the attribute dataSource data source of this class is assigned a value.

    /**
     * UnpooledDataSourceFactory class
     */
    protected DataSource dataSource;
    
    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }
    
    @Override
    public DataSource getDataSource() {
       return dataSource;
    }

UnpooledDataSourceThere are static code blocks in the class so the data source is loaded

    /**
     * UnpooledDataSource class
     */
    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
          Driver driver = drivers.nextElement();
          registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }

Data BaseIdProvider Node Resolution

Configuration of database IdProvider
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>
    
    <select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql">
        select
          *
        from student
    </select>

Based on the databaseId attribute in the mapping statement, different sql can be executed according to different database vendors.

databaseIdProviderElement() method
  private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // Maintain backward compatibility
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

Match the data source databaseIdProvider.getDatabaseId(environment.getDataSource()) according to the matching database vendor type

  @Override
  public String getDatabaseId(DataSource dataSource) {
    if (dataSource == null) {
      throw new NullPointerException("dataSource cannot be null");
    }
    try {
      return getDatabaseName(dataSource);
    } catch (Exception e) {
      log.error("Could not get a databaseId from dataSource", e);
    }
    return null;
  }
  
  private String getDatabaseName(DataSource dataSource) throws SQLException {
    // Get the name of the database product from the data source
    String productName = getDatabaseProductName(dataSource);
    if (this.properties != null) {
      for (Map.Entry<Object, Object> property : properties.entrySet()) {
        // Determine whether to include and choose the database product to use
        if (productName.contains((String) property.getKey())) {
          return (String) property.getValue();
        }
      }
      // no match, return null
      return null;
    }
    return productName;
  }
    
  private String getDatabaseProductName(DataSource dataSource) throws SQLException {
    Connection con = null;
    try {
      // Database Connection
      con = dataSource.getConnection();
      // Getting Connection Metadata
      DatabaseMetaData metaData = con.getMetaData();
      // Get the name of the database product
      return metaData.getDatabaseProductName();
    } finally {
      if (con != null) {
        try {
          con.close();
        } catch (SQLException e) {
          // ignored
        }
      }
    }
  }    

What I need to pay attention to here is the configuration: for example, using mysql, I stepped over the pit here, where Name is MySQL, I write y in capitals, the results do not match.
In addition, I can also match, should be used String.contains method, as long as it contains will conform, the code here should not be rigorous enough.

TypeeHandlers Node Resolution

TypeeHandlers Configuration
    <typeHandlers>
        <package name="com.ytao.main.handler"/>
        // or
        <typeHandler javaType="java.util.Date"  jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" />
    </typeHandlers>

Scanning for mappings between entire packages or specified types, javaType, jdbcType not required, handler mandatory

typeHandlerElement() method
  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // Get package name
          String typeHandlerPackage = child.getStringAttribute("name");
          // All types of processors under registration packages
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

Source code analysis is parsed according to all processors under the package or designated processors, and finally registered according to the type + handler and type + jdbc type + handler analyzed above.
There is also a TypeHandlerRegistry. register (Class <?> TypeHandlerClass) registration class.

  public void register(Class<?> typeHandlerClass) {
    // Does the flag retrieve javaType registration from the MappedTypes annotation
    boolean mappedTypeFound = false;
    // Get the value of MappedTypes
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        // Registered by type + handler
        register(javaTypeClass, typeHandlerClass);
        // Logo has been registered by annotation type
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      // Register through TypeHandler
      register(getInstance(null, typeHandlerClass));
    }
  }
  
  // instantiation
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        // Get a parametric constructor
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        // Instance object
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      // Getting a parametric constructor
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }  
  
  // Registration instance
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }  
  

Among the above register methods, the other register overloading methods are easy to understand after understanding type + jdbc type + handler, and the others are based on the encapsulation above.

Mapers Node Resolution

mappers configuration
    <mappers>
        <package name="com.ytao.main.mapper"/>
        // or
        <mapper resource="mapper/studentMapper.xml"/>
        // or
        <mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
        // or
        <mapper class="com.ytao.main.mapper.StudentMapper"/>
    </mappers>

The mappers nodes can be configured in the above four forms, and < package > and < mapper > are mutually exclusive nodes.

mapperElement() method

This method is responsible for parsing < mappers > nodes

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // If you configure the package node, scan
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // Parse the Mapper-like interface under the package and register it in the mapperRegistry of the configuration
          configuration.addMappers(mapperPackage);
        } else {
          // Get the resource,url,class attributes of the mapper node
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // According to resource parsing, and url, the class value must be empty, so the value cannot be configured. URL and class are the same. The other two properties cannot be configured with values.
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // Getting the flow through resource
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // Create an XML MapperBuilder object
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // Resolving mapping configuration files
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // Getting the stream through url
            InputStream inputStream = Resources.getUrlAsStream(url);
            // As with resource parsing, create an XML Mapper Builder object and parse the mapping configuration file
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // Interface for loading class attributes
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // Register the interface in the mapper Registry of configuration
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

The < package > package scans the classes and then registers them individually in the mapper Registry of the configuration, which is the same logic as < mapper > using the class attribute.
Parsing package mode

  // Definition in Configuration
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  /**
   * Step one
   * This function is in Configuration  
   */  
  public void addMappers(String packageName) {
    // MaperRegistry defines an attribute in Configuration
    mapperRegistry.addMappers(packageName);
  }
  
  /**
   * Step two
   * This function is in Mapper Registry  
   */   
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  
  /**
   * Step three
   * This function is in Mapper Registry  
   */       
  public void addMappers(String packageName, Class<?> superType) {
    // Get the class under the package through resolvereutil
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // Traverse the acquired class and register it with MapperRegistry
      addMapper(mapperClass);
    }
  }   
   
  /**
   * Step four
   * This function is in Mapper Registry
   */
  public <T> void addMapper(Class<T> type) {
    // The mapper class is an interface interface
    if (type.isInterface()) {
      // Determine whether the current class has been registered
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // Check whether loading is complete
      boolean loadCompleted = false;
      try {
        // Save the mapping between mapper interface and MapperProxyFactory
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // Parsing xml and annotations
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        // Logo Loading Completed
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }   
  

Resolving the class attribute of mapper

  // This function is in Configuration  
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  
  // ... Call step 4 above.

These two ways are to register the interface directly to mapper Registry, and the other two ways to parse xml are to get the namespace of the mapping file and register it. xml Mapper Builder is the class responsible for parsing the mapping configuration file. In the future, this class will be analyzed in detail separately. I will not expand on it here.

Here, the XML ConfigBuilder parsing configuration file is analyzed. This paper has a general understanding of the process and principle of the configuration file parsing. It is believed that we can find out the root cause of the abnormal configuration problems.

Personal blog: https://ytao.top
My public number ytao

Topics: Java JDBC Attribute Database