Operation principle of MyBatis

This article was recorded in my work. Due to my busy work, I have been kept locally and forgot to send it to my blog. Now I have optimized the structure of the article and sent it to you. Please criticize and correct it.
In addition, there will be many knowledge points recorded in their own work in the follow-up, and the articles will be transmitted through secondary optimization.

In the process of developing the scheduling system of big data platform, we quickly build the scheduling platform through spring boot, and the persistence framework adopts Mybatis. This is also the first time to use Mybatis. Of course, in the work, we only build some Dao and Mapper, and use some addition, deletion, modification and query to complete the historical records of scheduling tasks and the records of Dependency between tasks. However, the basic operation principle of Mybatis still needs to be combed in combination with the source code.
Here I give a simple Demo. Follow the Demo step by step to see the source code and realize the operation principle of Mybatis.

Part I: Demo sample

Create User entity class:

@Data
public class MyUser {
	private Integer uid;
	private String uname;
	private String usex;
}

Create UserDao interface:

import java.util.List;
import gzc.entity.MyUser;
public interface UserDao {
	// The method name defined by the interface is consistent with the Mapper mapping id
	public MyUser selectUserById(Integer uid);
	public List<MyUser> selectAllUser();
	public int addUser(MyUser user);
	public int updateUser(MyUser user);
	public int deleteUser(Integer uid);
}

Create mapping file: MyUserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <!-- namespace Property binding dao Interface -->
<mapper namespace="gzc.dao.UserDao">
	 <!-- according to uid Query a user information -->
	 <select id="selectUserById" resultType="zjw.entity.MyUser" parameterType="Integer">
	 	select * from myuser where uid = #{uid}
	 </select>
	 <!-- Query all user information -->
	 <select id="selectAllUser" resultType="zjw.entity.MyUser">
	 	select * from myuser
	 </select>
	 <!-- Add a user  #{uname} is the attribute value of gzc.entity.MyUser -- >
	 <insert id="addUser" parameterType="zjw.entity.MyUser">
	 insert into myuser values(#{uid},#{uname},#{usex})
	 </insert>
	 <!-- Modify a user -->
	 <update id="updateUser" parameterType="zjw.entity.MyUser">
	 	update myuser set uname=#{uname},usex=#{usex} where uid=#{uid}
	 </update>
	 <!-- Delete a user -->
	 <delete id="deleteUser" parameterType="zjw.entity.MyUser">
	 delete from myuser where uid=#{uid}
	 </delete>
</mapper>

Create the Mybatis main configuration file mybatis-config.xml:

<?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">
<configuration>
<!--utilize mybatis Configure the data source for the built-in environment, and Spring After integration, these data sources can be handed over to Spring Configuration processing-->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.cj.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/springtestdb?serverTimezone=UTC" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<!-- SQL Location of the mapping file-->
		<mapper resource="zjw/mapper/MyUserMapper.xml" />
	</mappers>
</configuration>

To create a test class:

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import zjw.dao.UserDao;
import zjw.entity.MyUser;
public class MybatisTest {
	public static void main(String[] args) {
		try {
			// Read the configuration file mybatis-config.xml
			InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
			// Build SqlSessionFactory from configuration file
			SqlSessionFactory ssl = new SqlSessionFactoryBuilder().build(config);
			// Create a SQLSession object through SqlSessionFactory
			SqlSession ss = ssl.openSession();

			/*
			 * Method 1: SqlSession executes the sql defined in the mapping file and returns the mapping result
			 * gzc.mapper.MyUserMapper.selectUserById The id of the namespace + SQL statement in MyUserMapper.xml. For example:
			 * MyUser mu = ss.selectOne("gzc.mapper.MyUserMapper.selectUserById", 6);
			 */

			/*
			 * Method 2: get Mapper mapping and Dao interface mapping through getMapper method of SqlSession object
			 * This method needs to bind dao's interface to Mapper's namespace
			 */
			 
			// Associate the dao interface method with the mapping file and return the interface object
			UserDao userDao = ss.getMapper(UserDao.class);
			// Query a user
			MyUser user = userDao.selectUserById(1);
			System.out.println(user);
			// Add a user
			MyUser newUser = new MyUser(8, "floret", "female");
			userDao.addUser(newUser);
			// Modify a user
			MyUser updatemu = new MyUser(7, "Xiao Ming", "male");
			userDao.updateUser(updatemu);
			// Delete a user
			userDao.deleteUser(3);
			// Find all users
			List<MyUser> myUsers = userDao.selectAllUser();
			for (MyUser myUser : myUsers) {
				System.out.println(myUser);
			}
			ss.commit();
			ss.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Mybatis important components and operation flow chart

  • Configuration: all configuration information of Mybatis is saved in the configuration object, and most configurations in the configuration file are stored in this class
  • SqlSession: as the top-level API of Mybatis, it represents the session when interacting with the database and completes the necessary database addition, deletion, modification and query functions
  • Executor: Mybatis executor, which is the core of Mybatis scheduling, is responsible for generating SQL statements and maintaining query cache
  • StatementHandler: encapsulates JDBC Statement operations and is responsible for JDBC Statement operations, such as setting parameters
  • ParameterHandler: it is responsible for converting the parameters passed by the user into the data type corresponding to JDBC Statement
  • ResultSetHandler: responsible for converting the ResultSet result set object returned by JDBC into a List type collection
  • TypeHandler: responsible for mapping and conversion between java data types and jdbc data types
  • MappedStatement: MappedStatement maintains the encapsulation of a < select | update | delete | Insert > node
  • SqlSource: it is responsible for dynamically generating SQL statements according to the parameterObject passed by the user, encapsulating the information into the BoundSql object and returning
  • BoundSql: represents dynamically generated SQL statements and corresponding parameter information

Here comes the real source code analysis

The test class is posted here for easy viewing:

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import zjw.dao.UserDao;
import zjw.entity.MyUser;
public class MybatisTest {
	public static void main(String[] args) {
		try {
			// Read the configuration file mybatis-config.xml
			InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
			// Build SqlSessionFactory from configuration file
			SqlSessionFactory ssl = new SqlSessionFactoryBuilder().build(config);
			// Create a SQLSession object through SqlSessionFactory
			SqlSession ss = ssl.openSession();

			/*
			 * Method 1: SqlSession executes the sql defined in the mapping file and returns the mapping result
			 * gzc.mapper.MyUserMapper.selectUserById The id of the namespace + SQL statement in MyUserMapper.xml. For example:
			 * MyUser mu = ss.selectOne("gzc.mapper.MyUserMapper.selectUserById", 6);
			 */

			/*
			 * Method 2: get Mapper mapping and Dao interface mapping through getMapper method of SqlSession object
			 * This method needs to bind dao's interface to Mapper's namespace
			 */
			 
			// Associate the dao interface method with the mapping file and return the interface object
			UserDao userDao = ss.getMapper(UserDao.class);
			// Query a user
			MyUser user = userDao.selectUserById(1);
			System.out.println(user);
			// Add a user
			MyUser newUser = new MyUser(8, "floret", "female");
			userDao.addUser(newUser);
			// Modify a user
			MyUser updatemu = new MyUser(7, "Xiao Ming", "male");
			userDao.updateUser(updatemu);
			// Delete a user
			userDao.deleteUser(3);
			// Find all users
			List<MyUser> myUsers = userDao.selectAllUser();
			for (MyUser myUser : myUsers) {
				System.out.println(myUser);
			}
			ss.commit();
			ss.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

This is the basic step for Mybatis to operate the database.

InputStream config = Resources.getResourceAsStream("mybatis-config.xml");

The resource loads the main configuration file of mybatis to get the input stream object. Let's focus on the next line of code:

// Build SqlSessionFactory from configuration file
SqlSessionFactory ssl = new SqlSessionFactoryBuilder().build(config);

This line of code represents building a session factory object from the flow object of the main configuration file. The builder mode is used here: to create an object, you do not directly create new, but use other classes to create the object. All the initialization work of mybatis is completed by this line of code. Let's go deep into the source code.
1: Enter the build method

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //Delegate XMLConfigBuilder to parse xml files and build
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

You can see that an XMLConfigBuilder object will be created, which is used to parse the main Configuration file. We can find that the outermost node of the main Configuration file is the tag. The initialization of mybatis is to parse the tag and all its sub tags, and encapsulate the parsed data in the Configuration class.
2: Enter parse() method

//Resolution configuration
  public Configuration parse() {
    //If it has been parsed, an error is reported
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //The root node is configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

XMLConfigBuilder maintains a parsed attribute, which is false by default. This method first determines whether the main configuration file has been parsed, and throws an exception if it has been parsed.
3: Enter the parseConfiguration method

private void parseConfiguration(XNode root) {
    try {
      //Step by step analysis
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2. Type alias
      typeAliasesElement(root.evalNode("typeAliases"));
      //3. Plug in
      pluginElement(root.evalNode("plugins"));
      //4. Target factory
      objectFactoryElement(root.evalNode("objectFactory"));
      //5. Object packaging factory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6. Setting
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7. Environment
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9. Type processor
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10. Mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

This method is obviously to parse all sub tags one by one. For example, the settings attribute, which often appears in the Configuration file, will configure the cache, log, etc. in settings. typeAliases is the Configuration alias. environments are Configuration database links and transactions. These child nodes will be parsed one by one and the parsed data will be encapsulated in the Configuration class. You can see that the return value of the second method is the Configuration object. We focus on the mappers tag, which also has one mapper tag to map the mapper.xml corresponding to the mapper.
4: Enter mapperElement method

//	10.4 automatically scan all mappers under the package
//	<mappers>
//	  <package name="org.mybatis.builder"/>
//	</mappers>
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          //10.4 automatically scan all mappers under the package
          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) {
            //10.1 using classpath
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //The mapper is complex. Call XMLMapperBuilder
            //Note that in the for loop, each mapper creates a new XML mapperbuilder to parse
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //10.2 using absolute url path
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            //The mapper is complex. Call XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            //10.3 using java class names
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //Add this mapping directly to the configuration
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  1. This method starts with a loop. Because there may be many mapper nodes under a mapper node. There must be more than one mapper.xml in the application. Therefore, he will traverse each mappers node to parse the XML file mapped by the node.
  2. Below the loop is an if else judgment. It first determines whether the child node under mappers is a package node. Because there are many xml files in actual development, it is impossible to map every xml file with a mapper node. We directly use a package node to map all xml under a package, which is multi file mapping.
  3. If it is not the package node, it must be the mapper node for single file mapping. Through the following code, we find that there are three ways to map a single file. The first way is to directly map an xml file using the resource attribute of the mapper node. The second is to use the url attribute of the mapper node to map an xml file on disk. The third method is to directly map a mapper interface using the class attribute of the mapper node.

We mainly look at the resource method of single file mapping.
5: Parsing xml in resource mode

if (resource != null && url == null && mapperClass == null) {
            //10.1 using classpath
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //The mapper is complex. Call XMLMapperBuilder
            //Note that in the for loop, each mapper creates a new XML mapperbuilder to parse
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
  1. The first line of code is to instantiate an error context object. The function of this object is to encapsulate the error information. If an error occurs, the toString method of this object will be called. The resource parameter of this method is the xml name of String type, which corresponds to the xml file of mapper. Prompt the user which xml file the error occurred in.
  2. The second line represents reading the xml to get the input stream object.
  3. Then create an xml file parser for mapper.

6: Enter parse method

//analysis
  public void parse() {
    //If it is not loaded, reload it to prevent repeated loading
    if (!configuration.isResourceLoaded(resource)) {
      //Configure mapper
      configurationElement(parser.evalNode("/mapper"));
      //Mark it, it has been loaded
      configuration.addLoadedResource(resource);
      //Bind mapper to namespace
      bindMapperForNamespace();
    }

    //There's still something that hasn't been parsed. Do you want to parse it here?  
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

At first, judge whether the xml file has been parsed. Because the Configuration object will maintain a set collection of String type loadedResources, which stores the names of all parsed xml files. Due to the first parsing, all are directly entered into if logic judgment.
7: Enter the ConfigurationElement method

	//Configure mapper element
//	<mapper namespace="org.mybatis.example.BlogMapper">
//	  <select id="selectBlog" parameterType="int" resultType="Blog">
//	    select * from Blog where id = #{id}
//	  </select>
//	</mapper>
  private void configurationElement(XNode context) {
    try {
      //1. Configure namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2. Configure cache Ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3. Configure cache
      cacheElement(context.evalNode("cache"));
      //4. Configure parametermap (obsolete, old style parameter mapping)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5. Configure resultmap (advanced function)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6. Configure SQL (define reusable SQL code segments)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7. Configure select Insert update delete todo
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

This method is to parse all node data of a mapper.xml. Such as parsing namespace, resultMap, etc. Focus on the last line of code:

//7. Configure select Insert update delete todo
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

Let's follow in:

//7. Configure select Insert update delete
  private void buildStatementFromContext(List<XNode> list) {
    //Call 7.1 build statement
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

Continue to buildStatementFromContext()

//7.1 build statements
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //Build all statements, and there can be many select under one mapper
      //The statement is complex, and the core is in it, so call XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          //Core XMLStatementBuilder.parseStatementNode
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
          //If the SQL statement is incomplete, write it down and insert it into the configuration
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

At the beginning, it is a loop to traverse a list, which contains all sql nodes in xml, such as select, insert, update and delete. Each sql node is a node, and each sql node is parsed circularly

8: Enter the parseStatementNode() method

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    //If the databaseId does not match, exit
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    //Indicates the number of result lines returned by the driver in batch at a time
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //Timeout
    Integer timeout = context.getIntAttribute("timeout");
    //Reference external parameterMap, obsolete
    String parameterMap = context.getStringAttribute("parameterMap");
    //Parameter type
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //Reference external resultmap (advanced function)
    String resultMap = context.getStringAttribute("resultMap");
    //Result type
    String resultType = context.getStringAttribute("resultType");
    //Scripting language, new features of mybatis 3.2
    String lang = context.getStringAttribute("lang");
    //Get language driven
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    //Result set type, forward_ ONLY|SCROLL_ SENSITIVE|SCROLL_ One of insensive
    String resultSetType = context.getStringAttribute("resultSetType");
    //Statement type, a kind of STATEMENT|PREPARED|CALLABLE
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    //Get command type (select|insert|update|delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //Do you want to cache the select results
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //The select statement is only applicable to nested results: if it is true, it is assumed that nested result sets or groups are included. In this way, when a main result row is returned, there will be no reference to the previous result set.
    //This makes it possible to get nested result sets without running out of memory. Default: false. 
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    //Parse the < include > sql fragment before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    //Resolve < selectkey > before parsing
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //Resolve to SqlSource, usually DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    //(only useful for insert) mark an attribute, and MyBatis will set its value through getGeneratedKeys or through the selectKey sub element of the insert statement
    String keyProperty = context.getStringAttribute("keyProperty");
    //(only useful for insert) mark an attribute, and MyBatis will set its value through getGeneratedKeys or through the selectKey sub element of the insert statement
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

	//Go to the assistant class again
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

The general meaning is to parse all the data in the sql tag and encapsulate all the data in the MappedStatement object through the addMappedStatement method. This object encapsulates all the contents of the tag where an sql is located, such as the id of the sql tag, sql statement, input parameter, output parameter, etc.

9: Enter the addMappedStatement() method

//Add mapping statement
  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {
    
    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    
    //Prefix id with namespace
    id = applyCurrentNamespace(id, false);
    //Is it a select statement
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //Builder mode again
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
    statementBuilder.resource(resource);
    statementBuilder.fetchSize(fetchSize);
    statementBuilder.statementType(statementType);
    statementBuilder.keyGenerator(keyGenerator);
    statementBuilder.keyProperty(keyProperty);
    statementBuilder.keyColumn(keyColumn);
    statementBuilder.databaseId(databaseId);
    statementBuilder.lang(lang);
    statementBuilder.resultOrdered(resultOrdered);
    statementBuilder.resulSets(resultSets);
    setStatementTimeout(timeout, statementBuilder);

    //1. Parameter mapping
    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    //2. Result mapping
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

    MappedStatement statement = statementBuilder.build();
    //After construction, call configuration.addMappedStatement
    configuration.addMappedStatement(statement);
    return statement;
  }

Let's just look at the last three lines of code:

  1. MappedStatement statement = statementBuilder.build(); Build a MapperStatement object through the parsed parameters
  2. configuration.addMappedStatement(statement). This line is to load the parsed MapperStatement into the Map set maintained by configuration. The key value is the id value of the sql tag. Here we should be selectUserById, etc. the value value is the MapperStatement object we parsed.

Obviously, the purpose of parsing xml is to parse the sql tags added, deleted, modified and queried in each xml into mapperstatements, and load the parsed objects into the Configuration Map for backup.

10: We return to the code in step 6

public void parse() {
    //If it is not loaded, reload it to prevent repeated loading
    if (!configuration.isResourceLoaded(resource)) {
      //Configure mapper
      configurationElement(parser.evalNode("/mapper"));
      //Mark it, it has been loaded
      configuration.addLoadedResource(resource);
      //Bind mapper to namespace
      bindMapperForNamespace();
    }

    //There's still something that hasn't been parsed. Do you want to parse it here?  
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

From step 6 to step 9, you are executing the line of ConfigurationElement(parser.evalNode("/mapper")). Next, look at the next line of code configuration.addLoadedResource(resource); In step 9, we have completely parsed an xml, so we will load the parsed xml name into the set set.
Next, we enter the bindMapperForNamespace() method to bind mapper through the namespace.
Eleventh: enter the bindMapperForNamespace() method

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
  1. First, obtain the namespace, which is generally the fully qualified name of the mapper. Obtain the class object of the mapper through reflection.
  2. In the if judgment, a Map object is also maintained in the Configuration. The key value is the class object of the mapper we just produced through reflection, and the value value is the proxy object of the class object produced through dynamic proxy.
  3. Because the mapper object of production has not been added to the Map, enter the if, first save the namespace to the set set that just saved the xml name, and then save the class object of the mapper of production to mapper.

Step 12: enter the addMapper method

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

The addMapper method of mapperRegistry is called. This class is the mapper registration class

//Let's see how to add a map
  public <T> void addMapper(Class<T> type) {
    //mapper must be an interface! Will be added
    if (type.isInterface()) {
      if (hasMapper(type)) {
        //If it is added repeatedly, an error will be reported
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        //If an exception occurs during loading, you need to delete the mapper from mybatis. This method is ugly. Is it a last resort?
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

The name of the Map maintained by mapperRegistry is knownMappers. You can see that the key value of put is the class object of the generated mapper, and the value is the proxy object of the mapper generated through the dynamic proxy.
So far, mybatis is initialized according to the main configuration file. In summary:

  1. Resolve the main Configuration file and encapsulate all the information in the main Configuration file into the Configuration object.
  2. In detail, the main Configuration file is parsed through XmlConfigBuilder, and all xml files mapped under mappers are parsed through XmlMapperBuild (circular parsing). Parse each sql in each xml into MapperStatement objects and install them in a Map set maintained by Configuration. key is id and value is MapperStatement object. Then, the name and namespace of the parsed xml are loaded into the set set, and the class object of the mapper and the proxy object of the class object generated through namespace reflection are loaded into the Map in the mapperRegistry maintained by the Configuration object.
  3. We use resource to import xml by parsing xml, parsing sql tags into mapersstatement objects and loading them into the collection, and then loading the class objects and proxy objects of mapper interface into the collection. However, there are four ways to import xml, including single file import, url import and class import, As like as two peas, we can see that the url way is to directly introduce a xml and resource. The class method is to introduce a mapper interface, but it is the same as the resource and url methods

13: Multi file mapping

if ("package".equals(child.getName())) {
          //10.4 automatically scan all mappers under the package
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }

It first obtains the package name of xml, then calls the addMappers object of Configuration. Multi file mapping is addmappers

//Add all classes under the package to the mapper
  public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }
public void addMappers(String packageName, Class<?> superType) {
    //Find all superType classes under the package
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

Find out the names of all mappers under the package through the ResolverUtil parsing tool, load the class object of the production mapper into the collection through reflection, and then call the addmapper (maperclass) method circularly. In this way, just like the class type of single file mapping, the class object of the mapper interface is passed in as a parameter, and then the production proxy object is loaded into the collection, and then the xml is parsed.


Source code analysis of obtaining session object

// Build SqlSessionFactory from configuration file
			SqlSessionFactory ssl = new SqlSessionFactoryBuilder().build(config);
			// Create a SQLSession object through SqlSessionFactory
			SqlSession ss = ssl.openSession();

Directly open a session. Session is the top-level API for our interaction with the database. All additions, deletions, changes and queries must call session. We enter the opensession method

/Eventually, two methods will be called: openSessionFromDataSource,openSessionFromConnection
  //The following six methods will call openSessionFromDataSource
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //Generate a transaction through the transaction factory
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //Generate an actuator (the transaction is included in the actuator)
      final Executor executor = configuration.newExecutor(tx, execType);
      //Then a DefaultSqlSession is generated
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //If there is an error opening the transaction, close it
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //Finally, clear the error context
      ErrorContext.instance().reset();
    }
  }

Let's look at openSessionFromDataSource. Because we parsed the main Configuration file and saved all node information in the Configuration object, we directly obtained the information of the Environment node at the beginning, which configures database connections and transactions. After that, a transaction factory is created through the Environment, and a transaction object is instantiated through the transaction factory. Finally, an executor is created. We know that session is the top-level API for interacting with the database. An executor will be maintained in the session to be responsible for sql production and execution and query caching.

//Generating actuator
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    //This sentence is another protection to prevent careless people from setting defaultExecutorType to null?
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //Then there are three simple branches, which produce three kinds of executors: batchexecutor / reuseexecution / simpleexecution
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //If caching is required, another cacheingexecution is generated (there is caching by default), decorator mode, so cacheingexecution is returned by default
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //The plug-in is called here. The behavior of the Executor can be changed through the plug-in
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

This process is to judge which kind of actuator is generated. There are three kinds of mybatis actuators:

/**
 * Type of actuator
 *
 */
public enum ExecutorType {
    //ExecutorType.SIMPLE
    //This actuator type does nothing special. It creates a new preprocessing statement for the execution of each statement.
    //ExecutorType.REUSE
    //This executor type reuses preprocessing statements.
    //ExecutorType.BATCH
    //This executor will execute all update statements in batch. If SELECT is executed among them, it will also calibrate them. It is necessary to ensure a simple and easy to understand behavior.
  SIMPLE, REUSE, BATCH
}

Simpleexecution: simple executor, which is used by default in MyBatis. Every time update or select is executed, a Statement object will be opened, and the Statement object will be closed directly after use (it can be a Statement or Preparedstatement object)
Reuseexecution: Reusable executor. Reuse here refers to the reuse of statements. It will internally use a Map to cache the created statements. Each time an SQL command is executed, it will judge whether there is a Statement object based on the SQL, If there is a Statement object and the corresponding connection has not been closed, continue to use the previous Statement object and cache it.
Because each SqlSession has a new Executor object, the Statement scope cached on reuseexecution is the same SqlSession.
BatchExecutor: batch executor used to output multiple SQL to the database at one time.
If it is not configured or specified, the default generation is simpleexecution. After the Executor is generated, a DefaultSqlSession is returned, which maintains the Configuration and Executor.


Query process source code analysis

 /**
   * Retrieve a single row mapped from the statement key and parameter.
   * Obtain the encapsulated object of a record according to the specified SqlID, but this method allows us to pass some parameters to sql
   * Generally, in actual use, this parameter passes pojo, or Map or immutable Map
   * @param <T> the returned object type
   * @param statement Unique identifier matching the statement to use.
   * @param parameter A parameter object to pass to the statement.
   * @return Mapped object
   */
  <T> T selectOne(String statement, Object parameter);


//Core selectOne
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    //Instead, call selectList. It is very simple. If you get 0 items, null will be returned, and if you get 1 item, 1 item will be returned. You will get multiple reports of toomanyresultexception error
    // In particular, the main thing is that null will be returned when no query result is found. Therefore, it is generally recommended to use wrapper type when writing resultType in mapper
    //Instead of basic types, for example, Integer rather than int is recommended. This avoids NPE
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

Find the implementation class DefaultSqlSession and find that the selectList() method is called. In fact, querying one or more of them calls the selectList method. Enter this method:

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  //Core selectList
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //Find the corresponding MappedStatement according to the statement id
      MappedStatement ms = configuration.getMappedStatement(statement);
      //Instead, use the executor to query the results. Note that the ResultHandler passed in here is null
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

We focus on:

//Find the corresponding MappedStatement according to the statement id
      MappedStatement ms = configuration.getMappedStatement(statement);

When calling selectOne query, the parameters passed are the id value of sql: selectUserById and the parameter of sql: 1. In this line of code, the value of parameter statement is selectUserById. Let's recall whether each sql tag is parsed into mapperstates during the initialization of mybatis, and these mapperstates are loaded into a Map collection maintained by the configuration object, The key value of the Map set is the id of the sql tag, and the value is the corresponding mappersstatement object. We said that it is used here to load it into the set for standby. Here, the id value of the sql tag is used to get the corresponding MapperStatement object from the Map.
For example, the sql of selectUserById is called by the selectOne method, so now take the corresponding MapperStatement object from the Map maintained by configuration through the key value of selectUserById. Why take out this object? Because mybatis encapsulates all the data of an sql tag in a MapperStatement object. For example: out parameter type, out parameter value, in parameter type, in parameter value, sql statement, etc.
Then look at the next line of code:

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

MapperStatement is passed into the query method as a parameter. This query method is called by the actuator. We know that the role of the actuator is to generate and execute sql and query cache. In this query method, we will query cache and execute sql statements. We enter the query method

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
	//When query ing, pass in a cachekey parameter
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  //Called by ResultLoader.selectList
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    //By default, the cache is not enabled (L2 cache). To enable L2 cache, you need to add a line to your SQL mapping file: < cache / >
    //To put it simply, first check the CacheKey, and then delegate it to the actual actuator
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }


At the beginning, get the BoundSql object from MapperStatement. The real sql statement is encapsulated in this object. This object is also responsible for replacing the placeholder in sql with the parameters we pass. It's just that MapperStatement maintains the reference of BoundSql.
Then look at createcache key, which means that a cache key is generated according to these parameters. When we call the same sql and the parameters passed are the same, the generated cache key is the same.
At first, we get the cache, but this cache is not where we store query results. It should be a secondary cache. If the query cache is null, the last sentence of code will be executed.

delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

The last line of code calls the delegate.query method. Delegate is a reference to an executor. Here, it is actually a reference to the simpleexecution simple executor. We know that an executor will be created when obtaining a session. If it is not configured, the simpleexecution will be created by default. Here, maintain the reference of simpleexecution in cachexcutor. Therefore, if the cache executor fails to execute sql, it is handed over to simpleexecution for execution.

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    //If it has been closed, an error is reported
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //Clear the local cache first and then query. However, it is cleared only when the query stack is 0. To handle recursive calls
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //Add one, so that the local cache will not be cleared when recursive calls are made to the above
      queryStack++;
      //First check from the localCache according to the cachekey
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //If the localCache cache is found, process the localOutputParameterCache
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //Query from database
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //Empty stack
      queryStack--;
    }
    if (queryStack == 0) {
      //Delay loading all elements in the queue
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      //Clear delay load queue
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
    	//If it is state, clear the local cache
        clearLocalCache();
      }
    }
    return list;
  }

At the beginning, a collection list is declared, and then through the cache key we created earlier, we go to the local cache to query whether there is cache. The following judgment is made. If the collection is not null, deal with the cache data and directly return to the list. If there is no cache, he will query from the database. You can see what their name means by looking at queryFromDatabase, We are now executing the first selectOne without caching. We enter the queryFromDatabase method.

//Query from database
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //Put placeholders in the cache first???
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //Delete placeholder last
      localCache.removeObject(key);
    }
    //Add cache
    localCache.putObject(key, list);
    //If it is a stored procedure, the OUT parameter is also added to the cache
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

Execute the doQuery method to find the data from the data and put it into the cache.

//select
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //Create a StatementHandler
      //Here you see the result handler passed in
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //Prepare statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

At the beginning, the doQuery method takes out the session processor Statementhandler from the Configuration to install the JDBC statement operation and be responsible for the operation of the JDBC statement. The steps for JDBC to operate the database are usually: register the driver - > get the connection - > create the session object (the statement mentioned above or the preparation that can prevent injection attack) - > execute the sql statement - > process the result set - > close the connection
After obtaining the session processor, the prepareStatement method is executed

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //Call StatementHandler.prepare
    stmt = handler.prepare(connection);
    //Call StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }

At first, getConnection obtains the database connection, and then executes handler.prepare(); The purpose of this method is to create a session object based on the connection transaction. Enter the prepare method

//Prepare statement
  @Override
  public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //Instantiate Statement
      statement = instantiateStatement(connection);
      //Set timeout
      setStatementTimeout(statement);
      //Set the number of reads
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

Focus on instantiateStatement:

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    //Call Connection.prepareStatement
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

We found that all return values are prepareStatement precompiled session objects, indicating that mybatis can prevent injection attacks by default.
Back to the prepareStatement method

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //Call StatementHandler.prepare
    stmt = handler.prepare(connection);
    //Call StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }

After obtaining the session object, execute the handler.parameterize(stmt) method; This step is basically the same as the step of obtaining the session object

public void parameterize(Statement statement) throws SQLException {
    //Call ParameterHandler.setParameters
    parameterHandler.setParameters((PreparedStatement) statement);
  }

The parameterHandler parameter processor is used here: it is responsible for converting the parameters passed by the user into the data type corresponding to JDBC Statement, that is, converting String into the type used by databases such as varchar.
So far, we have obtained the session object and set the JDBC parameters. We can continue to execute the doQuery method:

//select
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //Create a StatementHandler
      //Here you see the result handler passed in
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //Prepare statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

After obtaining the precompiled session object, directly execute the query method:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

Convert the session object into a preparedStatement precompiled session object, and then call the execute method with the session object. This is the same as using jdbc by calling the execute method.
After the sql is executed, we need to process the result set. The resultSetHandler is used in the return. The result set processor is responsible for converting the ResultSet result set object returned by JDBC into a List type collection, that is, converting the data we find in the database into a List type. Now we are the selectOne method, so there is only one piece of data in this collection.
As like as two peas, we have finished the steps of a query, which is actually the process of encapsulating the jdbc operation database. It is the same as jdbc's operation of database. Its encapsulation is to make it easier for us to transfer and process result set.
At this time, the queried data has been put in the cache. If the second query statement is called again, the database will not be operated, but the data will be taken directly from the cache.


Add update delete operation

The steps are as like as two peas, which are to extract the corresponding MapperStatement objects from the Map set maintained by configuration at the very beginning by the id value of the sql tag, and then execute the sql by encapsulating jdbc. The doQuery method of simpleexecution is used at the end of query, while the doUpdate method of simpleexecution is used at the end of add, delete, and update. Because mybatis thinks that add, delete, and update are operations that update the database, I don't believe we post the doUpdated code

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //Create a StatementHandler
      //You can see that the result handler passed in null
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      //Prepare statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.update
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

Except for the last sentence handler.update, the rest is consistent with the doQuery code.

That's all for the operation principle of Mybatis. You are welcome to criticize and correct.

Tags: Java Mybatis

Posted on Sun, 24 Oct 2021 08:17:08 -0400 by scald