MyBatis complete example

Here, I’ll show you how MyBatis works with an entry-level example.

Note: The principles and source code sections in later sections of this article will also be based on this example. Complete example source code address

1.1. Database preparation

In this example, you need to perform CRUD operations on a user table. Its 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 'username ', ageINT(3)NOT NULL DEFAULT 0 COMMENT 'age ', addressVARCHAR(32)NOT NULL DEFAULT '' COMMENT 'address ', emailVARCHAR(32)NOT NULL DEFAULT '' COMMENT ', PRIMARYKEY (id)
) COMMENT = 'User table';

INSERT INTO user (name, age, address, email)
VALUES ('Joe'.18.'Beijing'.'[email protected]');
INSERT INTO user (name, age, address, email)
VALUES ('bill'.19.'Shanghai'.'[email protected]');
Copy the code

1.2. Add MyBatis

If you use Maven to build your project, place the following dependency code in the POM.xml file:

<dependency>
  <groupId>org.Mybatis</groupId>
  <artifactId>Mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
Copy the code

1.3. MyBatis configuration

The XML configuration file contains the core Settings for the MyBatis system, including the DataSource to get the database connection instance and the TransactionManager to determine the transaction scope and control mode.

This example shows only the simplest configuration. [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>
Copy the code

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

1.4 Mapper

1.4.1 Mapper XML

Mapper. XML is a JDBC SQL template for MyBatis. [Example] usermapper. XML file.

Below 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>
Copy the code

1.4.2 Mapper. Java

The 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(a);

    int updateByPrimaryKey(User record);

}
Copy the code

By comparing usermapper. Java and usermapper. XML files, it is not difficult to find that there is a one-to-one correspondence between the methods in Usermapper. Java and the CRUD statement elements (,, and) of Usermapper. XML. In MyBatis, it is the fully qualified name of the method that binds the two together. 1.4.3 Data Entities Java [Example] user. Java file Public class User {private Long ID; private String name; private Integer age; private String address; private String email; },,, and the parameterType attribute, as well as the Type attribute of the parameterType attribute, may be bound to data entities. In this way, you can combine the input and output of JDBC operations with Javabeans to make it 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 a real application, SqlSessionFactory should be a singleton
        InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);

        // 2. Create an SqlSession instance to perform database operations
        SqlSession sqlSession = factory.openSession();

        // 3. Mapper maps and executes the command
        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}}Copy the code

**SqlSession interface is the core of MyBatis API core, it represents MyBatis and database a complete session.

  • MyBatis parses the configuration and creates an SqlSession based on the configuration.

  • MyBatis then maps Mapper to SqlSession, passes parameters, executes SQL statements and gets results.

Second, MyBatis life cycle

2.1. The SqlSessionFactoryBuilder is

2.1.1 SqlSessionFactoryBuilder responsibilities

The SqlSessionFactoryBuilder is responsible for creating an instance of SqlSessionFactory.

SqlSessionFactoryBuilder 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 might care about for an instance of SqlSessionFactory.

SqlSessionFactoryBuilder applies the Builder design pattern. It has five build methods that allow you to create SqlSessionFactory instances from 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)
Copy the code

2.1.2 SqlSessionFactoryBuilder lifecycle

SqlSessionFactoryBuilder can be instantiated, used, and discarded; once SqlSessionFactory is created, it is no longer needed. So the best scope for SqlSessionFactoryBuilder instances is the method scope (that is, local method variables).

You can reuse SqlSessionFactoryBuilder to create multiple instances of SqlSessionFactory, but it’s best not to keep it around all the time to ensure that all XML parsing resources can be freed up for more important things.

2.2. SqlSessionFactory

2.2.1 SqlSessionFactory duties

SqlSessionFactory is responsible for creating the SqlSession instance.

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

SqlSession openSession(a)
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(a);
Copy the code

Method description:

The default openSession() method takes no arguments and creates an SqlSession with the following features:

1) Transaction scope will be turned on (i.e., not commit automatically).

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

  • The transaction isolation level will use the default Settings for the driver or data source.

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

2) TransactionIsolationLevel said transaction isolation level, five of which corresponds to the JDBC transaction isolation level.

The ExecutorType enumeration type defines three values:

  • Executortype. SIMPLE: This type of actuator has no specific behavior. It creates a new preprocessed statement for each statement execution.

  • Executortype. REUSE: This type of executor reuses pre-processed statements.

  • Executortype. BATCH: This type of executor executes all update statements in batches. If a SELECT is executed between multiple updates, it separates the updates for easy comprehension if necessary.

2.2.2 SqlSessionFactory life cycle

SQLSessionFactory should exist as a singleton for the duration of the application.

2.3. SqlSession

2.3.1 SqlSession duties

The primary Java interface for MyBatis is SqlSession. It contains all the methods for executing statements, getting mappers, and managing transactions. For details, please refer to “MyBatis official document SqlSessions”.

The methods of the SQLSession class can be classified as follows:

2.3.2 SqlSession life cycle

SqlSessions are created by the SqlSessionFactory instance; And SqlSessionFactory is created by SqlSessionFactoryBuilder.

🔔 note: When MyBatis is used with some dependency injection framework (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 INSTANCE of SqlSession.

An instance of SqlSession is not thread-safe and therefore cannot be shared, so its best scope is the request or method scope. A reference to an SqlSession instance must never be placed in a static domain of a class, or even an instance variable of a class. A reference to an SqlSession instance should also never be placed in any type of managed scope, such as HttpSession in the Servlet framework. The correct scenario for using SqlSession on the Web is to open an SqlSession each time an HTTP request is received, return a response, and close it.

Programming mode:

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

2.4. The mapping

2.4.1 Responsibilities of mapper

Mappers are user-created interfaces bound to SQL statements.

The INSERT, UPDATE, DELETE, and SELECT methods in SqlSession are powerful, but also cumbersome. A more general approach is to use the mapper class to execute the mapping statement. A mapper class is an interface class that simply declares methods that match the SqlSession methods.

MyBatis will each node in the configuration file is an abstract for a Mapper interfaces, and methods declared in the interface and with nodes of < select | update | delete | insert > node corresponding, Namely < select | update | delete | insert > node id value for the method’s name in the Mapper interfaces, parameterType value indicates the Mapper corresponding method into the type, The resultMap value corresponds to the return value type represented by the Mapper interface or the element type of the return 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 determine the Statement ID based on the method name and parameter type of the method, and then map the Statement ID to SqlSession.

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

Note:

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

  • Mapper interfaces can inherit from other interfaces. When building mapper interfaces using XML, ensure that statements are contained in the appropriate namespace. Also, the only limitation is that you can’t have the same method signature in two inherited interfaces (a potentially dangerous practice not to do).

2.4.2 Mapper life cycle

The instance of the mapper interface is obtained from SqlSession. So technically, the maximum scope of any mapper instance is the same as the SqlSession that requested them. However, the best scope for a mapper instance is the method scope. That is, mapper instances should be requested in the method that calls them and then discarded after use.

Programming mode:

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

Mapper annotation

MyBatis is an XML-driven framework. The configuration information is XML-based, and the mapping statements are defined in XML. After MyBatis 3, annotation configuration is supported. Annotation configuration is based on the 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, MyBatis official documentation, which lists the list of annotations supported by MyBatis and basic usage.

Iii. Architecture of MyBatis

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

  • SqlSession – as MyBatis work of the main top-level API, and database interaction session, complete the necessary database add, delete, change and check function.

  • Executor – MyBatis Executor, the core of MyBatis scheduling, is responsible for the generation of SQL statements and the maintenance of query cache.

  • StatementHandler – Encapsulates the JDBC Statement operation and performs operations on the JDBC Statement, such as setting parameters and converting the Statement result set to a List.

  • ParameterHandler – Is responsible for converting user-passed parameters into those required for a JDBC Statement.

  • ResultSetHandler – Is responsible for converting a ResultSet object returned by JDBC into a collection of type List.

  • TypeHandler – Is responsible for mapping and converting between Java and JDBC data types.

  • Article MappedStatement – MappedStatement maintains a < select | update | delete | insert > node encapsulation.

  • SqlSource – Is responsible for dynamically generating SQL statements based on the parameterObject passed by the user, encapsulating the information into BoundSql objects, and returning it.

  • BoundSql – Indicates dynamically generated SQL statements and parameter information.

  • Configuration-mybatis All Configuration information is kept in the Configuration object.

The architectural hierarchy of these components is as follows:

3.1. The configuration layer

The configuration layer determines how MyBatis works.

MyBatis provides two configurations:

  • XML configuration file based approach

  • Java API-based approach

The SqlSessionFactoryBuilder creates the SqlSessionFactory based on the configuration;

SqlSessionFactory is responsible for creating SqlSessions.

3.2. The interface layer

The interface layer is responsible for how you interact with the database. MyBatis interacts with the database in two ways:

1) Use SqlSession: SqlSession encapsulates all execution statements, obtains mapper, and manages transaction methods.

  • Users can easily interact with the database by passing the Statement Id and query parameters into the SqlSession object.

  • The downside of this approach is that it doesn’t fit the object-oriented programming paradigm.

2) Using Mapper interface: MyBatis will generate a Mapper instance through dynamic proxy mechanism according to the method information declared by the corresponding interface; MyBatis will determine the Statement Id based on the method name and parameter type of the method, and then map the Statement Id to SqlSession.

3.3. Data processing layer

The data processing layer can be said to be the core of MyBatis. From a large perspective, it needs to complete two functions:

1) Create a dynamic SQL Statement based on the 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 by passing in parameter values, which makes MyBatis have strong flexibility and expansibility.

  • Parameter mapping refers to the conversion between Java data types and JDBC data types: there are two processes: the query phase, where we convert Java data to JDBC data and set the value in preparedStatement.setxxx (); The other is to convert the jdbcType data in the resultset query resultset to Java data types.

2) execute SQL statements and process response ResultSet ResultSet

  • After the dynamic SQL statement is generated, MyBatis will execute the SQL statement and convert the possible result set to a List List.

  • MyBatis supports one-to-many and many-to-one transformation of result set relation in the processing of result set, and has two support methods, one is the query of nested query statement, and the other is the query of nested result set.

3.4. Frame support layer

  1. Transaction management mechanism – MyBatis abstracts transactions into Transaction interfaces. MyBatis transaction management is divided into two forms:
  • The JDBC transaction management mechanism is used to commit, rollback, and close transactions using java.sql.Connection objects.

  • MANAGED transaction management: MyBatis will not implement transaction management itself, but let application containers such as JBOSS, Weblogic to implement transaction management.

  1. Connection Pool Management

  2. SQL statement configuration – Supports two methods:

  • The XML configuration

  • Annotation configuration

  1. Cache mechanism – MyBatis adopts two-level cache structure;
  • Level 1 cache is the Session level cache – level 1 cache is also known as local cache. Typically, a SqlSession object uses an Executor object to perform session operations, which maintains a Cache to improve query performance.
  1. The lifetime of level 1 cache is Session Session level.
  • Level 2 cache is an application-level cache. – Level 2 cache is enabled only when “cacheEnabled=true” is configured.
  1. If level 2 caching is enabled, the SqlSession first uses the CachingExecutor object to process the query request. CachingExecutor checks the level 2 cache to see if there is a match and returns the cache result if there is a match. If not, the real Executor object is used to complete the query. CachingExecutor then places the query result returned by the real Executor into the cache and returns it to the user.

  2. The life cycle of a level 2 cache is application-level.

4. SqlSession internal working mechanism

MyBatis encapsulates access to the database, putting 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, select is basically the same. So, next, I will use a complete SELECT query process as an example to explain the internal workings of SqlSession. If you understand the process of SELECT, you will be able to do the same for other CRUD operations.

4.1 SqlSession sub-components

SqlSession is the top-level interface of MyBatis. It provides all the methods to execute statements, get mapper and manage transactions.

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

Before we look at how each sub-component works, let’s take a quick look at the core sub-components of SqlSession.

4.1.1 Executor

Executor is an Executor that generates dynamic SQL and manages caches.

  • Executor is the Executor interface.

  • BaseExecutor

    Executor is an abstract class that follows the template method design pattern, with generic methods built in, leaving custom methods to be implemented by subclasses.

  • SimpleExecutor

    It’s the simplest actuator. It just executes the 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 the batch update operation needs to be called flushStatements to flush the cache because of the internal caching mechanism.

  • ReuseExecutor

    Is a reusable actuator. The object to be reused is a Statement. That is, the executor caches statements of the same SQL to avoid creating them repeatedly. The internal implementation maintains the Statement object through a HashMap. Because the current Map is valid only in this session, you need to call flushStatements to clear the Map after using it.

  • CachingExecutor is a cache executor. It is only used when level 2 caching is enabled.

4.1.2 StatementHandler

The StatementHandler object sets query parameters in the Statement object, processes the resultSet returned by JDBC, and returns the resultSet as a List.

Members of the StatementHandler Family:

  • StatementHandler is the interface;

  • BaseStatementHandler is an abstract class that implements StatementHandler with some built-in common methods.

  • SimpleStatementHandler handles statements;

  • PreparedStatementHandler is responsible for processing preparedStatements;

  • The CallableStatementHandler is responsible for handling callableStatements.

  • RoutingStatementHandler is responsible for delegating StatementHandler subclasses, based on the Statement type, Select to instantiate SimpleStatementHandler, PreparedStatementHandler, or CallableStatementHandler.

4.1.3 ParameterHandler

The ParameterHandler is responsible for converting incoming Java objects into JDBC-type objects and populating the values for the dynamic SQL in PreparedStatement.

ParameterHandler has only one concrete implementation class, DefaultParameterHandler.

4.1.4 ResultSetHandler

ResultSetHandler is responsible for two things:

  • Process the result set generated after the Statement is executed and generate a result list

  • Handles the output parameters of the stored procedure after execution

ResultSetHandler has only one concrete implementation class, DefaultResultSetHandler.

4.1.5 TypeHandler

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

4.2 SqlSession and Mapper

Let’s start by recalling the code for the test program section of the complete Sample section of MyBatis.

Code snippets in the mybatisdemo.java file:

// 2. Create an SqlSession instance to perform database operations
SqlSession sqlSession = factory.openSession();

// 3. Mapper maps and executes the command
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());
}
Copy the code

In the sample code, the Statement Id and parameters of a configured Sql Statement are passed to the sqlSession object. And returns the result. IO lot. Dunwu. Spring.. Orm mapper. UserMapper. SelectByPrimaryKey is configured in UserMapper. XML Statement ID, Params is an SQL parameter.

Code snippet from the 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>
Copy the code

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

4.3. SqlSession and Executor

Org. Apache. Ibatis. Session. Defaults. DefaultSqlSession selectList method in the source code:

@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. Locate the MappedStatement corresponding to the Configuration file in the Configuration object based on the Statement Id
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 2. Submit the SQL statement to 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(); }}Copy the code

Description:

All Configuration information of MyBatis is maintained in the Configuration object. Maintains a Map<String, MappedStatement> object in. Among them, the key for Mapper method fully qualified name (for this example, the key is IO. Making the dunwu. Spring. The orm. Mapper. UserMapper. SelectByPrimaryKey), Value is an MappedStatement object. Therefore, passing in the Statement Id can find the corresponding MappedStatement from the Map.

MappedStatement maintains metadata information about a Mapper method. You can see the following debug screenshot for data organization:

SqlSession: Mapper and SqlSession: Executor According to the Statement ID, the corresponding MappedStatement object is obtained in the Configuration, and then Executor is invoked to perform the specific operation.

4.4. Executor Workflow

Continuing the flow from the previous section, the SqlSession hands the SQL statement to the Executor for processing. So what does that do?

(1) Actuator query entry

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	// 1. The BoundSql object is used to dynamically generate SQL statements to be executed based on the parameters
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2. Create a cache Key based on the passed parameters
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
Copy the code

The executor query entry does two things:

  • Dynamic SQL generation: The BoundSql object is used to dynamically generate SQL statements to be executed based on the input parameters.

  • Manage cache: Create a cache Key based on the passed parameters.

(2) The second entry of the executor query

@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    / / a little
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 3. If there is a value in the cache, fetch data directly 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--;
    }
    / / a little
    return list;
  }
Copy the code

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

  • If yes, the data in the cache is returned.

  • If no, query database:

(3) Query the 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. Run the query to obtain the List result and update the query result 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;
  }
Copy the code

The queryFromDatabase method is responsible for calling doQuery, issuing a query to the database, and updating the returned results to the local cache.

(4) The actual query method. SimpleExecutor class doQuery() method implementation;

   @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 a StatementHandler object based on the existing parameters to perform query operations
      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 statementhandler.query () to return the List result
      return handler.query(stmt, resultHandler);
    } finally{ closeStatement(stmt); }}Copy the code

The executor.query () method creates a StatementHandler object, passes the necessary parameters to StatementHandler, and uses StatementHandler to query the database. Finally, the List result set is returned. As you can see from the code above, Executor does:

  • According to the parameters passed, the DYNAMIC parsing of SQL statements is completed, and BoundSql object is generated for StatementHandler to use.

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

Implement the prepareStatement() method:

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    // Set parameters for the Statement object. Set to the specified parameter
    handler.parameterize(stmt);
    return stmt;
  }
Copy the code

For JDBC PreparedStatement type objects, the SQL statement string is created using placeholders that are set later.

4.5. StatementHandler workflow

StatementHandler has a subclass, RoutingStatementHandler, which is responsible for delegating work to other StatementHandler subclasses.

It chooses to instantiate the corresponding StatementHandler based on the configured Statement type, and its proxy object does the work.

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()); }}Copy the code

Parameterize method for RoutingStatementHandler

PreparedStatementHandler parameterize method PreparedStatementHandler

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

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

StatementHandler query method source

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

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

4.6. ParameterHandler workflow

DefaultParameterHandler setParameters method

@Override
  public void setParameters(PreparedStatement ps) {
	// parameterMappings encapsulates the parameters corresponding to the #{} placeholder
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if(parameterMappings ! =null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // Do not handle arguments in stored procedures
        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 attribute 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 via TypeHandler
            // The values are then dynamically bound to 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);
          }
        }
      }
    }
  }
Copy the code

4.7. ResultSetHandler workflow

The implementation of ResultSetHandler can be summarized as follows: Converts the result set after the Statement is executed into the corresponding JavaBean object according to the ResultType or ResultMap configured in the Mapper file, and returns the result.

DefaultResultSetHandler handleResultSets method The handleResultSets method is the most critical method for DefaultResultSetHandler. Its 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;
  // The 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);
  // Iterate over the 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);
}
Copy the code

5. Reference materials

The official

  1. MyBatis Github

  2. MyBatis website

  3. MyBatis Generator

  4. Spring integration

  5. Spring integration of the Boot

extensions

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

  2. Mapper-crud extension

  3. MyBatis-PageHelper – MyBatis general pagination plugin

The article

  1. In-depth Understanding of MyBatis Principle

  2. MyBatis source Code Chinese annotation

  3. Powerful resultMap in MyBatis

Author: Zhang Peng, Vivo Internet Server Team