Mybaits source code analysis ---- create SqlSessionFactory according to the Configuration file (Configuration creation process)

Keywords: Java Mybatis xml Attribute JDBC

We use mybatis to operate the database through the API call of SqlSession, and create SqlSession through SqlSessionFactory. Let's take a look at the creation process of SqlSessionFactory.

Profile resolution portal

Let's look at the test method in the first article

 1 public static void main(String[] args) throws IOException {
 2     String resource = "mybatis-config.xml";
 3     InputStream inputStream = Resources.getResourceAsStream(resource);
 4     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 5     SqlSession sqlSession = sqlSessionFactory.openSession();
 6     try {
 7         Employee employeeMapper = sqlSession.getMapper(Employee.class);
 8          List<Employee> all = employeeMapper.getAll();
 9          for (Employee item : all)
10             System.out.println(item);
11     } finally {
12         sqlSession.close();
13     }
14 }

First, we load the configuration file with MyBatis provided tool class Resources to get an input stream. Then the SqlSessionFactory object is built by the build method of SqlSessionFactoryBuilder object. So the build method here is the entry method for us to analyze the configuration file parsing process. Let's see the code in build:

public SqlSessionFactory build(InputStream inputStream) {
    // Call overloaded method
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // Create profile parser
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // call parser.parse() Method, generate Configuration object
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
        inputStream.close();
        } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
        }
    }
}

public SqlSessionFactory build(Configuration config) {
    // Establish DefaultSqlSessionFactory,The generated Configuration afferent
    return new DefaultSqlSessionFactory(config);
}

SqlSessionFactory is created by the build method of SqlSessionFactoryBuilder. The internal of the build method is to generate a Configuration object by parsing the mybatis-config.xml file with an XMLConfigBuilder object. XMLConfigBuilder can be seen from its name that it parses the Mybatis Configuration file. In fact, it inherits a parent class, BaseBuilder. Most of its subclasses are named after xmlxxxxx builder, that is, its subclasses are corresponding to parsing an XML file or an element in an XML file.

Let's look at the construction method of XMLConfigBuilder:

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

You can see that the constructor of the parent class is called and a new Configuration() object is passed in, which is the final Mybatis configuration object.

Let's take a look at its base class, BaseBuilder.

public abstract class BaseBuilder {
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;
 
  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
  ....
}

There are only three member variables in the BaseBuilder, and typeAliasRegistry and typeHandlerRegistry are obtained directly from the member variables of Configuration. Next, let's look at the Configuration class.

The Configuration class is located in the org.apache.ibatis.session directory of the mybatis package. Its attribute is the Configuration corresponding to the global Configuration file mybatis-config.xml of mybatis. The content in the XML Configuration is parsed and assigned to the Configuration object.

There are many XML Configuration items, so there are many properties of Configuration class. Let's take a look at the following XML Configuration file example for Configuration:

<?xml version="1.0" encoding="UTF-8"?>    

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">    
<!-- Global configuration top level nodes -->
<configuration>    
     
     <!-- Attribute configuration,read properties Profile in -->
    <properties resource="db.propertis">
       <property name="XXX" value="XXX"/>
    </properties>
    
    <!-- Type alias -->
    <typeAliases>
       <!-- In use User When the type,Alias can be used directly,No input required User All paths of class -->
       <typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
    </typeAliases>

    <!-- Type processor -->
    <typeHandlers>
        <!-- The purpose of a type processor is to complete JDBC Type and java Conversion of type,mybatis By default, there are many types of processors,Normal without customization-->
    </typeHandlers>
    
    <!-- Object factory -->
    <!-- mybatis When creating a new instance of the result object,It will be done through the object factory. mybatis There is a default object factory, which does not need to be configured normally -->
    <objectFactory type=""></objectFactory>
    
    <!-- Plug-in unit -->
    <plugins>
        <!-- You can customize the interceptor through plugin Tag added -->
       <plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
    </plugins>
    
    <!-- Global configuration parameters -->
    <settings>   
        <setting name="cacheEnabled" value="false" />   
        <setting name="useGeneratedKeys" value="true" /><!-- Generate primary key automatically -->
        <setting name="defaultExecutorType" value="REUSE" />   
        <setting name="lazyLoadingEnabled" value="true"/><!-- Delay load ID -->
        <setting name="aggressiveLazyLoading" value="true"/><!--Whether objects with delay load attribute delay load -->
        <setting name="multipleResultSetsEnabled" value="true"/><!-- Whether to allow a single statement to return multiple result sets -->
        <setting name="useColumnLabel" value="true"/><!-- Use column labels instead of column names -->
        <setting name="autoMappingBehavior" value="PARTIAL"/><!-- Appoint mybatis How to map columns to field properties automatically;NONE:Automatic mapping;PARTIAL:Only mapped results without nested results;FULL:Any complex result can be mapped -->
        <setting name="defaultExecutorType" value="SIMPLE"/><!-- Default actuator type -->
        <setting name="defaultFetchSize" value=""/>
        <setting name="defaultStatementTimeout" value="5"/><!-- Timeout for the driver to wait for the database ,Units are seconds.-->
        <setting name="safeRowBoundsEnabled" value="false"/><!-- Allow nested statements RowBounds -->
        <setting name="safeResultHandlerEnabled" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/><!-- Whether the underline column name is automatically mapped to the hump attribute: for example user_id Mapping to userId -->
        <setting name="localCacheScope" value="SESSION"/><!-- Local cache( session Is session level) -->
        <setting name="jdbcTypeForNull" value="OTHER"/><!-- When the data is null,No specific JDBC Of type JDBC type -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- Specifies the method of triggering an object with delayed loading -->
        <setting name="callSettersOnNulls" value="false"/><!--If setter Method or map Of put Method, if the retrieved value is null Is the data useful?  -->
        <setting name="logPrefix" value="XXXX"/><!-- mybatis Log file prefix string -->
        <setting name="logImpl" value="SLF4J"/><!-- mybatis Implementation class of log -->
        <setting name="proxyFactory" value="CGLIB"/><!-- mybatis Proxy tools -->
    </settings>  

    <!-- Environment configuration collection -->
    <environments default="development">    
        <environment id="development">    
            <transactionManager type="JDBC"/><!-- Transaction manager -->
            <dataSource type="POOLED"><!-- Database connection pool -->    
                <property name="driver" value="com.mysql.jdbc.Driver" />    
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8" />    
                <property name="username" value="root" />    
                <property name="password" value="root" />    
            </dataSource>    
        </environment>    
    </environments>    
    
    <!-- mapper File mapping configuration -->
    <mappers>    
        <mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>    
    </mappers>    
</configuration>

For XML Configuration, the attributes of Configuration class correspond to XML Configuration. The Configuration class properties are as follows:

public class Configuration {
  protected Environment environment;//Running environment

  protected boolean safeRowBoundsEnabled = false;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase = false;
  protected boolean aggressiveLazyLoading = true; //true: Any property is loaded completely when an object with delay loading property is called;false: Load each attribute as needed
  protected boolean multipleResultSetsEnabled = true;//Whether multiple result sets are allowed to be returned from a single statement
  protected boolean useGeneratedKeys = false;//Support automatic generation of primary key
  protected boolean useColumnLabel = true;//Use column labels or not
  protected boolean cacheEnabled = true;//Use cache identity or not
  protected boolean callSettersOnNulls = false;//
  protected boolean useActualParamName = true;

  protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected Class <? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;//Appoint mybatis If columns are automatically mapped to fields and properties,PARTIAL Will automatically map simple results without nesting,FULL Any complex results will be mapped automatically
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;//Whether to delay loading, false All associated objects are loaded,true Indicates delayed loading
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;

  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<String>(); //Already loaded resource(mapper)
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

  protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
  
  //Other methods are omitted
}

The loading process is that SqlSessionFactoryBuilder parses a Configuration object through the parse method of XMLConfigBuilder according to the file stream configured by xml. Let's take a look at its constructor.

 1 public Configuration() {
 2     this.safeRowBoundsEnabled = false;
 3     this.safeResultHandlerEnabled = true;
 4     this.mapUnderscoreToCamelCase = false;
 5     this.aggressiveLazyLoading = true;
 6     this.multipleResultSetsEnabled = true;
 7     this.useGeneratedKeys = false;
 8     this.useColumnLabel = true;
 9     this.cacheEnabled = true;
10     this.callSettersOnNulls = false;
11     this.localCacheScope = LocalCacheScope.SESSION;
12     this.jdbcTypeForNull = JdbcType.OTHER;
13     this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
14     this.defaultExecutorType = ExecutorType.SIMPLE;
15     this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
16     this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
17     this.variables = new Properties();
18     this.reflectorFactory = new DefaultReflectorFactory();
19     this.objectFactory = new DefaultObjectFactory();
20     this.objectWrapperFactory = new DefaultObjectWrapperFactory();
21     this.mapperRegistry = new MapperRegistry(this);
22     this.lazyLoadingEnabled = false;
23     this.proxyFactory = new JavassistProxyFactory();
24     this.interceptorChain = new InterceptorChain();
25     this.typeHandlerRegistry = new TypeHandlerRegistry();
26     this.typeAliasRegistry = new TypeAliasRegistry();
27     this.languageRegistry = new LanguageDriverRegistry();
28     this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
29     this.caches = new Configuration.StrictMap("Caches collection");
30     this.resultMaps = new Configuration.StrictMap("Result Maps collection");
31     this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
32     this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
33     this.loadedResources = new HashSet();
34     this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
35     this.incompleteStatements = new LinkedList();
36     this.incompleteCacheRefs = new LinkedList();
37     this.incompleteResultMaps = new LinkedList();
38     this.incompleteMethods = new LinkedList();
39     this.cacheRefMap = new HashMap();
40     this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
41     this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
42     this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
43     this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
44     this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
45     this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
46     this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
47     this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
48     this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
49     this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
50     this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
51     this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
52     this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
53     this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
54     this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
55     this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
56     this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
57     this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
58     this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
59     this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
60     this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
61     this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
62     this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
63     this.languageRegistry.register(RawLanguageDriver.class);
64 }

We see line 26 this.typeAliasRegistry = new TypeAliasRegistry();, and lines 40 to 61 register many aliases with typeAliasRegistry. Let's look at typeAliasRegistry.

public class TypeAliasRegistry {
    private final Map<String, Class<?>> TYPE_ALIASES = new HashMap();

    public TypeAliasRegistry() {
        this.registerAlias("string", String.class);
        this.registerAlias("byte", Byte.class);
        this.registerAlias("long", Long.class);
        this.registerAlias("short", Short.class);
        this.registerAlias("int", Integer.class);
        this.registerAlias("integer", Integer.class);
        this.registerAlias("double", Double.class);
        this.registerAlias("float", Float.class);
        this.registerAlias("boolean", Boolean.class);
        this.registerAlias("byte[]", Byte[].class);
        this.registerAlias("long[]", Long[].class);
        this.registerAlias("short[]", Short[].class);
        this.registerAlias("int[]", Integer[].class);
        this.registerAlias("integer[]", Integer[].class);
        this.registerAlias("double[]", Double[].class);
        this.registerAlias("float[]", Float[].class);
        this.registerAlias("boolean[]", Boolean[].class);
        this.registerAlias("_byte", Byte.TYPE);
        this.registerAlias("_long", Long.TYPE);
        this.registerAlias("_short", Short.TYPE);
        this.registerAlias("_int", Integer.TYPE);
        this.registerAlias("_integer", Integer.TYPE);
        this.registerAlias("_double", Double.TYPE);
        this.registerAlias("_float", Float.TYPE);
        this.registerAlias("_boolean", Boolean.TYPE);
        this.registerAlias("_byte[]", byte[].class);
        this.registerAlias("_long[]", long[].class);
        this.registerAlias("_short[]", short[].class);
        this.registerAlias("_int[]", int[].class);
        this.registerAlias("_integer[]", int[].class);
        this.registerAlias("_double[]", double[].class);
        this.registerAlias("_float[]", float[].class);
        this.registerAlias("_boolean[]", boolean[].class);
        this.registerAlias("date", Date.class);
        this.registerAlias("decimal", BigDecimal.class);
        this.registerAlias("bigdecimal", BigDecimal.class);
        this.registerAlias("biginteger", BigInteger.class);
        this.registerAlias("object", Object.class);
        this.registerAlias("date[]", Date[].class);
        this.registerAlias("decimal[]", BigDecimal[].class);
        this.registerAlias("bigdecimal[]", BigDecimal[].class);
        this.registerAlias("biginteger[]", BigInteger[].class);
        this.registerAlias("object[]", Object[].class);
        this.registerAlias("map", Map.class);
        this.registerAlias("hashmap", HashMap.class);
        this.registerAlias("list", List.class);
        this.registerAlias("arraylist", ArrayList.class);
        this.registerAlias("collection", Collection.class);
        this.registerAlias("iterator", Iterator.class);
        this.registerAlias("ResultSet", ResultSet.class);
    }

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

In fact, there is a HashMap in typealias registry, and many aliases are registered in the builder of typealias registry to this HashMap. OK, now we just create an XMLConfigBuilder, in which we create a Configuration object. Next, we will analyze mybatis-config.xml into the corresponding properties in Configuration, and also It is the parser.parse() method:

XMLConfigBuilder

1 public Configuration parse() {
2     if (parsed) {
3         throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4     }
5     parsed = true;
6     // Analytical configuration
7     parseConfiguration(parser.evalNode("/configuration"));
8     return configuration;
9 }

Let's look at line 7 and notice an xpath expression - / configuration. This expression represents the < configuration / > tag of MyBatis. Select this tag here and pass it to the parseConfiguration method. Let's keep going.

private void parseConfiguration(XNode root) {
    try {
        // analysis properties To configure
        propertiesElement(root.evalNode("properties"));

        // analysis settings Configure and convert it to Properties object
        Properties settings = settingsAsProperties(root.evalNode("settings"));

        // Load vfs
        loadCustomVfs(settings);

        // analysis typeAliases To configure
        typeAliasesElement(root.evalNode("typeAliases"));

        // analysis plugins To configure
        pluginElement(root.evalNode("plugins"));

        // analysis objectFactory To configure
        objectFactoryElement(root.evalNode("objectFactory"));

        // analysis objectWrapperFactory To configure
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

        // analysis reflectorFactory To configure
        reflectorFactoryElement(root.evalNode("reflectorFactory"));

        // settings The information in is set to Configuration In object
        settingsElement(settings);

        // analysis environments To configure
        environmentsElement(root.evalNode("environments"));

        // analysis databaseIdProvider,Get and set databaseId reach Configuration object
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));

        // analysis typeHandlers To configure
        typeHandlerElement(root.evalNode("typeHandlers"));

        // analysis mappers To configure
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

Resolving properties configuration

Let's take a look at the configuration of the properties node. As follows:

<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</properties>

I have configured a resource property for the properties node, as well as two child nodes. Then let's look at the logic of propertiesElement

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // analysis propertis And convert the contents of these nodes to attribute objects Properties
        Properties defaults = context.getChildrenAsProperties();
        // Obtain propertis Node resource and url Attribute value
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");

        // If neither is empty, an exception will be thrown.
        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) {
            // Load and parse property files from the file system
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            // adopt url Load and parse property file
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        // Set property value to configuration in
        configuration.setVariables(defaults);
    }
}

public Properties getChildrenAsProperties() {
    //Create a Properties object
    Properties properties = new Properties();
    // Get and traverse child nodes
    for (XNode child : getChildren()) {
        // Obtain property Nodal name and value attribute
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
            // Set property to property object
            properties.setProperty(name, value);
        }
    }
    return properties;
}

// -☆- XNode
public List<XNode> getChildren() {
    List<XNode> children = new ArrayList<XNode>();
    // Get the list of child nodes
    NodeList nodeList = node.getChildNodes();
    if (nodeList != null) {
        for (int i = 0, n = nodeList.getLength(); i < n; i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                children.add(new XNode(xpathParser, node, variables));
            }
        }
    }
    return children;
}

There are three steps to resolve properties:

  1. Resolve the child nodes of the properties node and set the resolution results to the properties object.
  2. Read the property configuration from the file system or over the network, depending on whether the resource and url of the properties node are empty.
  3. Set the resolved property object to the XPathParser and Configuration objects.

It should be noted that the propertiesElement method first resolves the content of the child nodes of the properties node, then reads the property configuration from the file system or the network, and puts all the properties and property values into the defaults property object. This will lead to the problem of property overwrite with the same name, that is, the property and property value read from the file system or the network will overwrite the property and property value with the same name in the properties sub node.

Analyze settings configuration

The resolution process of settings node

Let's take a look at a simple configuration of settings, as follows:

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>

Let's take a look at settings as properties

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    // Obtain settings The content in the child node, parsed into Properties,getChildrenAsProperties Method has been analyzed before
    Properties props = context.getChildrenAsProperties();

    // Establish Configuration Class's meta information object
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
        // Testing Configuration Whether there are related properties in, if not, an exception will be thrown.
        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;
}

Set settings to Configuration

Next let's look at the process of setting the settings Configuration to the Configuration object. As follows:

private void settingsElement(Properties props) throws Exception {
    // Set up autoMappingBehavior Property, default is PARTIAL
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    // Set up cacheEnabled Property, default is true
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));

    // Omit some codes

    // Resolving the default enumeration processor
    Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
    // Set default enumeration processor
    configuration.setDefaultEnumTypeHandler(typeHandler);
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    
    // Omit some codes
}

The above code handles calling the setter method of Configuration

Parsing typeAliases configuration

In MyBatis, we can define an alias for some of the classes we write ourselves. In this way, when using, we only need to enter the alias, without writing out the fully qualified class name. In MyBatis, we have two ways to configure aliases. The first is to configure only the package name, let MyBatis scan the types in the package, and get the corresponding aliases according to the types.

<typeAliases>
    <package name="com.mybatis.model"/>
</typeAliases>

The second way is to explicitly configure aliases for a type manually. The configuration of this method is as follows:

<typeAliases>
    <typeAlias alias="employe" type="com.mybatis.model.Employe" />
    <typeAlias type="com.mybatis.model.User" />
</typeAliases>

Let's take a look at how two different alias configurations are resolved. The code is as follows:

XMLConfigBuilder

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // Resolves alias and type mappings from the specified package
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
                
            // from typeAlias Mapping of resolving aliases and types in nodes
            } else {
                // Obtain alias and type Attribute values alias Not required, can be empty
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    // Load type Corresponding type
                    Class<?> clazz = Resources.classForName(type);

                    // Register alias to type mapping
                    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);
                }
            }
        }
    }
}

We can see that whether the name of the child node is package is used to judge through package scanning and manual registration.

Resolve and register aliases from the typeAlias node

In alias configuration, the type attribute must be configured, while the alias attribute is not.

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

public void registerAlias(Class<?> type) {
    // Get the short name of the full path class name
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        // Take alias from annotation
        alias = aliasAnnotation.value();
    }
    // Call overload method to register alias and type mapping
    registerAlias(alias, type);
}

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("The parameter alias cannot be null");
    }
    // Convert alias to lowercase
    String key = alias.toLowerCase(Locale.ENGLISH);
    /*
     * If there is a type mapping in type ﹣ aliases, it is judged whether the current type is consistent with the type in the mapping.
     * Throw an exception if it is inconsistent. An alias is not allowed to correspond to two types.
     */
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
        throw new TypeException(
            "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // Cache alias to type mapping
    TYPE_ALIASES.put(key, value);
}

If the user explicitly configures the Alias attribute, MyBatis will use the lowercase form of the class name as the Alias. For example, the fully qualified class name com.mybatis.model.User is Alias user. If there is @ Alias annotation in the class, the value is taken from the annotation as the Alias.

Resolves and registers aliases from the specified package

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

public void registerAliases(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    /*
     * Find the class whose parent class under the package is Object.class.
     * After the lookup is completed, the lookup results will be cached in the internal collection of resolveutil.
     */ 
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // Get search results
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
        // Ignore anonymous class, interface, inner class
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            // Register alias for type 
            registerAlias(type);
        }
    }
}

There are two main steps:

  1. Find all classes under the specified package
  2. Traverse the found type collection and register aliases for each type

Let's look at all the classes under the specified package.

private Set<Class<? extends T>> matches = new HashSet();

public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
    //Convert package name to file path
    String path = this.getPackagePath(packageName);

    try {
        //adopt VFS(Virtual file system) gets the pathnames of all files under the specified package.such as com/mybatis/model/Employe.class
        List<String> children = VFS.getInstance().list(path);
        Iterator i$ = children.iterator();

        while(i$.hasNext()) {
            String child = (String)i$.next();
            //with.class The ending file is added to the Set Collection
            if (child.endsWith(".class")) {
                this.addIfMatching(test, child);
            }
        }
    } catch (IOException var7) {
        log.error("Could not read package: " + packageName, var7);
    }

    return this;
}

protected String getPackagePath(String packageName) {
    //Convert package name to file path
    return packageName == null ? null : packageName.replace('.', '/');
}

protected void addIfMatching(ResolverUtil.Test test, String fqn) {
    try {
        //Convert the pathname to the fully qualified class name, and load the class name through the class loader, such as com.mybatis.model.Employe.class
        String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.');
        ClassLoader loader = this.getClassLoader();
        if (log.isDebugEnabled()) {
            log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
        }

        Class<?> type = loader.loadClass(externalName);
        if (test.matches(type)) {
            //Add to matches Collection
            this.matches.add(type);
        }
    } catch (Throwable var6) {
        log.warn("Could not examine class '" + fqn + "'" + " due to a " + var6.getClass().getName() + " with message: " + var6.getMessage());
    }

}

The main steps are as follows:

  1. Obtain the pathnames of all files under the specified package through VFS (virtual file system), such as COM / mybatis / model / employee.class.
  2. Filter filenames ending in. class
  3. Convert the pathname to the fully qualified class name, and load the class name through the class loader
  4. Match the type and put it into the internal collection if the matching rule is met

Here we should note that when we analyzed the creation of Configuration objects, we have registered many aliases by default. You can go back to the beginning of this article.

Parsing plugins configuration

The plug-in is an extension mechanism provided by MyBatis. Through the plug-in mechanism, we can do some custom operations at some points in the SQL execution process. This is a metaphor for paging plug-ins. Before SQL execution, we will dynamically splice statements. We will talk about the plug-in mechanism separately later. First, we will understand the plug-in configuration. As follows:

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>

The analysis process is as follows:

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            // Get configuration information
            Properties properties = child.getChildrenAsProperties();
            // Parse the type of interceptor and create the interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // set a property
            interceptorInstance.setProperties(properties);
            // Add interceptor to Configuration in
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

First, get the Configuration, then parse the interceptor type and instantiate the interceptor. Finally, set the properties to the interceptor and add the interceptor to the Configuration.

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            // Get configuration information
            Properties properties = child.getChildrenAsProperties();
            // Analyze the type of interceptor and instantiate the interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // set a property
            interceptorInstance.setProperties(properties);
            // Add interceptor to Configuration in
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

public void addInterceptor(Interceptor interceptor) {
    //Add to Configuration Of interceptorChain Attribute
    this.interceptorChain.addInterceptor(interceptor);
}

Let's take a look at interceptor chain.

public class InterceptorChain {
    private final List<Interceptor> interceptors = new ArrayList();

    public InterceptorChain() {
    }

    public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator i$ = this.interceptors.iterator(); i$.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)i$.next();
        }

        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(this.interceptors);
    }
}

It's actually a set of interceptors. We'll talk about the principle of plug-ins later.

Analyzing environment configuration

In MyBatis, the transaction manager and data sources are configured in environments. Their configuration is roughly as follows:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>

Let's take a look at the environmentslelement method

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            // Obtain default attribute
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            // Obtain id attribute
            String id = child.getStringAttribute("id");
            /*
             * Detect the id of the current environment node and the attribute default of its parent environment 
             * If the content is consistent, true will be returned; otherwise, false will be returned.
             * Set the subelement whose default attribute value is equal to the id attribute value of the subelement environment to the currently used environment object
             */
            if (isSpecifiedEnvironment(id)) {
                // take environment Medium transactionManager Label to TransactionFactory object
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // take environment Medium dataSource Label to DataSourceFactory object
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                // Establish DataSource object
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                // structure Environment Object and set to configuration in
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

Take a look at the acquisition of TransactionFactory and DataSourceFactory

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        //Get by alias Class,Instantiation
        TransactionFactory factory = (TransactionFactory)this.resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    } else {
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }
}

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

< transactionmanager type = "JDBC" / > there are two configurations of "JDBC" and "MANAGED" in the type. In the alias registered by default in our previous Configuration, there are two corresponding transactionfactories: JdbcTransactionFactory.class and ManagedTransactionFactory.class.

There are three configurations of "JNDI", "POOLED" and "UNPOOLED" in < datasource type = "POOLED" > the default registered alias includes three datasourcefactories: JndiDataSourceFactory.class, PooledDataSourceFactory.class and UnpooledDataSourceFactory.class.

In our environment configuration, if transactionManager type="JDBC" and dataSource type="POOLED", the generated transactionManager is JdbcTransactionFactory and DataSourceFactory is pooledatasourcefactory.

Let's take a look at pooledatasourcefactory and UnpooledDataSourceFactory

public class UnpooledDataSourceFactory implements DataSourceFactory {
    private static final String DRIVER_PROPERTY_PREFIX = "driver.";
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length();
    //Establish UnpooledDataSource Example
    protected DataSource dataSource = new UnpooledDataSource();
    
    public DataSource getDataSource() {
        return this.dataSource;
    }
    //slightly
}
 
//inherit UnpooledDataSourceFactory
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
    public PooledDataSourceFactory() {
        //Establish PooledDataSource Example
        this.dataSource = new PooledDataSource();
    }
}

We found that the dataSource created by UnpooledDataSourceFactory is UnpooledDataSource, and the dataSource created by PooledDataSourceFactory is PooledDataSource.

Parsing mappers configuration

The mapperElement method will convert the elements in the mapper tag into MapperProxyFactory generated proxy classes and bind to the mapper.xml file, which will be explained in the next article.

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
}

Create DefaultSqlSessionFactory

So far, we have gone through the important steps in the parse method of XMLConfigBuilder, and then we return a complete Configuration object. Finally, we create a SqlSessionFactory instance DefaultSqlSessionFactory through the overload method of SqlSessionFactoryBuilder's build. Let's take a look at it.

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

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    //Just will configuration Set as its property
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    
    //slightly
}

Posted by rommel_toledo on Sun, 27 Oct 2019 23:53:57 -0700