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
BaseBuilder
Definition has three attributesprotected 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
- The package node is the class that configures the entire package
- 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; }
UnpooledDataSource
There 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