Analyze the working principle of MyBatis from the perspective of source code

1, MyBatis complete example

Here, I'll use an entry-level example to demonstrate how MyBatis works.

Note: the principles and source code in the later chapters of this article will also be explained based on this example. Full sample source address

1.1. Database preparation

In this example, CRUD operation is required for a user table. The data model is as follows:

CREATE TABLE IF NOT EXISTS user (
    id      BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Id',
    name    VARCHAR(10)         NOT NULL DEFAULT '' COMMENT 'user name',
    age     INT(3)              NOT NULL DEFAULT 0 COMMENT 'Age',
    address VARCHAR(32)         NOT NULL DEFAULT '' COMMENT 'address',
    email   VARCHAR(32)         NOT NULL DEFAULT '' COMMENT 'mail',
    PRIMARY KEY (id)
) COMMENT = 'User table';

INSERT INTO user (name, age, address, email)
VALUES ('Zhang San', 18, 'Beijing', 'xxx@163.com');
INSERT INTO user (name, age, address, email)
VALUES ('Li Si', 19, 'Shanghai', 'xxx@163.com');

1.2. Add MyBatis

If you use Maven to build a project, you need to put the following dependent code in the pom.xml file:

<dependency>
  <groupId>org.Mybatis</groupId>
  <artifactId>Mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

1.3. MyBatis configuration

The XML configuration file contains the core settings for the MyBatis system, including the data source to obtain the database connection instance and the transaction manager to determine the transaction scope and control mode.

In this example, only the simplest configuration is given. [example] MyBatis-config.xml file

<?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="com.mysql.cj.jdbc.Driver" />
        <property name="url"
                  value="jdbc:mysql://127.0.0.1:3306/spring_tutorial?serverTimezone=UTC" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="Mybatis/mapper/UserMapper.xml" />
  </mappers>
</configuration>

Note: the above configuration file only specifies the data source connection method and the mapping configuration file of the User table.

1.4 Mapper

1.4.1 Mapper.xml

Personally, Mapper.xml file can be regarded as the JDBC SQL template of MyBatis. [example] UserMapper.xml file.

The following is a complete Mapper file automatically generated by MyBatis Generator.

<?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="io.github.dunwu.spring.orm.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="io.github.dunwu.spring.orm.entity.User">
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="age" jdbcType="INTEGER" property="age" />
    <result column="address" jdbcType="VARCHAR" property="address" />
    <result column="email" jdbcType="VARCHAR" property="email" />
  </resultMap>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    delete from user
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="io.github.dunwu.spring.orm.entity.User">
    insert into user (id, name, age,
      address, email)
    values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER},
      #{address,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR})
  </insert>
  <update id="updateByPrimaryKey" parameterType="io.github.dunwu.spring.orm.entity.User">
    update user
    set name = #{name,jdbcType=VARCHAR},
      age = #{age,jdbcType=INTEGER},
      address = #{address,jdbcType=VARCHAR},
      email = #{email,jdbcType=VARCHAR}
    where id = #{id,jdbcType=BIGINT}
  </update>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select id, name, age, address, email
    from user
    where id = #{id,jdbcType=BIGINT}
  </select>
  <select id="selectAll" resultMap="BaseResultMap">
    select id, name, age, address, email
    from user
  </select>
</mapper>

1.4.2 Mapper.java

Mapper.java file is the Java object corresponding to Mapper.xml. [example] UserMapper.java file

public interface UserMapper {

    int deleteByPrimaryKey(Long id);

    int insert(User record);

    User selectByPrimaryKey(Long id);

    List<User> selectAll();

    int updateByPrimaryKey(User record);

}

Comparing the UserMapper.java and UserMapper.xml files, it is not difficult to find the method in UserMapper.java and the CRUD statement element of UserMapper.xml(  ,,,) There is a one-to-one correspondence.

In MyBatis, it is through the fully qualified name of the method that the two are bound together.

1.4.3 data entity.java

[example] User.java file

public class User {
    private Long id;

    private String name;

    private Integer age;

    private String address;

    private String email;

}

,,,   The parameterType property of the and    of   type   Properties may be bound to data entities, so that the input and output of JDBC operations can be combined with JavaBean s, which is more convenient and easy to understand.

1.5. Test procedure

[example] MyBatisDemo.java file

public class MyBatisDemo {

    public static void main(String[] args) throws Exception {
        // 1. Load MyBatis configuration file and create SqlSessionFactory
        // Note: in practical applications, SqlSessionFactory should be a singleton
        InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);

        // 2. Create a SqlSession instance for database operation
        SqlSession sqlSession = factory.openSession();

        // 3. Mapper maps and executes
        Long params = 1L;
        List<User> list = sqlSession.selectList("io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey", params);
        for (User user : list) {
            System.out.println("user name: " + user.getName());
        }
        // Output: user name: Zhang San
    }

}

**Description: * * SqlSession   Interface is the core of MyBatis API, which represents   MyBatis has a full session with the database.

  • MyBatis parses the configuration and creates a configuration based on it   SqlSession  .

  • MyBatis then maps Mapper to   SqlSession, then pass the parameters, execute the SQL statement and get the results.

2, MyBatis lifecycle

2.1. SqlSessionFactoryBuilder

2.1.1 responsibilities of sqlsessionfactorybuilder

SqlSessionFactoryBuilder   Responsible for creating   SqlSessionFactory   example.

SqlSessionFactory builder can build an instance of SqlSessionFactory from an XML Configuration file or an instance of a pre customized Configuration.

The Configuration class contains everything you may care about for a SqlSessionFactory instance.

SqlSessionFactoryBuilder   The builder design pattern is applied. It has five build methods that allow you to create SqlSessionFactory instances through different resources.

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)

2.1.2 life cycle of sqlsessionfactorybuilder

SqlSessionFactoryBuilder   It can be instantiated, used, and discarded. Once SqlSessionFactory is created, it is no longer needed. Therefore   SqlSessionFactoryBuilder   The best scope of an instance is the method scope (that is, the local method variable).

You can reuse SqlSessionFactory builder to create multiple SqlSessionFactory instances, but it's best not to keep it all the time to ensure that all XML parsing resources can be released to more important things.

2.2. SqlSessionFactory

2.2.1 SqlSessionFactory responsibilities

SqlSessionFactory is responsible for creating SqlSession instances.

SqlSessionFactory applies the factory design pattern, which provides a set of methods for creating SqlSession instances.

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

Method description:

The default openSession() method has no parameters. It creates a SqlSession with the following characteristics:

1) The transaction scope will be turned on (that is, it will not be committed automatically).

  • The Connection object will be obtained from the DataSource instance configured by the current environment.

  • The transaction isolation level will use the default settings of the driver or data source.

  • Preprocessing statements are not reused and updates are not processed in batches.

2) The transaction isolation level represents the transaction isolation level, which corresponds to the five transaction isolation levels of JDBC.

3) The ExecutorType enumeration type defines three values:

  • ExecutorType.SIMPLE: this type of actuator has no special behavior. It creates a new preprocessing statement for the execution of each statement.

  • ExecutorType.REUSE: executors of this type reuse preprocessing statements.

  • ExecutorType.BATCH: this type of executor will execute all update statements in batch. If SELECT is executed among multiple updates, multiple update statements will be separated if necessary to facilitate understanding.

2.2.2 SqlSessionFactory lifecycle

SQLSessionFactory should always exist as a singleton during the running of the application.

2.3. SqlSession

2.3.1 SqlSession responsibilities

The main Java interface of MyBatis is SqlSession. It contains all the methods of executing statements, obtaining mappers and managing transactions. For details, please refer to:   SqlSessions in the official MyBatis document  」 .

The methods of SQLSession class can be roughly classified according to the following figure:

2.3.2 SqlSession lifecycle

SqlSessions are created by an instance of SqlSessionFactory; SqlSessionFactory   Created by SqlSessionFactoryBuilder.

🔔   Note: when MyBatis is used with some dependency injection frameworks (such as Spring or Guice), SqlSessions will be created by the dependency injection framework, so you don't need to use SqlSessionFactoryBuilder or SqlSessionFactory.

Each thread should have its own SqlSession instance.

The instance of SqlSession is not thread safe, so it cannot be shared, so its best scope is the request or method scope. Never put the reference of SqlSession instance in the static domain of a class, or even the instance variable of a class. Never put the reference of SqlSession instance in any type of managed scope, such as Servlet HttpSession in the framework. The correct scenario for using SqlSession in the Web is: each time you receive an HTTP request, you can open a SqlSession, return a response, and close it.

Programming mode:

try (SqlSession session = sqlSessionFactory.openSession()) {  // Your application logic code}

2.4. Mapper

2.4.1 mapper responsibilities

Mappers are user created interfaces that bind SQL statements.

The insert, update, delete and select methods in SqlSession are powerful, but they are also cumbersome. A more general approach is to use mapper classes to execute mapping statements. A mapper class is an interface class that only needs to declare methods that match the SqlSession method.

MyBatis abstracts each node in the configuration file into a Mapper interface, and the methods and    In node  < select|update|delete|insert>   Corresponding to nodes, i.e  < select|update|delete|insert>   The id value of the 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.

MyBatis will generate a Mapper instance through the dynamic proxy mechanism according to the method information declared by the corresponding interface; MyBatis will be determined according to the method name and parameter type of this method   Statement id, and then map with SqlSession. The bottom layer still completes the interaction with the database through SqlSession.

The following example shows some method signatures and how they map to SqlSession.

be careful:

  • The mapper interface does not need to implement any interface or inherit from any class. As long as the method can be uniquely identified, the corresponding mapping statement is OK.

  • Mapper interfaces can inherit from other interfaces. When using XML to build the mapper interface, ensure that the statements are included in the appropriate namespace. Moreover, the only limitation is that you cannot have the same method signature in two inherited interfaces (potentially dangerous practice is not advisable).

2.4.2 mapper life cycle

The instance of the mapper interface is obtained from SqlSession. Therefore, technically, the maximum scope of any mapper instance is the same as the SqlSession requesting them. Nevertheless, the best scope for a mapper instance is the method scope. That is, mapper instances should be requested in the methods that call them and discarded after use.

Programming mode:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // Your application logic code
}

Mapper annotation

MyBatis is an XML driven framework. The configuration information is based on XML, and the mapping statement is also defined in XML. After MyBatis 3, annotation configuration is supported. Annotation configuration is based on configuration API; The configuration API is based on XML configuration.

MyBatis supports annotations such as @ Insert, @ Update, @ Delete, @ Select, @ Result, etc.

For details, please refer to: sqlSessions in the official MyBatis document , which lists the annotation list supported by MyBatis and the basic usage.

III   MyBatis architecture

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

  • SqlSession - 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 - MyBatis executor, the core of MyBatis scheduling, is responsible for generating SQL statements and maintaining query cache.

  • StatementHandler - 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 - is responsible for converting the parameters passed by the user into the parameters required by JDBC Statement.

  • ResultSetHandler - responsible for converting the ResultSet result set object returned by JDBC into a List type collection.

  • TypeHandler - is responsible for the mapping and conversion between java data types and jdbc data types.

  • MappedStatement -   MappedStatement   Maintained a  < select|update|delete|insert>   Encapsulation of nodes.

  • SqlSource - is responsible for dynamically generating SQL statements according to the parameterObject passed by the user, encapsulating the information into the BoundSql object and returning it.

  • BoundSql - represents dynamically generated SQL statements and corresponding parameter information.

  • Configuration - all configuration information of mybatis is maintained in the configuration object.

The architecture levels of these components are as follows:

3.1. Configuration layer

The configuration layer determines how MyBatis works.

MyBatis provides two configurations:

  • XML configuration file based approach

  • Java API based approach

SqlSessionFactoryBuilder will create SqlSessionFactory according to the configuration;

SqlSessionFactory is responsible for creating SqlSessions.

3.2. Interface layer

The interface layer is responsible for the way of interaction with the database. MyBatis interacts with the database in two ways:

1) Use SqlSession: SqlSession encapsulates all execution statements, get mappers and methods of managing transactions.

  • The user only needs to pass in the Statement Id and query parameters to the SqlSession object to interact with the database conveniently.

  • The disadvantage of this approach is that it does not conform to the paradigm of object-oriented programming.

2) Using Mapper interface: MyBatis will generate a Mapper instance through the dynamic proxy mechanism according to the method information declared by the corresponding interface; MyBatis will determine the Statement Id according to the method name and parameter type of this method, and then map it to SqlSession. The bottom layer still completes the interaction with the database through SqlSession.

3.3. Data processing layer

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

1) Build a dynamic SQL Statement according to the passed parameter Statement and parameters

  • 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.

  • Parameter mapping refers to the conversion between java data types and jdbc data types: there are two processes: in the query phase, we need to convert java type data into jdbc type data   preparedStatement.setXXX()   To set the value; The other is to convert the jdbc type data of the resultset query result set into java data type.

2) Execute SQL statements and process the response result set ResultSet

  • After the dynamic SQL statement is generated, MyBatis will execute the SQL statement and convert the possible returned result set into   List   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.

3.4. Frame support layer

  1. Transaction management mechanism - MyBatis abstracts transactions into transaction interfaces. There are two forms of transaction management in MyBatis:
  • Use the transaction management mechanism of JDBC: that is, use the java.sql.Connection object to commit, rollback, close and so on.

  • Use the MANAGED transaction management mechanism: MyBatis will not implement transaction management itself, but let the program container (JBOSS, Weblogic) implement transaction management.

  1. Connection pool management

  2. SQL statement configuration - supports two methods:

  • xml configuration

  • Annotation configuration

  1. Cache mechanism - MyBatis adopts two-level cache structure;
  • L1 Cache is Session level Cache - L1 Cache is also called local Cache. Generally speaking, a SqlSession object will use an Executor object to complete Session operations, and the Executor object will maintain a Cache to improve query performance.
  1. The lifecycle of the L1 cache is Session level.
  • The L2 cache is the Application level cache - the L2 cache is enabled only when the user configures "cacheEnabled=true".
  1. If L2 caching is enabled, SqlSession will first use the cacheingexecution object to process the query request. The cacheingexecution will check whether there is matching data in the L2 cache. If there is matching, it will directly return the cache result; If it is not in the cache, it is handed over to the real Executor object to complete the query. Then the cacheingexecution will put the query results returned by the real Executor into the cache and return them to the user.

  2. The lifecycle of L2 cache is application level.

4, Internal working mechanism of SqlSession

From the above, we have learned that MyBatis encapsulates the access to the database and puts the session and transaction control of the database into the SqlSession object. So how does it work? Next, we analyze it through source code interpretation.

The internal processing mechanism of SqlSession for insert, update, delete and select is basically the same. Therefore, next, I will take a complete select query process as an example to explain the internal working mechanism of SqlSession. I believe that if readers understand the processing flow of select, they can also do everything for other CRUD operations.

four point one   SqlSession subcomponent

The previous content has been introduced: SqlSession is the top-level interface of MyBatis. It provides all methods such as executing statements, obtaining mappers and managing transactions.

In fact, SqlSession implements task distribution by aggregating multiple sub components and making each sub component responsible for its own functions.

Before understanding the working mechanism of each sub component, let's briefly understand the core sub components of SqlSession.

4.1.1 Executor

The Executor is the Executor, which is responsible for generating dynamic SQL and managing the cache.

  • Executor   Actuator interface.

  • BaseExecutor

    It is an abstract class of Executor. It adopts the template method design pattern, has built-in some common methods, and leaves the customized methods to subclasses for implementation.

  • SimpleExecutor

    It is the simplest actuator. It only executes SQL directly and does nothing extra.

  • BatchExecutor

    Is a batch executor. Its purpose is to optimize performance through batch processing. It is worth noting that for batch update operations, due to the internal cache mechanism, you need to call flushStatements to clear the cache after use.

  • ReuseExecutor

    Is a reusable actuator. The reused object is the Statement, that is, the executor will cache the Statement of the same SQL to avoid creating the Statement repeatedly. Its internal implementation is to maintain the Statement object through a HashMap. Since the current Map is only valid in this session, it needs to be called after use   flushStatements   To clear the Map.

  • CachingExecutor   Is the cache executor. It is only used when L2 caching is enabled.

4.1.2 StatementHandler

StatementHandler   Object is responsible for setting   Statement   Object, process the resultSet returned by JDBC, and process the resultSet into a List set.

StatementHandler   Family members:

  • StatementHandler   Is the interface;

  • BaseStatementHandler is an abstract class that implements StatementHandler, with some common methods built in;

  • Simplestationhandler is responsible for processing the Statement;

  • PreparedStatementHandler is responsible for processing PreparedStatement;

  • CallableStatementHandler is responsible for processing callablestatements.

  • RoutingStatementHandler is responsible for representing the specific subclass of StatementHandler. According to the Statement type, select to instantiate simplestationhandler, PreparedStatementHandler and CallableStatementHandler.

4.1.3 ParameterHandler

ParameterHandler is responsible for converting the incoming Java object into a JDBC type object and populating the dynamic SQL of PreparedStatement with values.

ParameterHandler has only one concrete implementation class, DefaultParameterHandler.

4.1.4 ResultSetHandler

ResultSetHandler is responsible for two things:

  • Process the result set generated after Statement execution and generate the result list

  • Process the output parameters after the execution of the stored procedure

ResultSetHandler has only one concrete implementation class, DefaultResultSetHandler.

4.1.5 TypeHandler

TypeHandler is responsible for converting Java object types and JDBC types to each other.

four point two   SqlSession and Mapper

Let's recall the code in the test program section of the complete example Chapter of MyBatis.

Code snippet in MyBatisDemo.java file:

// 2. Create a SqlSession instance for database operation
SqlSession sqlSession = factory.openSession();

// 3. Mapper maps and executes
Long params = 1L;
List<User> list = sqlSession.selectList("io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey", params);
for (User user : list) {
    System.out.println("user name: " + user.getName());
}

In the sample code, pass the Statement ID and parameters of a configured SQL statement to the sqlSession object, and then return the result io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey   Is the Statement ID configured in UserMapper.xml, and params is the SQL parameter.

Code snippet in UserMapper.xml file:

 <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select id, name, age, address, email
    from user
    where id = #{id,jdbcType=BIGINT}
  </select>

MyBatis maps SqlSession and Mapper to each other through the fully qualified name of the method.

4.3. SqlSession and Executor

Source code of selectList method in org.apache.ibatis.session.defaults.DefaultSqlSession:

@Override
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}

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

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 1. Find MappedStatement corresponding to Configuration file in Configuration object Configuration according to Statement Id
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 2. Submit the SQL statement to the Executor for processing
    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();
  }
}

explain:

All Configuration information of MyBatis is maintained in the Configuration object. A Map < string, MappedStatement > object is maintained in. Where key is the fully qualified name of Mapper method (for this example, key is   io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey  ), value is a MappedStatement object. Therefore, you can find the corresponding MappedStatement from the Map by passing in the Statement Id.

MappedStatement maintains the metadata information of a Mapper method. For data organization, please refer to the following   debug screenshot:

* * summary: * through "SqlSession and Mapper" and "SqlSession and Executor" these two sections, we already know that the function of SqlSession is to get corresponding MappedStatement objects in Configuration according to Statement ID, and then invoke Executor to perform specific operations.

4.4. Executor workflow

Continuing the process in the previous section, SqlSession passes the SQL statement to the Executor for processing. What have you done?

(1) Actuator query entry

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 passed parameters, which is represented by BoundSql object
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2. Create a cache Key according to the parameters passed
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

The executor query entry mainly does two things:

  • Generate dynamic SQL: dynamically generate the SQL statements to be executed according to the passed parameters, represented by BoundSql object.

  • Manage cache: create a cache Key according to the parameters passed.

(2) The actuator queries the second entry

@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // slightly
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 3. If there is a value in the cache, the data is directly fetched from the cache; Otherwise, query the database
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    // slightly
    return list;
  }

The main function of the actual query method is to judge whether the cache key can hit the cache:

  • If hit, the data in the cache will be returned;

  • If not, query the database:

(3) Query database

  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, get the List results, and update the query results to the local 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;
  }

The responsibility of the queryFromDatabase method is to call doQuery, initiate a query to the database, and update the returned results to the local cache.

(4) Actual query method. Implementation of doQuery() method of simpleexecution class;

   @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();
      // 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
      return handler.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:

  • According to the passed parameters, complete the dynamic parsing of SQL statements and generate BoundSql objects for StatementHandler;

  • Create a cache for queries to improve performance

  • Create a JDBC Statement connection object, pass it to the StatementHandler object, and return the List query result.

Implementation of prepareStatement() method:

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    //Set the parameters for the created Statement object, that is, set the parameter in the SQL Statement? Set to the specified parameter
    handler.parameterize(stmt);
    return stmt;
  }

For objects of type PreparedStatement of JDBC, we use SQL statements in the creation process. The string will contain several placeholders, and then we set the value of the placeholder.

4.5. StatementHandler workflow

StatementHandler has a subclass RoutingStatementHandler, which is responsible for representing other StatementHandler subclasses.

It will select and instantiate the corresponding StatementHandler according to the configured Statement type, and then its proxy object will complete the work.

[source code] RoutingStatementHandler

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }

}

[source code] RoutingStatementHandler   of   parameterize method source code

[source code] PreparedStatementHandler   of   parameterize method source code

StatementHandler uses the ParameterHandler object to complete the assignment of a Statement.

@Override
public void parameterize(Statement statement) throws SQLException {
  // Use the ParameterHandler object to set the value of the Statement
  parameterHandler.setParameters((PreparedStatement) statement);
}

[source code] query method source code of StatementHandler

StatementHandler uses the ResultSetHandler object to complete the processing of the ResultSet.

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  // Use ResultHandler to process ResultSet
  return resultSetHandler.handleResultSets(ps);
}

4.6. ParameterHandler workflow

[source code] of DefaultParameterHandler   setParameters method

@Override
  public void setParameters(PreparedStatement ps) {
	// parameterMappings is the encapsulation of placeholder #{} corresponding parameters
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // Parameters in stored procedures are not processed
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            // Get the corresponding actual value
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            // Get the corresponding property in the object or find the value in the Map object
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }

          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // Convert Java object parameters to JDBC type parameters through TypeHandler
            // Then, the values are dynamically bound into PreparedStaement
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

4.7. ResultSetHandler workflow

The implementation of ResultSetHandler can be summarized as follows: convert the result set after Statement execution into the corresponding JavaBean object according to the ResultType or ResultMap configured in Mapper file, and finally return the result.

[source code] the handleResultSets method of DefaultResultSetHandler. The handleResultSets method is the most critical method of DefaultResultSetHandler. The implementation is as follows:

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

  final List<Object> multipleResults = new ArrayList<>();

  int resultSetCount = 0;
  // First result set
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // Determine the number of result sets
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // Traversal processing result set
  while (rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = resultMaps.get(resultSetCount);
    handleResultSet(rsw, resultMap, multipleResults, null);
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  String[] resultSets = mappedStatement.getResultSets();
  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);
}

5, References

official

  1. MyBatis Github

  2. MyBatis official website

  3. MyBatis Generator

  4. Spring integration

  5. Spring Boot integration

Extension

  1. MyBatis-plus  - CRUD extension, code generator, pager and other functions

  2. Mapper  - CRUD extension

  3. MyBatis-PageHelper  - MyBatis universal paging plug-in

article

  1. <Deep understanding of MyBatis principle>

  2. <MyBatis source code Chinese Notes>

  3. <Powerful resultMap in MyBatis>

Author: vivo Internet server team - Zhang Peng

Tags: Java JDBC Mybatis SQL orm

Posted on Wed, 24 Nov 2021 11:12:47 -0500 by jinwu