MyBatis11-"General Source Code Guide: Detailed Explanation of MyBatis Source Code" Notes-executor package (Part 2)

This series of articles is my notes and summary from the book "General Source Code Guide: Detailed Explanation of MyBatis Source Code
" . This book is based on MyBatis-3.5.2 version. The author of the book is Brother Yi . The link is Brother Yi's Weibo in CSDN. But among all the articles I read, there was only one that briefly introduced this book. It doesn’t reveal too much about the charm of the book. Next, I will record my learning summary. If the author thinks that I have infringed the copyright, please contact me to delete it. Thanks again to Brother Yi for providing learning materials. This explanation will accompany the entire series of articles. Respect the originality . I have purchased the revised book on WeChat Reading.
Copyright statement: This article is an original article by CSDN blogger "Architect Yi Ge" and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/onlinedct/article/details/107306041

1. Statement processing function

1.1.MyBatis’ support for multiple statement types

In the writing of MyBatis mapping files, we often see "${}" and "#{}", two symbols that define variables, and their meanings are as follows.

  • ${}: Variables using this symbol will be directly inserted into the SQL fragment in the form of strings.
  • #{}: Variables using this symbol will be assigned to the SQL fragment in a precompiled form.

MyBatis supports three statement types, and different statement types support different variable symbols. The three statement types in MyBatis are as follows.

  • STATEMENT: In this statement type, only simple string concatenation of SQL fragments will be performed. Therefore, only using "${}" to define variables is supported.
  • PREPARED: In this statement type, the SQL fragments are first concatenated into strings, and then the SQL fragments are assigned values. Therefore, the two forms of "${}" and "#{}" are supported to define variables.
  • CALLABLE: This statement type is used to implement the call of the execution process. It will first perform string splicing on the SQL fragments, and then assign values ​​to the SQL fragments. Therefore, the two forms of "${}" and "#{}" are supported to define variables.
<!--直接字符串拼接,必须自己加引号,否则会拼接为下面的语句然后失败::SELECT * FROM `user` WHERE schoolName = Sunny School-->
    <select id="queryUserBySchoolName_A" resultType="com.github.yeecode.mybatisdemo.model.User" statementType="STATEMENT">
      SELECT * FROM `user` WHERE schoolName = "${schoolName}"
    </select>

    <!--会把变量转为?后进行填入,不能有引号-->
    <select id="queryUserBySchoolName_B" resultType="com.github.yeecode.mybatisdemo.model.User" statementType="PREPARED">
      SELECT * FROM `user` WHERE schoolName = #{schoolName}
    </select>

    <!--使用前需要先到数据库创建以下存储过程:-->
    <!--CREATE PROCEDURE `yeecode`(IN `ageMinLimit` int,IN `ageMaxLimit` int,OUT `count` int, OUT `maxAge` int)-->
    <!--BEGIN-->
    <!--SELECT COUNT(*),MAX(age) INTO count,maxAge FROM user WHERE age >= ageMinLimit AND age <= ageMaxLimit;-->
    <!--END $$-->
    <select id="runCall" statementType="CALLABLE">
      CALL yeecode(
      ${ageMinLimit},
      #{ageMaxLimit,mode=IN,jdbcType=NUMERIC},
      #{count,mode=OUT,jdbcType=NUMERIC},
      #{maxAge,mode=OUT,jdbcType=NUMERIC}
      );
    </select>

Because SQL statements in the form of STATEMENT and PREPARED are more commonly used, they will not be introduced separately. The following describes the use of the CALLABLE statement in detail.

When calling the CALLABLE statement, you can directly use Map to set input parameters. The operation of calling the stored procedure using MyBatis is as shown in the following code.

 Map<String, Integer> param = new HashMap<>();
 param.put("ageMinLimit",10);
 param.put("ageMaxLimit",30);
 session.selectOne("com.github.yeecode.mybatisdemo.dao.UserMapper.runCall",param);
 System.out.println("proceduce param :" + param);

After the stored procedure is called, MyBatis will directly write the output results back to the given Map parameters according to the output parameter settings. The key is the variable name and the value is the stored procedure result.

1.2.MyBatis statement processing function

The statement sub-package is responsible for providing statement processing functions, where StatementHandler is the parent interface of the statement processing function class. The class diagram of the StatementHandler interface and its subclasses is as follows:
Insert image description here
The RoutingStatementHandler class is a proxy class, which can select a specific proxy object based on the specific type of the incoming MappedStatement object, and then delegate all actual operations to the proxy object. . The function is as its name suggests. The RoutingStatementHandler class provides the routing function, and the routing selection is based on the statement type.

  // 根据语句类型选取出的被代理类的对象
  private final StatementHandler delegate;

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

BaseStatementHandler, as the parent class of the three implementation classes, provides public methods of the implementation classes. And the template pattern used by the BaseStatementHandler class defines the framework of the entire method in the prepare method, and then hands over some subclass-related operations to its three subclasses.
The SimpleStatementHandler class, PreparedStatementHandler class and CallableStatementHandler class are three real Statement processors, which handle Statement objects, PreparedStatement objects and CallableStatement objects respectively. The difference between the three Statement processors can be seen through the parameterize method.

  • The implementation of parameterize method in SimpleStatementHandler is empty because it only needs to complete string replacement and does not require parameter processing.
  • The parameterize method in PreparedStatementHandler finally calls the parameter assignment method in the java.sql.PreparedStatement class after multi-level transfer through the ParameterHandler interface.
  • parameterize method in CallableStatementHandler. It completes a total of two steps: one is to call the output parameter registration method in java.sql.CallableStatement through the registerOutputParameters method to complete the registration of the output parameters; the other is to call the java.sql.PreparedStatement class after multi-level transfer through the ParameterHandler interface. parameter assignment method.
  /**
   * 对语句进行参数处理
   * @param statement SQL语句
   * @throws SQLException
   */
  @Override
  public void parameterize(Statement statement) throws SQLException {
    
    
    // 输出参数的注册
    registerOutputParameters((CallableStatement) statement);
    // 输入参数的处理
    parameterHandler.setParameters((CallableStatement) statement);
  }

It can be seen that the SimpleStatementHandler class, PreparedStatementHandler class and CallableStatement-Handler class ultimately rely on the functions provided by the Statement interface and its sub-interfaces under the java.sql package to complete specific parameter processing operations.

1.3. Parameter processing function

Assigning values ​​to parameters in SQL statements is a very important step in MyBatis statement processing, and this step is completed by the parameter sub-package.
There is actually only one ParameterHandler interface in the parameter sub-package, which defines two methods:

  • The getParameterObject method is used to obtain the actual parameter object corresponding to the SQL statement.
  • The setParameters method is used to complete variable assignment in SQL statements.
    The ParameterHandler interface has a default implementation class DefaultParameterHandler, which is in the defaults subpackage of the scripting package.
public class DefaultParameterHandler implements ParameterHandler {
    
    
  // 类型处理器注册表
  private final TypeHandlerRegistry typeHandlerRegistry;
  // MappedStatement对象(包含完整的增删改查节点信息)
  private final MappedStatement mappedStatement;
  // 参数对象
  private final Object parameterObject;
  // BoundSql对象(包含SQL语句、参数、实参信息)
  private final BoundSql boundSql;
  // 配置信息
  private final Configuration configuration;

The statement types that support parameter setting in MyBatis are the PreparedStatement interface and its sub-interfaces (CallableStatement is a sub-interface of PreparedStatement), so the input parameters of setParameters are of PreparedStatement type.

 /**
   * 为语句设置参数
   * @param ps 语句
   */
  @Override
  public void setParameters(PreparedStatement ps) {
    
    
    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);
        // ParameterMode.OUT是CallableStatement的输出参数,已经单独注册。故忽略
        if (parameterMapping.getMode() != ParameterMode.OUT) {
    
    
          Object value;
          // 取出属性名称
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) {
    
    
            // 从附加参数中读取属性值
            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);
          }
          // 确定该参数的处理器
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
    
    
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
    
    
            // 此方法最终根据参数类型,调用java.sql.PreparedStatement类中的参数赋值方法,对SQL语句中的参数赋值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
    
    
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

The implementation logic of the setParameters method is also very simple, that is, taking out the value of each parameter in turn, and then calling the assignment method in PreparedStatement according to the parameter type to complete the assignment.

1.4. Result processing function

To process MyBatis query results, the functions that need to be completed are:

  • Process logic such as nested mapping in result mapping;
  • According to the mapping relationship, the result object is generated;
  • Assign values ​​to the properties of the result object based on database query records;
  • Summarize the result objects into List, Map, Cursor and other forms.

The result sub-package of the executor package is only responsible for completing part of the simple function of "summarizing result objects into the form of List, Map, Cursor, etc.": summarizing the result objects into the form of List or Map. The function of summarizing the results into Cursor form is implemented by the cursor package.

Before introducing the result sub-package, we first introduce the two interfaces located in the session package: ResultContext interface and ResultHandler interface.

  • The ResultContext interface represents the result context, which stores a result of the database operation (corresponding to a record in the database).
  • The ResultHandler interface represents the result handler, and the database operation results will be processed by it. Therefore, ResultHandler will be responsible for processing ResultContext.

There are three main classes in the result sub-package: DefaultResultContext class, DefaultResultHandler class and DefaultMapResultHandler class. Among these three classes, the DefaultResultContext class is the only implementation class of the ResultContext interface, and the DefaultResultHandler class and DefaultMapResultHandler class are the implementation classes of the ResultHandler interface.
Insert image description here
DefaultResultContext is used to store a result object, corresponding to a record in the database.

public class DefaultResultContext<T> implements ResultContext<T> {
    
    
  // 结果对象
  private T resultObject;
  // 结果计数(表明这是第几个结果对象)
  private int resultCount;
  // 使用完毕(结果已经被取走)
  private boolean stopped;

After understanding the various properties of the DefaultResultContext class, the analysis of each ResultHandler class is very simple. The DefaultResultHandler class is responsible for aggregating the result objects in the DefaultResultContext class into a List return; and the DefaultMapResultHandler class is responsible for aggregating the result objects in the DefaultResultContext class into a Map return.

Among them, the DefaultMapResultHandler class is slightly more complicated, so we will introduce it using it as an example.

public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {
    
    
  // Map形式的映射结果
  private final Map<K, V> mappedResults;
  // Map的键。由用户指定,是结果对象中的某个属性名
  private final String mapKey;
  // 对象工厂
  private final ObjectFactory objectFactory;
  // 对象包装工厂
  private final ObjectWrapperFactory objectWrapperFactory;
  // 反射工厂
  private final ReflectorFactory reflectorFactory;
    /**
   * 处理一个结果
   * @param context 一个结果
   */
  @Override
  public void handleResult(ResultContext<? extends V> context) {
    
    
    // 从结果上下文中取出结果对象
    final V value = context.getResultObject();
    // 获得结果对象的元对象
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    // 基于元对象取出key对应的值
    final K key = (K) mo.getValue(mapKey);
    mappedResults.put(key, value);
  }

The handleResult method in the DefaultMapResultHandler class is used to complete the assembly of Map.

In this way, we understand how a single result object is aggregated and returned in the form of List, Map, or Cursor. DefaultResultContext, as the default ResultContext implementation class, stores a result object, corresponding to a record in the database. ResultHandler has three implementation classes that can handle DefaultResultContext objects. The functions of these three implementation classes are as follows.

  • The DefaultResultHandler class is responsible for aggregating multiple ResultContexts into a List and returning them.
  • The DefaultMapResultHandler class is responsible for aggregating multiple ResultContexts into a Map return.
  • The ObjectWrapperResultHandler internal class in the DefaultCursor class is responsible for aggregating multiple ResultContexts into one Cursor return.

1.5. Result set processing function

Section 1.4 introduces the mechanism by which MyBatis aggregates single result objects into List, Map, and Cursor. But that's only a very small part of the results processing process. In the result processing process, unfinished functions include:

  • Process logic such as nested mapping in result mapping;
  • According to the mapping relationship, the result object is generated;
  • Assign values ​​to the properties of the result object based on the database query records.

The above functions are provided by the resultset sub-package. Although the resultset subpackage provides many functions, it only has three classes.
Insert image description here

ResultSetWrapper is the result encapsulation class, ResultSetHandler and DefaultResultSetHandler are the interface and implementation classes of the result set processor respectively.
Before introducing the above three classes, we first understand the result set of MyBatis.

1.5.1. Processing of multiple result sets in MyBatis

In order to introduce the result set processing function in MyBatis, we first clarify the following concepts.

  • Result: A record queried from the database is a result, which can be mapped to a Java object.
  • Result set: refers to a collection of results. Multiple records queried from the database are a result set. The result set can be returned in the form of List, Map, or Cursor.
  • Multiple result sets: A collection of result sets, which contains multiple result sets.

Speaking of multiple result sets, you may have a question: Usually every database query operation only returns one result set. How can there be the concept of multiple result sets? For example, the union operation in the following code actually merges two result sets, but in the end the two result sets will still be merged into one result set.
Insert image description here
Although the results in the result set can be clearly divided into two categories, they still belong to one result set. In this result set, each result contains two attributes: id and name.

But in fact, a database query can indeed return multiple result sets.
Insert image description here
Insert image description here
MyBatis also supports processing multiple result sets,

<resultMap id="userMap" type="User" autoMapping="true">
    <collection property="taskList" resultSet="taskRecord" resultMap="taskMap" />
</resultMap>

<resultMap id="taskMap" type="Task">
    <result property="id" column="id" />
    <result property="userId" column="userId" />
    <result property="taskName" column="taskName" />
</resultMap>

<!--返回两个结果组成的结果集-->
<select id="query" resultMap="userMap,taskMap" resultSets="userRecord,taskRecord" statementType="CALLABLE">
  CALL multiResults()
</select>

The statement will accept two result sets and name the two result sets userRecord and taskRecord respectively. Then use userMap to map the result set userRecord, and use taskMap to map the result set taskRecord.
Finally, you can get multiple result sets stored in the result variable, two of which are stored using List. The first result set is a list of User objects mapped using userMap, and the second result set is a list of Task objects mapped using taskMap.

1.5.2. Result set encapsulation class

After java.sql.Statement completes the database operation, the corresponding operation result is returned by java.sql.ResultSet. The methods defined in the java.sql.ResultSet interface are mainly divided into several categories:

  • Switch to the next result, and read whether this result is the first result, the last result, and other related methods for switching between results;
  • Read the value of a column of the current result;
  • Modify the value of a column of the current result (the modification will not affect the actual value in the database);
  • Some other auxiliary functions, such as reading the type information of all columns, etc.
    The above methods can already satisfy the query operation of database results.

The ResultSetWrapper class in MyBatis is a further encapsulation of java.sql.ResultSet, and the decorator mode is used here. The ResultSetWrapper class extends more functions based on the java.sql.ResultSet interface. These functions include getting a list of all column names, getting a list of all column types, getting the JDBC type of a certain column, and getting the corresponding type processor etc.

 /* 
 * ResultSet的封装,包含了ResultSet的一些元数据
 * 类似于装饰器模式,但是比较简单。只是在原有类的外部再封装一些属性、方法
 */
public class ResultSetWrapper {
    
    
  // 被装饰的resultSet对象
  private final ResultSet resultSet;
  // 类型处理器注册表
  private final TypeHandlerRegistry typeHandlerRegistry;
  // resultSet中各个列对应的列名列表
  private final List<String> columnNames = new ArrayList<>();
  // resultSet中各个列对应的Java类型名列表
  private final List<String> classNames = new ArrayList<>();
  // resultSet中各个列对应的JDBC类型列表
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  // <列名,< java类型,TypeHandler>>
  // 这里的数据是不断组建起来的。java类型传入,然后去全局handlerMap索引java类型的handler放入map,然后在赋给列名。
  // 每个列后面的java类型不应该是唯一的么?不是的
  //    <resultMap id="userMapFull" type="com.example.demo.UserBean">
  //        <result property="id" column="id"/>
  //        <result property="schoolName" column="id"/>
  //    </resultMap>
  // 上面就可能不唯一,同一个列可以给不同的java属性
  // 类型与类型处理器的映射表。结构为:Map<列名,Map<Java类型,类型处理器>>
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  // 记录了所有的有映射关系的列。
  // key为resultMap的id,后面的List为该resultMap中有映射的列的列表
  // <resultMap的id,List<对象映射的列名>>
  // 记录了所有的有映射关系的列。结构为:Map<resultMap的id,List<对象映射的列名>>
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  // 记录了所有的无映射关系的列。
  // key为resultMap的id,后面的List为该resultMap中无映射的列的列表
  //  // <resultMap的id : List<对象映射的列名>>
  // 记录了所有的无映射关系的列。结构为:Map<resultMap的id,List<对象映射的列名>>
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();

1.5.2. Result set processing class

ResultSetHandler is the result set processor interface, which defines three abstract methods of the result set processor.

  • <E> List<E> handleResultSets (Statement stmt): Process the execution results of Statement into List.
  • <E> Cursor<E> handleCursorResultSets (Statement stmt): Process the execution results of Statement into Map.
  • void handleOutputParameters(CallableStatement cs): Process the output results of the stored procedure.
    The DefaultResultSetHandler class, as the default and only implementation class of the ResultSetHandler interface, implements the above abstract method. The following takes the handleResultSets method in the DefaultResultSetHandler class as an example to introduce how MyBatis completes the processing of the result set.

It can also be seen from the name of the handleResultSets method (Sets is the plural form) that it can handle multiple result sets. When processing multiple result sets, we get a two-level list, that is, the result set list and the result list nested within it, while when processing a single result set, we can get the result list directly.
Insert image description here

  /**
   * 处理Statement得到的多结果集(也可能是单结果集,这是多结果集的一种简化形式),最终得到结果列表
   * @param stmt Statement语句
   * @return 结果列表
   * @throws SQLException
   */
  @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;
    // 可能会有多个结果集,先取出第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 查询语句对应的resultMap节点,可能含有多个
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 合法性校验(存在输出结果集的情况下,resultMapCount不能为0)
    validateResultMapsCount(rsw, resultMapCount);
    // 循环遍历每一个设置了resultMap的结果集
    while (rsw != null && resultMapCount > resultSetCount) {
    
    
      // 获得当前结果集对应的resultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 进行结果集的处理
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 获取下一结果集
      rsw = getNextResultSet(stmt);
      // 清理上一条结果集的环境
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 获取多结果集中所有结果集的名称
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
    
    
      // 循环遍历每一个没有设置resultMap的结果集
      while (rsw != null && resultSetCount < resultSets.length) {
    
    
        // 获取该结果集对应的父级resultMap中的resultMapping(注:resultMapping用来描述对象属性的映射关系)
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
    
    
          // 获取被嵌套的resultMap的编号
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          // 处理嵌套映射
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    // 判断是否是单结果集:如果是则返回结果列表;如果否则返回结果集列表
    return collapseSingleResultList(multipleResults);
  }

The handleResultSets method completes the processing of multiple result sets. But the processing of each result set is implemented by the handleResultSet sub-method

  /**
   * 处理单一的结果集
   * @param rsw ResultSet的包装
   * @param resultMap resultMap节点的信息
   * @param multipleResults 用来存储处理结果的list
   * @param parentMapping
   * @throws SQLException
   */
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    
    
    try {
    
    
      if (parentMapping != null) {
    
     // 嵌套的结果
        // 向子方法传入parentMapping。处理结果中的记录。
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
    
     // 非嵌套的结果
        if (resultHandler == null) {
    
    
          // defaultResultHandler能够将结果对象聚合成一个List返回
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          // 处理结果中的记录。
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
    
    
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
    
    
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

What is passed into the handleResultSet method is already a single result set, and the handleResultSet method calls the handleRowValues ​​method for further processing.
In the handleRowValues ​​method, classification will be performed again based on whether there is nesting in the current mapping, and the handleRowValuesForNestedResultMap method and the handleRowValuesForSimpleResultMap method will be called respectively.

  /**
   * 处理单结果集中的属性
   * @param rsw 单结果集的包装
   * @param resultMap 结果映射
   * @param resultHandler 结果处理器
   * @param rowBounds 翻页限制条件
   * @param parentMapping 父级结果映射
   * @throws SQLException
   */
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
                              RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    
    
    if (resultMap.hasNestedResultMaps()) {
    
    
      // 前置校验
      ensureNoRowBounds();
      checkResultHandler();
      // 处理嵌套映射
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
    
    
      // 处理单层映射
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

Take the handleRowValuesForSimpleResultMap method as an example to continue viewing the overall processing flow.

  /**
   * 处理非嵌套映射的结果集
   * @param rsw 结果集包装
   * @param resultMap 结果映射
   * @param resultHandler 结果处理器
   * @param rowBounds 翻页限制条件
   * @param parentMapping 父级结果映射
   * @throws SQLException
   */
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    
    
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    // 当前要处理的结果集
    ResultSet resultSet = rsw.getResultSet();
    // 根据翻页配置,跳过指定的行
    skipRows(resultSet, rowBounds);
    // 持续处理下一条结果,判断条件为:还有结果需要处理 && 结果集没有关闭 && 还有下一条结果
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    
    
      // 经过鉴别器鉴别,确定经过鉴别器分析的最终要使用的resultMap
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // 拿到了一行记录,并且将其转化为一个对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // 把这一行记录转化出的对象存起来
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

In the handleRowValuesForSimpleResultMap method, the processing of the results in the result set is truly completed. When processing each result, the following functions are included.

  • Obtain the resultMap corresponding to the record based on the discriminator. This function is implemented by calling the resolveDiscriminatedResult-Map submethod.
  • According to the resultMap, this record is converted into an object, and this function is implemented by calling the getRowValue submethod.
  • To store the object converted from this row of records, this function is implemented by calling the storeObject submethod.

getRowValue method
This method uses reflection to create the object corresponding to the record and assigns values ​​to the properties of the object. You can continue to track the operation process of creating an object through the createResultObject sub-method, and the operation process of assigning values ​​to object properties can continue to be tracked through the applyAutomaticMappings sub-method and applyPropertyMappings sub-method.

  /**
   * 将一条记录转化为一个对象
   * @param rsw 结果集包装
   * @param resultMap 结果映射
   * @param columnPrefix 列前缀
   * @return 转化得到的对象
   * @throws SQLException
   */
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    
    
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建这一行记录对应的对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    
    
      // 根据对象得到其MetaObject
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // 是否允许自动映射未明示的字段
      if (shouldApplyAutomaticMappings(resultMap, false)) {
    
    
        // 自动映射未明示的字段
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      // 按照明示的字段进行重新映射
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

storeObject method
In the storeObject method, it will be processed separately according to the current object:

  • If the current object belongs to the parent mapping, the object is bound to the parent object;
  • If the current object belongs to an independent mapping, use ResultHandler to aggregate the object.
  /**
   * 存储当前结果对象
   * @param resultHandler 结果处理器
   * @param resultContext 结果上下文
   * @param rowValue 结果对象
   * @param parentMapping 父级结果映射
   * @param rs 结果集
   * @throws SQLException
   */
  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    
    
    if (parentMapping != null) {
    
    
      // 存在父级,则将这一行记录对应的结果对象绑定到父级结果上
      linkToParents(rs, parentMapping, rowValue);
    } else {
    
    
      // 使用resultHandler聚合该对象
      callResultHandler(resultHandler, resultContext, rowValue);
    }
  }

In this way, after calling layers of sub-methods, the source code reading of the handleResultSets method is completed. It can be seen that in the handleResultSets method, important operations such as generating result objects, assigning values ​​to the properties of the result objects, and aggregating or binding the result objects are completed.

2.Actuator

Each subpackage provides some subfunctionality for the executor. But in the end, these sub-functions are connected in series by the Executor interface and its implementation class, and jointly provide services to the outside world.
Insert image description here

2.1.Actuator interface

First, take a look at the methods defined in the Executor interface.

public interface Executor {
    
    
  ResultHandler NO_RESULT_HANDLER = null;
  // 数据更新操作,其中数据的增加、删除、更新均可由该方法实现
  int update(MappedStatement ms, Object parameter) throws SQLException;
  // 数据查询操作,返回结果为列表形式
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  // 数据查询操作,返回结果为列表形式
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
  // 数据查询操作,返回结果为游标形式
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
  // 清理缓存
  List<BatchResult> flushStatements() throws SQLException;
  // 提交事务
  void commit(boolean required) throws SQLException;
  // 回滚事务
  void rollback(boolean required) throws SQLException;
  // 创建当前查询的缓存键值
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  // 本地缓存是否有指定值
  boolean isCached(MappedStatement ms, CacheKey key);
  // 清理本地缓存
  void clearLocalCache();
  // 懒加载
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  // 获取事务
  Transaction getTransaction();
  // 关闭执行器
  void close(boolean forceRollback);
  // 判断执行器是否关闭
  boolean isClosed();
  // 设置执行器包装
  void setExecutorWrapper(Executor executor);
}

Based on the above methods, operations such as data addition, deletion, modification, query, and transaction processing can be completed. In fact, all database operations of MyBatis are indeed implemented by calling these methods.

2.1. Executor base class and implementation class

Among the various implementation classes of the Executor interface, CachingExecutor does not contain specific database operations, but encapsulates a layer of cache based on other database operations, so it does not inherit BaseExecutor. All other implementation classes inherit BaseExecutor.

BaseExecutor is an abstract class and uses the template pattern. It implements some common basic functions of its subclasses, and leaves operations directly related to subclasses to be handled by subclasses.

  /**
   * 查询数据库中的数据
   * @param ms 映射语句
   * @param parameter 参数对象
   * @param rowBounds 翻页限制条件
   * @param resultHandler 结果处理器
   * @param key 缓存的键
   * @param boundSql 查询语句
   * @param <E> 结果类型
   * @return 结果列表
   * @throws SQLException
   */
  @SuppressWarnings("unchecked")
  @Override
  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) {
    
    
        // 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    
    
        // 本地缓存没有结果,故需要查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
    
    
      queryStack--;
    }
    if (queryStack == 0) {
    
    
      // 懒加载操作的处理
      for (DeferredLoad deferredLoad : deferredLoads) {
    
    
        deferredLoad.load();
      }
      deferredLoads.clear();
      // 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    
    
        clearLocalCache();
      }
    }
    return list;
  }

In the core method of the query method, an attempt will be made to read the first-level cache. When there are no results in the cache, the queryFromDatabase method will be called to query the results in the database.

  /**
   * 从数据库中查询结果
   * @param ms 映射语句
   * @param parameter 参数对象
   * @param rowBounds 翻页限制条件
   * @param resultHandler 结果处理器
   * @param key 缓存的键
   * @param boundSql 查询语句
   * @param <E> 结果类型
   * @return 结果列表
   * @throws SQLException
   */
  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 {
    
    
      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 specific implementation of the doQuery submethod is implemented by a subclass of BaseExecutor, so this is a typical template pattern.

Next, you might as well analyze the source code of the update method in BaseExecutor

  /**
   * 更新数据库数据,INSERT/UPDATE/DELETE三种操作都会调用该方法
   * @param ms 映射语句
   * @param parameter 参数对象
   * @return 数据库操作结果
   * @throws SQLException
   */
  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    
    
    ErrorContext.instance().resource(ms.getResource())
            .activity("executing an update").object(ms.getId());
    if (closed) {
    
    
      // 执行器已经关闭
      throw new ExecutorException("Executor was closed.");
    }
    // 清理本地缓存
    clearLocalCache();
    // 返回调用子类进行操作
    return doUpdate(ms, parameter);
  }

It can be clearly seen that the source code of the update method is much simpler than the source code of the query method. The reason why the source code of query operations is often more complicated than the source code of add, delete, and modify operations is:

  • The input parameters of query operations are relatively complex, while the parameters of operations such as adding, deleting, and modifying are usually relatively simple.
  • The output results of query operations are relatively complex. The results are usually mapped into objects, and even include complex operations such as nesting, result set operations, lazy loading, and object type identification. The output results of operations such as adding, deleting, and modifying are usually It is relatively simple and only contains the number of affected items.
  • The output result form of the query operation is relatively complex, such as supporting output in the form of List, Map, Cursor, etc.; while the result form of operations such as adding, deleting, and modifying is usually relatively simple, containing only a number.
  • The implementation of query operations is relatively complex, such as caching, lazy loading, nested mapping, etc. Operations such as adding, deleting, and modifying often do not require these complex processes.

Because query operations are more complex than other operations, query operations are often used as an example for source code analysis in source code reading in this book. When reading source code, you often encounter many branches. These branches are similar in implementation ideas. We do not need to read all their source codes. We only need to select some representative branches for in-depth reading. In the process of selecting branches, there are the following two ideas.

  1. You can consider choosing the most complex branch, so that when the code of this branch is clearly analyzed by us, the codes of other branches will naturally be clear as well.
  2. We can also consider choosing the simplest branch, which will allow us to quickly understand the idea of ​​​​the code, and it will be easier to analyze other complex branches.
    The choice of which branch to follow depends on the specific circumstances. Generally speaking, choose complex branches for important codes; choose simple branches for minor codes; choose complex branches for simple codes; choose simple branches for complex codes.
    For MyBatis, input/output parameter processing, cache processing, lazy loading processing, etc. are all very important functions. Therefore, we will choose the query operation branch that contains these functions to start reading the source code.

BaseExecutor has four implementation classes, and their functions are as follows.

  • ClosedExecutor: An executor that can only represent that it has been closed and has no other actual functions.
  • SimpleExecutor: The simplest executor.
  • BatchExecutor: An executor that supports batch execution function.
  • ReuseExecutor: An executor that supports Statement object reuse.
    The selection of the three executors, SimpleExecutor, BatchExecutor, and ReuseExecutor, is done in the MyBatis configuration file. The optional values ​​are defined by the enumeration class ExecutorType in the session package. These three executors are mainly based on StatementHandler to complete work such as creating Statement objects and binding parameters.
    BatchResult is also a class in the executor, which can save the parameter object list and the number of affected items for batch operations.

3. Error context

The related methods of the ErrorContext class are called at the beginning of many methods.

    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());

The ErrorContext class is an error context, which can save some background information in advance. In this way, when an error actually occurs, the background information can be provided, which will facilitate our error troubleshooting.

public class ErrorContext {
    
    
  // 获得当前操作系统的换行符
  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  // 将自身存储进ThreadLocal,从而进行线程间的隔离
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();
  // 存储上一版本的自身,从而组成错误链
  private ErrorContext stored;
  // 下面几条为错误的详细信息,可以写入一项或者多项
  private String resource;
  private String activity;
  private String object;
  private String message;
  private String sql;
  private Throwable cause;

The property settings of the ErrorContext class are very simple, but the entire class is designed very cleverly. first,

  /**
   * 从ThreadLocal取出已经实例化的ErrorContext,或者实例化一个ErrorContext放入ThreadLocal
   * @return ErrorContext实例
   */
  public static ErrorContext instance() {
    
    
    ErrorContext context = LOCAL.get();
    if (context == null) {
    
    
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  }

The ErrorContext class implements the singleton pattern, and its singleton is bound to ThreadLocal. This ensures that each thread has a unique ErrorContext.
The ErrorContext class also has a packaging mechanism, that is, each ErrorContext object can wrap an ErrorContext object. In this way, the error context can form an error chain, which is very similar to the exception chain introduced earlier. This packaging function is implemented by the store method:

  /**
   * 创建一个包装了原有ErrorContext的新ErrorContext
   * @return 新的ErrorContext
   */
  public ErrorContext store() {
    
    
    ErrorContext newContext = new ErrorContext();
    newContext.stored = this;
    LOCAL.set(newContext);
    return LOCAL.get();
  }

Of course, in addition to being able to create a new ErrorContext object that wraps the original ErrorContext object, the ErrorContext class also supports the reverse operation of this operation - stripping out the internal ErrorContext object of an ErrorContext object. This stripping function is implemented by the recall method:

  /**
   * 剥离出当前ErrorContext的内部ErrorContext
   * @return 剥离出的ErrorContext对象
   */
  public ErrorContext recall() {
    
    
    if (stored != null) {
    
    
      LOCAL.set(stored);
      stored = null;
    }
    return LOCAL.get();
  }

In addition, the ErrorContext class also has the reset method for clearing all information, the toString method for converting to string output, and the instance, resource, activity, store and other methods for setting various detailed information. The usage scenarios of these methods are as follows.

  • When you need to obtain the ErrorContext object of the current thread, call the instance method.
  • When the thread executes to a certain stage and generates new context information, call resource, activity and other methods to supplement the context information to the ErrorContext object.
  • When the thread enters the next level of operation and is in a new environment, call the store method to obtain a new ErrorContext object that wraps the original ErrorContext object.
  • When the thread returns to the upper level from the next level operation, the recall method is called to peel off the ErrorContext object of the upper level.
  • When the thread enters a new environment that is unrelated to the previous operation, call the reset method to clear all information of the ErrorContext object.
  • When the thread needs to print exception information, call the toString method to output the environment information when the error occurred.
    Through these operations, the thread's ErrorContext class always saves the context information of the current moment, and this information can be provided once an exception actually occurs.
    Insert image description here

The wrapException method (contained in the ExceptionFactory class of the exceptions package) only explicitly updates the message attribute and cause attribute in the ErrorContext object, but the result output by the toString method may contain richer attribute information. And those attribute information are updated in real time as the thread execution environment changes.

Guess you like

Origin blog.csdn.net/d495435207/article/details/131342823