Setting up the source code environment

Here I mention that in the early version of Mybatis,Dao development method is Mapper interface and its implementation class, the implementation class needs to be written by ourselves, later Mybatis used JDK dynamic proxy to do the proxy for Mapper interface, for us to achieve the implementation class; But its underlying is also the use of Mapper interface implementation class, it is impossible to say that only one can be able to communicate with JDBC! Its basic environment can refer to the official tutorial www.mybatis.org/mybatis-3/z… At the end of this chapter, the test environment in this chapter is given

Enter the Debug trace quickly

We can make a breakpoint here,Debug mode starts to enter the breakpoint, and then press F7 to trace its method

Source code analysis preparation

Before carrying out the initialization process of Mybatis, we need to put the whole outline out in front, so that we can understand, and then have a general idea in mind when carrying out each step;

  • What is the initialization process of Mybatis?

SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); This line of code is to perform the Mybatis initialization operation, this operation is usually only once in the application, after the completion of the SqlSessionFactory will not be used, and the SqlSessionFactory will follow the entire application life cycle;

From the application stage: Mybatis generates SqlSessionFactory according to the global XML configuration file is the initialization process of Mybatis.

  • Analysis of the meaning of the word

Since the title is analysis of certain…. Compared to you can also see that this chapter will not be in-depth mining of the underlying code, I personally think that the main meaning of a brief analysis is “” can quickly establish the underlying source code in our hearts of the architecture diagram, quickly browse the code, and the concept of checking “”, of course, does not contain some modest statement ha ~~ The main purpose mentioned here is that this analysis of Mybatis is a quick look at the code; A new chapter on the core approach will be published later

  • Main steps in the initialization process of Mybatis
    • Parses the global Configuration file XML into the Configuration object
    • Parse the mapping Configuration file XML into the mapperRegistry object of Configuration
    • Parse the statements in the mapping Configuration file XML into MappedStatement objects and store them in the mappedStatements collection of the Configuration object
    • Finally, construct DefaultSqlSessionFactory object with Configuration as parameter

Source code analysis

Step 1: Load the global configuration file XML toConfigurationobject

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
parser.parse();
Copy the code

Main function: Loads the Configuration in the global Configuration file into the properties of a Configuration object

This is the first step. We break into the Main method from new SqlSessionFactoryBuilder().build(inputStream). You can see that after building the SqlSessionFactoryBuilder object, the overloaded build method is called

/ / the SqlSessionFactoryBuilder is the constructor
public SqlSessionFactoryBuilder(a) {}/ / the build method
public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
 }

// Build method (overload)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            // Step 1: Create an XML configuration builder that parses the global XML file contents
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException var13) {
            }
        }
        return var5;
    }
Copy the code

Before we go any further, we need to look at the XMLConfigBuilder object, which by its name parses XML configuration files; XMLConfigBuilder inherits from the BaseBuilder class. In the BaseBuilder class, there is a Configuration object, which is used to store global Configuration files and other Mapper Configuration information. At the same time we also can see from the chart XMLMapperBuilder, XMLStatementBuilder, MapperBuilderAssistant also inherited BaseBuilder class

XMLxxxBuilder is used to parse XML configuration files. Different types of XMLxxxBuilder are used to parse different parts of MyBatis configuration files.

  • XMLConfigBuilder is used to parse the global configuration file of MyBatis
  • XMLMapperBuilder is used to parse mapping files in MyBatis
  • XMLStatementBuilder is used to parse statement statements in mapping files.
  • MapperBuilderAssistant is used to assist in parsing mapping files and generating MappedStatement objects

These XMLXXxBuilders all have a common parent class, BaseBuilder. This parent class maintains a global Configuration object, which is stored in the MyBatis Configuration file after being parsed

Super (new Configuration()) is a breakpoint in the XMLConfigBuilder object.

You can see that the Configuration constructor is shown below, which explains why we can just write JDBC in the global Configuration file, because some default aliases are loaded when the Configuration object is built. Don’t tell me you don’t know what an alias is

public Configuration(a) {
        this.safeResultHandlerEnabled = true;
        this.multipleResultSetsEnabled = true;
        this.useColumnLabel = true;
        this.cacheEnabled = true;
        this.useActualParamName = true;
        this.localCacheScope = LocalCacheScope.SESSION;
        this.jdbcTypeForNull = JdbcType.OTHER;
        this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals"."clone"."hashCode"."toString"));
        this.defaultExecutorType = ExecutorType.SIMPLE;
        this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
        this.variables = new Properties();
        this.reflectorFactory = new DefaultReflectorFactory();
        this.objectFactory = new DefaultObjectFactory();
        this.objectWrapperFactory = new DefaultObjectWrapperFactory();
        this.lazyLoadingEnabled = false;
        this.proxyFactory = new JavassistProxyFactory();
        this.mapperRegistry = new MapperRegistry(this);
        this.interceptorChain = new InterceptorChain();
        this.typeHandlerRegistry = new TypeHandlerRegistry();
        this.typeAliasRegistry = new TypeAliasRegistry();
        this.languageRegistry = new LanguageDriverRegistry();
        this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
        this.caches = new Configuration.StrictMap("Caches collection");
        this.resultMaps = new Configuration.StrictMap("Result Maps collection");
        this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
        this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
        this.loadedResources = new HashSet();
        this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
        this.incompleteStatements = new LinkedList();
        this.incompleteCacheRefs = new LinkedList();
        this.incompleteResultMaps = new LinkedList();
        this.incompleteMethods = new LinkedList();
        this.cacheRefMap = new HashMap();
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
        this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
        this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
        this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        this.languageRegistry.register(RawLanguageDriver.class);
    }
Copy the code

Step one hasn’t been done yet? Parser.parse (); parser.parse(); parser.parse(); parser.parse(); parser.parse(); Once you have the XMLConfigBuilder object, you can then use it to parse the configuration file

   public Configuration parse(a) {
       The global configuration file can be parsed only once
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            // mark parsed as resolved
            this.parsed = true;
            // Parse the Configuration node in the XML of the global configuration file
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration; }}Copy the code
// Take a look at the method of resolving the configuration node of the global configuration file
private void parseConfiguration(XNode root) {
        try {
            The Configuration information of the properties node in the global Configuration file is stored in the Variables property of the Configuration object
            this.propertiesElement(root.evalNode("properties"));
            // Parse the Configuration information of the Settings node in the global Configuration file into the properties of the Configuration object
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.settingsElement(settings);
           // Resolve the configuration information of the typeAliases node in the global configuration file to set into the TypeAliasRegistry property of the BaseBuilder object
            this.typeAliasesElement(root.evalNode("typeAliases"));
            // Parse plugins for global configuration files
            this.pluginElement(root.evalNode("plugins"));
            // Resolve the objectFactory setting from the global Configuration file into the objectFactory property of the Configuration object
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // Parse the Environment node in the global Configuration file to the Environment property in the Configuration object
            
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            Note that this is a core method. Let's click on it
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ var3, var3); }}Copy the code

As you can see from the above code, XMLConfigBuilder parses properties such as 、、、、、 in the configuration file in turn.

Step 2: Parse the mapping Configuration file XML into the mapperRegistry container of Configuration

this.mapperElement(root.evalNode("mappers"));
Copy the code

Main function: MyBatis will iterate over all the child nodes. If the current node is traversed, MyBatis will register all the Mapper classes under the package into the mapperRegistry container of Configuration. If the current node is, the system obtains the resource, URL, and class attributes in sequence, parses the mapping file, and registers the Mapper class corresponding to the mapping file in the mapperRegistry container of configuration.

There is an important step in XMLConfigBuilder parsing global configuration files; This.mapperelement (root.evalNode(“mappers”)) starts parsing the map file. In the following figure, we build an XMLMapperBuilder object that parses the map file. The XMLConfigBuilder object in the first step parses the global configuration file

MapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
      // The parent class BaseBuilder is initialized first and configuration is assigned to BaseBuilder;
        super(configuration);
      // Then create the MapperBuilderAssistant object, which is the XMLMapperBuilder's helper, to assist the XMLMapperBuilder with some actions to parse the mapping file
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
      
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

    public void parse(a) {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }
Copy the code

Look again at mapperparser.parse ();

    public void parse(a) {
        // If the mapping file has not been loaded
        if (!this.configuration.isResourceLoaded(this.resource)) {
            // Execute the XML method configurationElement to load the mapping file
            this.configurationElement(this.parser.evalNode("/mapper"));
            // Add this mapping file to the parsed collection
            this.configuration.addLoadedResource(this.resource);
            / / binding Namespace
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }
Copy the code

The following is how Mybatis parses Statement in a mapping file

   private void configurationElement(XNode context) {
        try {
            / / get the namespace
            String namespace = context.getStringAttribute("namespace");
            // Check the namespace. If it is empty, throw an exception
            if(namespace ! =null && !namespace.equals("")) {
                / / set the namespace
                this.builderAssistant.setCurrentNamespace(namespace);
                // Parse each XML node in each Statement
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
               
                // Step 3: Parse the Statement core method
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty"); }}catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: "+ var3, var3); }}Copy the code

As you can see from the above code, XMLMapperBuilder parses the Mapper mapping file with the help of the MapperBuilderAssistant and, at the end of the parsing, parses each node in it into an MappedStatement object

Step 3: Parse the mapping file’s Statement into the MappedStatement object

this.buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));

Function: Parses the child nodes of a mapping file into MappedStatement objects

We go into this. BuildStatementFromContext (context. EvalNodes (” select | insert | update | delete “)); So let’s look at this method

    private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() ! =null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        Iterator var3 = list.iterator();

        while(var3.hasNext()) {
            XNode context = (XNode)var3.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser); }}}Copy the code

The main logic is in the two lines of code shown below

Let’s go to parseStatementNode of the XMLStatementBuilder class

The MapperBuilderAssistant wraps the MappedStatement object and places it in the **mappedStatements** container of the Configuration object

Initialization completed

Mybatis initialization is complete!!

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

Build source Debug environment

POM depends on

<! -- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>
Copy the code

Test the SQL

CREATE TABLE `user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `username` varchar(10) NOT NULL.`password` varchar(52) NOT NULL,
  PRIMARY KEY (`id`))ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
Copy the code

Mybatis global configuration file

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

      
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="jimisun"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>
Copy the code

UserMapper interface

public interface UserMapper {
    User selectUser(Integer id);
}
Copy the code

UserMapper configuration

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

      
<mapper namespace="user">
    <select id="selectUser" resultType="com.jimisun.mybatis.domain.User">
        select *
        from user
        where id = #{id}
    </select>
</mapper>
Copy the code

The User entity

public class User {
    private int id;
    private String username;
    private String password;
	getter and setter .....
}

Copy the code

The Main method

    public static void main(String[] args) throws IOException {

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("user.selectUser".2);
        System.out.println(user.toString());

    }
Copy the code

This tutorial belongs to the Spring Framework In-depth analysis column for Java engineers