MyBatis05-"General Source Code Guide: Detailed Explanation of MyBatis Source Code" notes-mapping package

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. mapping package

The mapping package is a very important package, which defines many parsing entity classes in MyBatis. Some of these entity classes are related to SQL statements, some are related to SQL input/output parameters, and some are related to configuration information.

The mapping package mainly completes the following functions.

  • SQL statement processing function;
  • Output result processing function;
  • Input parameter processing function;
  • Multiple database type processing functions;
  • Other functions.

1.1 SQL language processing function

In the mapping package, there are three main classes related to SQL statement processing functions, the MappedStatement class, the SqlSource class and the BoundSql class.

  • The MappedStatement class represents all the content within the database operation nodes (select, insert, update, delete four types of nodes);
  • The SqlSource class is the SQL statement contained in the database operation tag;
  • The BoundSql class is the product of further processing of the SqlSource class.
    Insert image description here

1.1.1 MappedStatement class

MappedStatement is a typical analytical entity class, which is the entity corresponding to the database operation node in the mapping file.

<insert id="addUser" parameterType="com.github.yeecode.mybatisdemo.model.User">
    INSERT INTO `user`
    (`name`,`email`,`age`,`sex`,`schoolName`)
    VALUES
    (#{name},#{email},#{age},#{sex},#{schoolName})
</insert>

1.1.2 SqlSource class

SqlSource is a parsing entity interface, which corresponds to the SQL statement in MappedStatement. SqlSource itself is an interface, which only defines one method to return a BoundSql object.

/**
 * 一共有四个实现
 */
public interface SqlSource {
    
    
  /**
   * 获取一个BoundSql对象
   * @param parameterObject 参数对象
   * @return BoundSql对象
   */
  BoundSql getBoundSql(Object parameterObject);
}

Insert image description here
The differences between the four implementation classes of the SqlSource interface are as follows.

  • DynamicSqlSource: dynamic SQL statement. The so-called dynamic SQL refers to statements containing dynamic SQL nodes (such as "if" nodes) or "${}" placeholders.
  • RawSqlSource: native SQL statement. Refers to a non-dynamic statement, which may contain "#{}" placeholders, but does not contain dynamic SQL nodes or "${}" placeholders.
  • StaticSqlSource: static statement. The statement may contain "?" and can be submitted directly to the database for execution.
  • ProviderSqlSource: The above are all SQL statements obtained through XML files, while ProviderSqlSource is a SQL statement obtained through annotation mapping.

Both DynamicSqlSource and RawSqlSource will be processed into StaticSqlSource, and then the SqlSource object is obtained through the getBoundSql method of StaticSqlSource. Both DynamicSqlSource and RawSqlSource are in the scripting package. The next chapter details the conversion process between the four implementation classes of the SqlSource interface.

1.1.3 BoundSql class

BoundSql is the SQL statement after parameter binding is completed.

/**
 * 绑定的SQL,是从SqlSource而来,将动态内容都处理完成得到的SQL语句字符串,其中包括?,还有绑定的参数
 */
public class BoundSql {
    
    
  // 可能含有“?”占位符的sql语句
  private final String sql;
  // 参数映射列表
  private final List<ParameterMapping> parameterMappings;
  // 实参对象本身
  private final Object parameterObject;
  // 实参
  private final Map<String, Object> additionalParameters;
  // additionalParameters的包装对象
  private final MetaObject metaParameters;
}  

BoundSql is an important intermediate product in SQL statements. It not only stores the SQL information after conversion, but also contains actual parameter information and some additional environment information. Next, it will continue to play a role in the execution of SQL.

1.2 Output result processing function

In the database operation node of the mapping file, you can directly use the resultType setting to map the output results to Java objects. However, there is a more flexible and powerful way, which is to use resultMap to define the mapping method of the output results. The function of resultMap is very powerful. It supports assembly, judgment, lazy loading, etc. of output results.

    <resultMap id="userMap" type="User" autoMapping="false">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <discriminator javaType="int" column="sex">
            <case value="0" resultMap="boyUserMap"/>
            <case value="1" resultMap="girlUserMap"/>
        </discriminator>
    </resultMap>

resultMap can output different subclasses based on the different sex attributes in the result object.
The processing of output results mainly involves the ResultMap class, ResultMapping class, and Discriminator class, which are also parsing entity classes.
Insert image description here

1.2.1 ResultMap class

The ResultMap class is the parsing entity class corresponding to the resultMap node, and its attributes are highly consistent with the information of the resultMap node.

public class ResultMap {
    
    
  // 全局配置信息
  private Configuration configuration;
  // resultMap的编号
  private String id;
  // 最终输出结果对应的Java类
  private Class<?> type;
  // XML中的<result>的列表,即ResultMapping列表
  private List<ResultMapping> resultMappings;
  // XML中的<id>的列表
  private List<ResultMapping> idResultMappings;
  // XML中的<constructor>中各个属性的列表
  private List<ResultMapping> constructorResultMappings;
  // XML中非<constructor>相关的属性列表
  private List<ResultMapping> propertyResultMappings;
  // 所有参与映射的数据库中字段的集合
  private Set<String> mappedColumns;
  // 所有参与映射的Java对象属性集合
  private Set<String> mappedProperties;
  // 鉴别器
  private Discriminator discriminator;
  // 是否存在嵌套映射
  private boolean hasNestedResultMaps;
  // 是否存在嵌套查询
  private boolean hasNestedQueries;
  // 是否启动自动映射
  private Boolean autoMapping;

After comparing the XML configuration, all attributes are easier to understand. What is slightly more complicated is that there are four *ResultMappings lists.

    <resultMap id="userMapByConstructor" type="User">
        <constructor>
            <idArg column="id" javaType="Integer"/>
            <arg column="name" javaType="String"/>
            <arg column="sex" javaType="Integer"/>
            <arg column="schoolName" javaType="String"/>
        </constructor>
    </resultMap>

    <resultMap id="girlUserMap" type="Girl" extends="userMap">
        <result property="email" column="email"/>
    </resultMap>

    <resultMap id="userMap" type="User" autoMapping="false">
        <id property="id" column="id" javaType="Integer" jdbcType="INTEGER"
            typeHandler="org.apache.ibatis.type.IntegerTypeHandler"/>
        <result property="name" column="name"/>
        <discriminator javaType="int" column="sex">
            <case value="0" resultMap="boyUserMap"/>
            <case value="1" resultMap="girlUserMap"/>
        </discriminator>
    </resultMap>

    <resultMap id="boyUserMap" type="Boy" extends="userMap">
        <result property="age" column="age"/>
    </resultMap>

In the resultMap of "id="userMap"", MyBatis will call the parameterless constructor of the class to create an object, and then assign values ​​to each attribute. In the resultMap of "id="userMapByConstructor"", MyBatis will call the corresponding constructor method to create the object. Therefore, the properties of the object are divided into two categories: properties in the constructor method and properties in the non-constructor method. An idArg tag can be set under the constructor tag. An id tag can also be set under the ordinary resultMap tag. Different from the attributes corresponding to other tags, the attributes corresponding to these two tags can be used as identification attributes to distinguish whether the object is the same object . Therefore, the attributes of the object are divided into two categories: id attributes and non-id attributes .

According to the above two classification methods, the following four attributes are generated.

  • resultMappings: all attributes;
  • idResultMappings: all id attributes;
  • constructorResultMappings: attributes in all constructors;
  • propertyResultMappings: All properties in non-constructor methods.

1.2.2 ResultMapping class

There are a large number of properties in ResultMapping, so creating a ResultMapping object is very complicated. To improve this process, ResultMapping uses the builder pattern. Moreover, its builder is placed directly inside the class and appears as an internal static class. Invocations of methods in an inner static class do not need to create an object of the class, but they can generate an object of the class. The builder method can easily create a ResultMapping object and set various properties.
The builder pattern based on internal classes improves the cohesion of classes and is worth learning from when designing software.

1.2.3 Discriminator

Discriminator is the discriminator inside resultMap. Just like the selection statement in the program, it allows the data query results to be mapped differently based on certain conditions.

    <resultMap id="userMap" type="User" autoMapping="false">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <discriminator javaType="int" column="sex">
            <case value="0" resultMap="boyUserMap"/>
            <case value="1" resultMap="girlUserMap"/>
        </discriminator>
    </resultMap>

    <resultMap id="girlUserMap" type="Girl" extends="userMap">
        <result property="email" column="email"/>
    </resultMap>

    <resultMap id="boyUserMap" type="Boy" extends="userMap">
        <result property="age" column="age"/>
    </resultMap>

The resultMap of "id="userMap"" can perform different mappings according to the value of the sex field: if the sex value is 0, the final output result is a Girl object, and the email attribute is set according to the query result; if the sex value is 1, the final The output result is a Boy object, and the age attribute is set according to the query result.

public class Discriminator {
    
    

  // 存储条件判断行的信息,如<discriminator javaType="int" column="sex">中的信息
  private ResultMapping resultMapping;

  // 存储选择项的信息,键为value值,值为resultMap值。如<case value="0" resultMap="boyUserMap"/>中的信息
  private Map<String, String> discriminatorMap;

Compared to the properties of the Discriminator class, we are more concerned about its validity logic. This part of the logic can be seen in the resolveDiscriminatedResultMap method of the DefaultResultSetHandler class.

  /**
   * 应用鉴别器
   * @param rs 数据库查询出的结果集
   * @param resultMap 当前的ResultMap对象
   * @param columnPrefix 属性的父级前缀
   * @return 已经不包含鉴别器的新的ResultMap对象
   * @throws SQLException
   */
  public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
    
    
    // 已经处理过的鉴别器
    Set<String> pastDiscriminators = new HashSet<>();
    Discriminator discriminator = resultMap.getDiscriminator();
    while (discriminator != null) {
    
    
      // 求解条件判断的结果,这个结果值就是鉴别器鉴别的依据
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
      // 根据真实值判断属于哪个分支
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
      // 从接下来的case里面找到这个分支
      if (configuration.hasResultMap(discriminatedMapId)) {
    
    
        // 找出指定的resultMap
        resultMap = configuration.getResultMap(discriminatedMapId);
        // 继续分析下一层
        Discriminator lastDiscriminator = discriminator;
        // 查看本resultMap内是否还有鉴别器
        discriminator = resultMap.getDiscriminator();
        // 辨别器出现了环
        if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
    
    
          break;
        }
      } else {
    
    
        break;
      }
    }
    return resultMap;
  }

View the solution process of the judgment condition, which is in the getDiscriminatorValue method of the DefaultResultSetHandler class.

  /**
   * 求解鉴别器条件判断的结果
   * @param rs 数据库查询出的结果集
   * @param discriminator 鉴别器
   * @param columnPrefix
   * @return 计算出鉴别器的value对应的真实结果
   * @throws SQLException
   */
  private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
    
    
    final ResultMapping resultMapping = discriminator.getResultMapping();
    // 要鉴别的字段的typeHandler
    final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
    // prependPrefix(resultMapping.getColumn(), columnPrefix) 得到列名,然后取出列的值
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
  }

1.2.4 Input parameter processing function

MyBatis can not only map database results into objects, but also map objects into input parameters required by SQL statements. This mapping relationship is represented by the parameterMap tag. In this way, as long as the User object is entered, parameterMap can disassemble it into name and schoolName parameters.

<parameterMap id="userParam01" type="User">
    <parameter property="name" javaType="String"/>
    <parameter property="schoolName" javaType="String"/>
</parameterMap>

In the process of input parameter processing, the two classes ParameterMap and ParameterMapping are mainly involved, and they are also parsing entity classes.
Insert image description here
As parsing entity classes, the ParameterMap class and ParameterMapping class correspond to the attributes in the tag, and the overall architecture is relatively simple. And these two classes are very similar to the ResultMap class and ResultMapping class.

1.2.4 Multi-database type processing function

As an excellent ORM framework, MyBatis supports a variety of databases, such as SQL Server, DB2, Oracle, MySQL, PostgreSQL, etc. However, the supported SQL specifications vary slightly between different types of databases. For example, to limit the number of query results, use the TOP keyword in SQL Server and the LIMIT keyword in MySQL. In order to be compatible with the SQL specifications of different databases, MyBatis supports multiple databases. Before using multiple databases, you need to enumerate the database types to be used in the configuration file.

<databaseIdProvider type="DB_VENDOR">
    <property name="MySQL" value="mysql" />
    <property name="SQL Server" value="sqlserver" />
</databaseIdProvider>

<select id="selectByAge" resultMap="userMap" databaseId="mysql">
    SELECT * FROM `user` WHERE `age` = #{age} TOP 5
</select>
<select id="selectByAge" resultMap="userMap" databaseId="sqlserver">
    SELECT * FROM `user` WHERE `age` = #{age} LIMIT 5
</select>

In this way, MyBatis will use different query statements when connecting to different databases. In this example, MyBatis will use the query statement containing "TOP 5" when connecting to the MySQL database, and use the statement containing "LIMIT 5" when connecting to the SQL Server database.
The implementation of multi-data support is handled by the DatabaseIdProvider interface. It has a VendorDatabaseIdProvider subclass and a soon-to-be-deprecated DefaultDatabaseIdProvider subclass.

  /**
   * 获取当前的数据源类型的别名
   * @param dataSource 数据源
   * @return 数据源类型别名
   * @throws SQLException
   */
  private String getDatabaseName(DataSource dataSource) throws SQLException {
    
    
    // 获取当前连接的数据库名
    String productName = getDatabaseProductName(dataSource);
    // 如果设置有properties值,则根据将获取的数据库名称作为模糊的key,映射为对应的value
    if (this.properties != null) {
    
    
      for (Map.Entry<Object, Object> property : properties.entrySet()) {
    
    
        if (productName.contains((String) property.getKey())) {
    
    
          return (String) property.getValue();
        }
      }
      // 没有找到对应映射
      return null;
    }
    return productName;
  }

The getDatabaseName method does two jobs, first to get the type of the current data source, and then to map the data source type to the alias we set in the databaseIdProvider node. In this way, when a SQL statement needs to be executed, the SQL statement can be filtered based on the databaseId setting in the database operation node.

1.2.5 Other functions

There are also two important classes in the mapping package: Environment class and CacheBuilder class. The Environment class is also a parsing entity class, which corresponds to the environments node in the configuration file.

public final class Environment {
    
    
  // 编号
  private final String id;
  // 事务工厂
  private final TransactionFactory transactionFactory;
  // 数据源信息
  private final DataSource dataSource;

The CacheBuilder class is a cache builder, which is responsible for completing the creation of cache objects.

There are also some enumeration classes in the mapping package

  • FetchType: lazy loading settings;
  • ParameterMode: parameter type, refers to input parameters, output parameters, etc.;
  • ResultFlag: The special flag of the attribute in the returned result, indicating whether it is an id attribute or a constructor attribute;
  • ResultSetType: Access methods supported by the result set;
  • SqlCommandType: SQL command type, including add, delete, modify, query, etc.;
  • StatementType: SQL statement type, indicating whether it is a precompiled statement, whether it is a stored procedure, etc.

Guess you like

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