Look along the Hubble and see the overall architecture of MyBatis

1, Foreword

MyBatis is a very popular ORM framework at present. Its function is very powerful, but its implementation is relatively simple and elegant. This paper mainly describes the architecture design idea of MyBatis, discusses several core components of MyBatis, and then explores the implementation of MyBatis by combining a select query example and going deep into the code.

2, MyBatis four tier architecture

MyBatis is divided into interface layer, data processing layer, framework support layer and guidance layer from top to bottom, as follows:

Function of interface layer: the interface layer is used to receive requests and call sql statements.
Interface layer details: the Mybatis interface layer provides two database access methods. The Mybatis Statement ID accesses the database and the Mapper interface accesses the database. The bottom layer is the same. Both create a new SqlSession object and pass in the statementId and parameters to it. Finally, use SqlSession to transfer sql statements;

Function of data processing layer: process data.
Data processing includes four steps:
(1) Parameter mapping (in the query stage, java type data is converted into jdbc type data, and the value is set through preparedStatement.setXXX(); in the return stage, the data of jdbc type in the resultset query result set is converted into java type data);
(2) Dynamic generation of SQL statements;
(3) sql statement execution;
(4) Return value processing (for the returned result set, it supports the one-to-many and many to one conversion of the result set relationship, and there are two support methods, one is the query of nested query statements, and the other is the query of nested result sets.)

Function of framework support layer: under the data processing layer, it provides technical support for data processing.
Technical support includes four: cache, transaction, data source and connection pool, and support for two configuration modes (xml configuration and annotation + annotation driven). The cache includes level-1 cache and level-2 cache, which are used to speed up the select query, and the update/delete/insert cache is cleared; Ensure consistency of transactions; The connection pool realizes Collection connection reuse;

Role of boot layer: located at the bottom, it is used to guide the startup and operation of the whole mybatis.
The boot layer includes two ways: xml configuration and Java api.

2.1 interface layer

The interface layer provides two ways of interaction between MyBatis and database:

a. Use the traditional API provided by MyBatis, that is, statementId;

b. Using Mapper interface

2.1.1 MyBatis uses Statement ID to interact with database

MyBatis provides a very convenient and simple API for users to add, delete, modify and query data in the database, and maintain database connection information and MyBatis's own configuration information, as follows:

As shown in the above figure, MyBatis uses API to interact with the database, which is divided into two steps:
(1) Create a SqlSession object that deals with the database
(2) According to the Statement Id and parameters passed to the SqlSession object, the SqlSession object is used to complete the interaction with the database

SqlSession session = sqlSessionFactory.openSession();   // SqlSession object
Blog blog =(Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id); // statement Id

The above method of using MyBatis is to create a SqlSession object dealing with the database, and then operate the database according to the Statement Id and parameters. Although this method is very simple and practical, it does not conform to the concept of object-oriented language and the programming habit of interface oriented programming. Since interface oriented programming is the general trend of object-oriented, MyBatis has added the second way to use MyBatis to support Mapper interface calls in order to adapt to this trend.

2.1.2 Mybatis uses Mapper interface to interact with database

Mybatis uses Mapper interface to interact with the database, which conforms to the design principle of object-oriented interface. The steps are as follows:

(1) MyBatis abstracts each < Mapper > node in the configuration file into a Mapper interface;

(2) The method declared in Mapper interface corresponds to the < select | update | delete | Insert > node item in < Mapper > node;

(3) The id value of the < select | update | delete | Insert > node is the method name in the Mapper interface, the parameterType value represents the input parameter type of the method corresponding to Mapper, and the resultMap value corresponds to the return value type represented by the Mapper interface or the element type of the returned result set.

After configuration according to MyBatis configuration specification, the actual operation steps of the bottom layer are as follows:

(1) Instantiate Mapper interface implementation class: through SqlSession.getMapper(XXXMapper.class) method, MyBatis underlying source code will generate a Mapper instance through dynamic proxy mechanism according to the method information declared by the corresponding interface;

(2) The method name and parameter list are used as the method signature (the unique identification of the method) to determine the statement ID. the bottom layer still uses SqlSession to call the sql statement: when we use a method of Mapper interface, MyBatis will determine the statement ID according to the method name and parameter type of the method, and the bottom layer still uses SqlSession.select("statementId",parameterObject); Or SqlSession.update("statementId",parameterObject); And so on to realize the operation of the database;

MyBatis refers to Mapper interface for two reasons:

(1) In order to meet the needs of interface oriented programming;
(2) This enables users to use annotations to configure SQL statements on the interface, so that they can break away from the XML configuration file and realize "0 configuration".

Summary:
The Mybatis interface layer provides two database access methods, the Mybatis Statement ID and the mapper interface. The bottom layer is the same. Using mapper also determines the statement id through the method name and parameter list as the method signature, and uses SqlSesson to call sql statements;
The interface layer is the first of the four Mybatis layers. Its function is to receive requests and start calling sql statements.

2.2 data processing layer

The data processing layer can be said to be the core of MyBatis. In general, it needs to complete two or four functions:

a. Construct dynamic SQL statements by passing in parameters; (parameter mapping + dynamic SQL statement generation)

b. The execution of SQL statements and the integration of encapsulated query results. (SQL execution + result processing)

2.2.1 parameter mapping

Parameter mapping refers to the conversion between java data types and jdbc data types:

There are two processes:

First, in the query phase, java type data is converted into jdbc type data, and the value is set through preparedStatement.setXXX();

Second, in the return phase, the data of JDBC type in the resultset query result set is converted into java type data.

2.2.2 dynamic SQL statement generation

Dynamic statement generation can be said to be a very elegant design of MyBatis framework. MyBatis uses Ognl to dynamically construct SQL statements through the passed in parameter values, which makes MyBatis highly flexible and extensible.

2.2.3 execution of SQL statement

After the dynamic SQL statement is generated, MyBatis executes the SQL statement.

2.2.4 result processing

After Mybatis executes the SQL statement, it may convert the returned result set into a List.

MyBatis supports the one-to-many and many to one conversion of result set relationships in the processing of result sets, and there are two support methods, one is the query of nested query statements, and the other is the query of nested result sets.

Interviewer: what is your understanding of mybatis Association query (nested statement query and nested result query of Mapper interface)?

2.3 frame support layer

2.3.1 transaction management mechanism

Transaction management mechanism is an indispensable part of ORM framework, and the quality of transaction management mechanism is also a standard to consider whether an ORM framework is excellent or not.

2.3.2 connection pool management mechanism

Because creating a database connection takes up a lot of resources, the design of connection pool is very important for applications with large data throughput and very large access.

2.3.3 caching mechanism

In order to improve data utilization and reduce the pressure on the server and database, MyBatis will provide session level data cache for some queries. It will place a query into SqlSession. Within the allowable time interval, MyBatis will directly return the cache results to users for identical queries without looking in the database.

2.3.4 configuration mode of SQL statement

When the interface layer MyBatis uses Statement ID and database, the framework support layer can only use XML configuration; When the interface layer MyBatis uses mapper interface to interact with the database, the framework support layer can use XML configuration mode or annotation configuration mode. It is recommended to use annotation configuration mode, which is in line with the object-oriented idea. In the later stage, general mapper is introduced, and sql statements in mapper.xml are no longer required for single table query.

The traditional method of configuring SQL statements in MyBatis is to use XML files, but this method can not well support the concept of interface oriented programming. In order to support interface oriented programming, MyBatis introduces the concept of Mapper interface. The introduction of interface oriented makes it possible to use annotations to configure SQL statements, Users only need to add necessary annotations on the interface (generally @ Mapper annotations), and there is no need to configure XML files.

In Spring, @ Service @Controller annotation + @ ComponentScan scanning annotation is equivalent to xml configuration
In Mybatis, @ Mapper annotation + @ MapperScan scan annotation is equivalent to xml configuration
In short, annotation + scanning is equivalent to xml configuration.

2.4 guide layer

The boot layer is the way to configure and start MyBatis configuration information.

MyBatis provides two ways to guide MyBatis: XML configuration file based and Java API based.

3, Main components of MyBatis and their relationship

From the perspective of MyBatis code implementation, the main core components of MyBatis are as follows:

Component nameeffect
Sqlsession (interface layer)As the main top-level API of MyBatis, it represents the session interacting with the database and completes the necessary database addition, deletion, modification and query functions
Executor (data processing layer - dynamic SQL statement generation)MyBatis executor, the core of MyBatis scheduling, is responsible for generating SQL statements and maintaining query cache
Statementhandler (data processing layer - dynamic SQL statement generation)It encapsulates the JDBC statement operation and is responsible for the operation of JDBC statement, such as setting parameters and converting the Statement result set into a List set.
Parameterhandler (data processing layer - parameter mapping)It is responsible for converting the parameters passed by the user into the parameters required by JDBC Statement
Resultsethandler (data processing layer - result processing and mapping)It is responsible for converting the ResultSet result set object returned by JDBC into a List type collection
Typehandler (data processing layer - parameter mapping)Responsible for the mapping and conversion between java data types and jdbc data types
Mappedstatement (interface layer)MappedStatement maintains an encapsulation of < select | update | delete | Insert > nodes
Sqlsource (data processing layer SQL parsing)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 (data processing layer - dynamic SQL statement generation)Represents dynamically generated SQL statements and corresponding parameter information
ConfigurationAll Configuration information of MyBatis is maintained in the Configuration object.

Note: here are only the components that I personally think belong to the core. Please don't preconceived that MyBatis has only these components! Everyone has different understanding of MyBatis, and the analysis results will naturally be different. Readers are welcome to raise questions and different opinions, and we will discuss them together. Their relationship is shown in the figure below:

mybatis and jdbc are two different things

(1) mybatis is an ORM framework and object relationship mapping framework for server-side programs. In the figure above, only ten components of mybatis are explained, not jdbc components.

(2) jdbc is a java database connection tool. It is only used for Java statements, but it can connect to different databases, usually mysql.

Mybatis is below the server program and jdbc is above the database, so in the above figure, mybatis is above and jdbc is below.

4, Analyze the architecture design of MyBatis from a select query statement of MyBatis

4.1 from a macro perspective

4.1.1 database data preparation

   --Create an employee basic information table
    create  table "EMPLOYEES"(
        "EMPLOYEE_ID" NUMBER(6) not null,
       "FIRST_NAME" VARCHAR2(20),
       "LAST_NAME" VARCHAR2(25) not null,
       "EMAIL" VARCHAR2(25) not null unique,
       "SALARY" NUMBER(8,2),
        constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
    );
    comment on table EMPLOYEES is 'Employee information form';
    comment on column EMPLOYEES.EMPLOYEE_ID is 'staff id';
    comment on column EMPLOYEES.FIRST_NAME is 'first name';
    comment on column EMPLOYEES.LAST_NAME is 'last name';
    comment on column EMPLOYEES.EMAIL is 'email address';
    comment on column EMPLOYEES.SALARY is 'salary';
    
    --Add data
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (100, 'Steven', 'King', 'SKING', 24000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);    

4.1.2 Mybatis configuration file

The configuration file is generally named mybatisconfig.xml (any name), and the contents are as follows:

<?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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
	 <property name="driver" value="oracle.jdbc.driver.OracleDriver" />  
         <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />  
         <property name="username" value="louis" />  
         <property name="password" value="123456" />
      </dataSource>
    </environment>
  </environments>
    <mappers>
       <mapper  resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
    </mappers>
</configuration>

mapper can be identified in two ways:
mybatisConfig.xml or @ Mapper annotation + @ MapperScan annotation

4.1.3 parameter mapping

POJO+XxxMapper.java+XxxMapper.xml

package com.louis.mybatis.model;
 
import java.math.BigDecimal;
 
public class Employee {
    private Integer employeeId;
 
    private String firstName;
 
    private String lastName;
 
    private String email;
 
    private BigDecimal salary;
 
    public Integer getEmployeeId() {
        return employeeId;
    }
 
    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public BigDecimal getSalary() {
        return salary;
    }
 
    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
}
<?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" >
<mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >
 
  <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
    <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
    <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
    <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
    <result column="EMAIL" property="email" jdbcType="VARCHAR" />
    <result column="SALARY" property="salary" jdbcType="DECIMAL" />
  </resultMap>
  
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    	EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
    	from LOUIS.EMPLOYEES
    	where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
  </select>
</mapper>

XxxMapper.xml is used to write sql statements. After using the general mapper, the single table query does not need to write sql statements in XxxMapper.xml.

4.1.4 related dependencies

In pom.xml, import mybatis and jdbc dependencies as follows:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>batis</groupId>
  <artifactId>batis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>batis</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
 
    <dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.2.7</version>
	</dependency>
    
	<dependency>
		<groupId>com.oracle</groupId>
		<artifactId>ojdbc14</artifactId>
		<version>10.2.0.4.0</version>
	</dependency>
    
  </dependencies>
</project>

4.1.5 client code

package com.louis.mybatis.test;
 
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
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 com.louis.mybatis.model.Employee;
 
/**
 * SqlSession Simple query demo class
 * @author louluan
 */
public class SelectDemo {
 
	public static void main(String[] args) throws Exception {
		/*
		 * 1.Load the configuration file of mybatis, initialize mybatis, and create SqlSessionFactory, which is the factory that creates SqlSession
		 * This is just for demonstration. SqlSessionFactory is created temporarily. In actual use, SqlSessionFactory only needs to be created once and used as a single example
		 */
		InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		SqlSessionFactory factory = builder.build(inputStream);
		
		//2. Create a SqlSession from the SqlSessionFactory of the SqlSession factory for database operation
		SqlSession sqlSession = factory.openSession();
	
		//3. Use SqlSession query
		Map<String,Object> params = new HashMap<String,Object>();
		
		params.put("min_salary",10000);
		//a. Query employees whose salary is less than 10000
		List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
		//b. No minimum wage, check all employees
		List<Employee> result1 = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
		System.out.println("Number of employees with salary less than 10000:"+result.size());
		//~output: total data queried: 5  
		System.out.println("Number of all employees: "+result1.size());
		//~output: number of all employees: 8
	}
 
}

From 4.1.1 to 4.1.5, table design, mybatisConfig.xml (two methods of mapper, configuration in XML or @ Mapper+@MapperScan), POJO + xxmapper.java + xxmapper.xml (using general mapper, single table operation does not need to write sql statements), dependency import (mybatis dependency and jdbc dependency), and client code (get InputStream from the configuration file, then SqlSessionFactoryBuilder, then SqlSessionFactory, then SqlSession, pass in statementid and parameters, execute sql statements and get the result set)

4.2 mybatis query from the perspective of source code

The whole working process of SqlSession is introduced here

4.2.1 start a database access session

The code implementation of opening a database access session is to create a SqlSession object, as follows:

SqlSession sqlSession = factory.openSession();

MyBatis encapsulates the access to the database and puts the session and transaction control of the database into the SqlSession object.

As shown in the figure above, there are two contents in SqlSession: transaction control + data query. Transaction control is commit and rollback, and data query is select update insert delete.

4.2.2 SqlSession component

The SqlSession object calls the selectList method, passes a StatementId and parameter of the configuration Sql statement, and then returns the result. The code is as follows:

List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

The above "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary" is the Statement ID configured in EmployeesMapper.xml, and params is the passed query parameter.

Note: here is statementId, which is the class name + method name

Let's take a look at the definition of sqlSession.selectList() method:

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
 
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1. According to the Statement Id, find the MappedStatement corresponding to the Configuration file in the mybatis Configuration object Configuration	
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2. Delegate the query task to the Executor of MyBatis
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

During the initialization of MyBatis, all the configuration information of MyBatis will be loaded into memory and maintained using the org.apache.ibatis.session.Configuration instance. Users can use the sqlSession.getConfiguration() method to obtain it. The organization format of configuration information in the configuration file of MyBatis almost exactly corresponds to the organization format of objects in memory. In the above example

  <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
    select 
    	EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
    	from LOUIS.EMPLOYEES
    	<if test="min_salary != null">
    		where SALARY < #{min_salary,jdbcType=DECIMAL}
    	</if>
  </select>

When loaded into memory, a corresponding MappedStatement object will be generated, and then maintained in a Map of Configuration in the form of key = "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary", value is MappedStatement object. When it needs to be used in the future, it only needs to be obtained through Id value.

From the above code, we can see that the function of SqlSession is: SqlSession gets the corresponding MappedStatement object in mybatis configuration object Configuration according to Statement ID, then invokes mybatis executor Executor to perform specific operation. The code is as follows:

 //1. According to the Statement Id, find the MappedStatement corresponding to the Configuration file in the mybatis Configuration object Configuration. In the Configuration, there are key and value. Here, the value is obtained according to the key
MappedStatement ms = configuration.getMappedStatement(statement);
//2. Delegate the query task to the Executor of MyBatis for execution
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

SqlSession's mission is completed. Watch the Executor's performance.

4.2.3 Executor

The MyBatis Executor executes the query() method according to the parameters passed by SqlSession, as follows:

/**
* BaseExecutor Class partial code
*
*/
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	  
	// 1. Dynamically generate the SQL statement to be executed according to the specific incoming parameters, which is represented by BoundSql object  
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2. Create a cache Key for the current query
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
 
  @SuppressWarnings("unchecked")
  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 (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	// 3. If there is no value in the cache, read the data directly from the database  
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //4. Execute the query, return the List result, and then put the query result into the cache
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

Further, how does the doQuery() method execute?

/**
*
*SimpleExecutor Implementation of doQuery() method of class
*
*/
  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();
      //5. Create StatementHandler object to execute query operation according to existing parameters
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //6. Create a java.Sql.Statement object and pass it to the StatementHandler object
      stmt = prepareStatement(handler, ms.getStatementLog());
      //7. Call the StatementHandler.query() method to return the List result set
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

After several twists and turns, the above Executor.query() method will finally create a StatementHandler object, pass the necessary parameters to StatementHandler, use StatementHandler to complete the query on the database, and finally return the List result set.

From the above code, we can see that the functions and functions of the Executor are:

(1) according to the passed parameters, complete the dynamic parsing of SQL statements and generate BoundSql objects for StatementHandler

// 1. Dynamically generate the SQL statement to be executed according to the specific incoming parameters, which is represented by BoundSql object  
BoundSql boundSql = ms.getBoundSql(parameter); 

(2) create a cache for queries to improve performance

// 2. Create a cache Key for the current query
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// This query() uses both the dynamically generated boundSql and the cache key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 

(3) if there is no value in the cache, read the data directly from the database

  if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  } else {
	// 3. If there is no value in the cache, read the data directly from the database  
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

(4) execute the query, return the List result, and then put the query result into the cache

try {
  //4. Execute the query, return the List result, and then put the query result into the cache
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
  localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
}
return list;

(5) in the specific doQuery() query, create a JDBC Statement connection object, pass it to the StatementHandler object, and return the List query result

  //5. Create StatementHandler object to execute query operation according to existing parameters
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  //6. Create a java.Sql.Statement object and pass it to the StatementHandler object
  stmt = prepareStatement(handler, ms.getStatementLog());
  //7. Call the StatementHandler.query() method to return the List result set
  // StatementHandler as the caller and Statement as the argument
  return handler.<E>query(stmt, resultHandler);

OK, the task of the Executor is completed. Watch the performance of StatementHandler and Statement. The core is:

  // StatementHandler as the caller and Statement as the argument
  return handler.<E>query(stmt, resultHandler);

4.2.4 StatementHandler and Statement

The StatementHandler object is responsible for setting the query parameters in the Statement object, processing the resultSet returned by JDBC, processing the resultSet into a List set and returning it. The implementation of the doQuery() method is as follows (next to steps 6 and 7 of the above Executor):

/**
*
*SimpleExecutor Implementation of doQuery() method of class
*
*/
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(); 
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 
   // 1. Prepare the Statement object and set the parameters of the Statement object 
   stmt = prepareStatement(handler, ms.getStatementLog()); 
   // 2. StatementHandler executes the query() method and returns the List result 
   return handler.<E>query(stmt, resultHandler); 
   } finally { 
   closeStatement(stmt); 
   } 
 }
 
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //Set parameters for the created Statement object, that is, set "in the SQL Statement as the specified parameter
    handler.parameterize(stmt);
    return stmt;
  }

We can conclude from the above that the StatementHandler object mainly completes two tasks:

(1. Setting parameters: for objects of type PreparedStatement of JDBC, we use SQL statements during creation. The string will contain several placeholders. We then set the value of the placeholder. The StatementHandler sets the value of the Statement through the parameterize(statement) method;

(2. Query: StatementHandler executes the Statement through the List query (Statement statement, resulthandler, resulthandler) method, and encapsulates the resultSet returned by the Statement object into a List.

StatementHandler has two functions: setting parameters and querying.

4.2.4.1 setting parameters

Setting parameters: implementation of parameterize(statement) method of StatementHandler

/**
*   StatementHandler Class 
*/
public void parameterize(Statement statement) throws SQLException {
	//Use the ParameterHandler object to set the value of the Statement  
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  /**
   * 
   *ParameterHandler Implementation of setParameters(PreparedStatement ps) of class
   * Set parameters for a Statement
   */
  public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          
          // Each Mapping has a TypeHandler. Set the parameters of preparedStatement according to the TypeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          // Set the parameter, where the java type is changed to jdbc type
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

From the above code, we can see that the parameterize(Statement) method of StatementHandler calls the setParameters(statement) method of ParameterHandler. This method performs the "statement" of the statement object according to the parameters we enter? Assign a value at the placeholder as follows:

typeHandler.setParameter(ps, i + 1, value, jdbcType);

Explain the setParameter method: get a parameterMappings from boundSql dynamic Sql, and then traverse the list. For each parameterMapping, get the propertyName from parameterMapping, get the value from propertyName, then get the typeHandler from parameterMapping, get the JDBC type from parameterMapping, and finally use typeHandler.setParameter(ps, i + 1, value, jdbcType);

4.2.4.2 query

Query: implementation of list < E > query (statement statement, resulthandler, resulthandler) method of StatementHandler

  /**
   * PreParedStatement Class
   */
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
	// 1. Call preparedStatemnt.execute() method, and then give resultSet to ResultSetHandler for processing  
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //2. Use ResultHandler to process ResultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }

StatementHandler gives the query business to ResultSetHandler to complete. The following protagonist becomes ResultSetHandler

/**  
* DefaultResultSetHandler Class
*
*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
 
    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
 
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      
      //Set resultSet
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
 
    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
 
    return collapseSingleResultList(multipleResults);
  }


From the above code, we can see that the implementation of the List query (Statement statement, resulthandler, resulthandler) method of StatementHandler calls the handleResultSets(Statement) method of ResultSetHandler. The handleResultSets(Statement) method of ResultSetHandler will convert the resultSet result set generated after the execution of the Statement statement into a List result set.

4.2.5 summary

The first protagonist SqlSession

SqlSession gets the corresponding MappedStatement object in mybatis configuration object Configuration according to Statement ID, then invokes the mybatis executor to perform the specific operation.

  //1. According to the Statement Id, find the MappedStatement corresponding to the Configuration file in the mybatis Configuration object Configuration. In the Configuration, there are key and value. Here, the value is obtained according to the key
  MappedStatement ms = configuration.getMappedStatement(statement);
  //2. Delegate the query task to the Executor of MyBatis for execution
  List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

SqlSession's mission is completed. Watch the Executor's performance.

The second protagonist Executor

The functions and functions of the Executor are:

(1) according to the passed parameters, complete the dynamic parsing of SQL statements and generate BoundSql objects for StatementHandler

// 1. Dynamically generate the SQL statement to be executed according to the specific incoming parameters, which is represented by BoundSql object  
BoundSql boundSql = ms.getBoundSql(parameter); 

(2) create a cache for queries to improve performance

// 2. Create a cache Key for the current query
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// This query() uses both the dynamically generated boundSql and the cache key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 

(3) if there is no value in the cache, read the data directly from the database

  if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  } else {
	// 3. If there is no value in the cache, read the data directly from the database  
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

(4) execute the query, return the List result, and then put the query result into the cache

try {
  //4. Execute the query, return the List result, and then put the query result into the cache
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
  localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
}
return list;

(5) in the specific doQuery() query, create a JDBC Statement connection object, pass it to the StatementHandler object, and return the List query result

  //5. Create StatementHandler object to execute query operation according to existing parameters
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  //6. Create a java.Sql.Statement object and pass it to the StatementHandler object
  stmt = prepareStatement(handler, ms.getStatementLog());
  //7. Call the StatementHandler.query() method to return the List result set
  return handler.<E>query(stmt, resultHandler);

The third protagonist StatementHandler, the fourth protagonist parameterhandler, the fifth protagonist TypeHandler and the sixth protagonist ResultSetHandler

We can conclude from the above that the StatementHandler object mainly completes two tasks:

(1. Setting parameters: for objects of type PreparedStatement of JDBC, we use SQL statements during creation. The string will contain several placeholders. We then set the value of the placeholder. The StatementHandler sets the value of the Statement through the parameterize(statement) method;

typeHandler.setParameter(ps, i + 1, value, jdbcType);

Explain the setParameter method: get a parameterMappings from boundSql dynamic Sql, and then traverse the list. For each parameterMapping, get the propertyName from parameterMapping, get the value from propertyName, then get the typeHandler from parameterMapping, get the JDBC type from parameterMapping, and finally use typeHandler.setParameter(ps, i + 1, value, jdbcType);

(2. Query: StatementHandler executes the Statement through the List query (Statement statement, resulthandler, resulthandler) method, and encapsulates the resultSet returned by the Statement object into a List.

StatementHandler gives the query business to ResultSetHandler. For ResultSetHandler, TypeHandler class should also be used to convert jdbc type to java type when returning, but it is not found. Just search getTypeHandler() in DefaultResultSetHandler class.

5, Epilogue

"Look along the Hubble, see the overall architecture of MyBatis", completed.

Make progress every day!!!

Tags: Java Database SQL

Posted on Thu, 07 Oct 2021 02:14:27 -0400 by everogrin