Joint query and nested mapping in mybatis

Posted by croakingtoad on Mon, 20 Dec 2021 10:44:36 +0100

Joint query and nested mapping scenario

Joint query refers to a query in which the query statement is associated with multiple tables such as left join or full table connection. This query involves multiple tables and generally finds the fields of multiple tables, and the corresponding resultMap needs to be mapped into multiple objects, as shown below:

  <resultMap id="blogComplexMap" type="com.entity.Blog" autoMapping="true">
        <id column="id" property="id"></id>
        <result column="title" property="title"></result>
        <collection property="comments" ofType="com.entity.Comment" autoMapping="true" columnPrefix="comment_">
        </collection>
    </resultMap>

<!--Joint query-->
    <select id="selectBlogByIdWithComplex" resultMap="blogComplexMap">
        select b.id,b.title,c.id as comment_id ,c.content as comment_content from  blog b left join comment c on b.id=c.blog_id where b.id = #{id}
    </select>

Here, you need to find the fields of blog and comment tables, and in the result mapping, you need to map the comment collection object in the blog object.

Mapping concept description

Mapping refers to the correspondence between the returned ResultSet column and the Java Bean property. The mapping description is carried out through ResultMapping, which is encapsulated into a whole with ResultMap. Mapping is divided into simple mapping and composite nested mapping.

Simple mapping: that is, the returned result set column has a 1-to-1 relationship with the object attribute. In this case, the ResultHandler will traverse the rows in the result set in turn, create an object for each row, and then fill the mapping attribute of the object in the traversal result set column. The following figure shows the one-to-one correspondence between the user object and the attribute in the user's resultMap:

Nested mapping: but most of the time, the object structure is implemented at the tree level. That is, objects are included in the object. The corresponding mapping is also this nested structure.

In the configuration mode of nested mapping, sub mapping can be configured directly, and external mapping and automatic mapping can also be introduced. There are two types of nested structures in mapping: one-to-one (association) and one to many (collection).

Joint query concept

How to get the results after the mapping is configured? Ordinary single table query cannot obtain the results required by composite mapping, so joint query must be used. Then, the data columns returned by the joint query are split into different object attributes. The 1-to-1 and 1-to-many splitting and creation methods are the same.
1-to-1 query mapping

select a.id,
       a.title,
       b.id as user_id,
       b.name as user_name
from blog a
         left join users b on a.author_id=b.id
where a.id = 1;

By combining the above statements with the query statements, the results in the following table can be obtained. In the result, the first two fields correspond to Blog and the last two fields correspond to User. Then populate the Blog object with User as the author attribute.


In the above two examples, each line will produce two objects, a Blog parent object and a User child object.
1 to many query

select a.id,a.title,
       c.id as comment_id,
       c.body as comment_body
from blog a
         left join comment c on a.id=c.blog_id
where a.id = 1;

Three results can be obtained from the above statements. The first two fields correspond to Blog and the last two fields correspond to comment. Different from 1 to 1, the three lines point to the same Blog. Because its ID is the same.


In the above results, the same three lines of blogs will create a Blog, and create three different comments to form a collection, which will be filled into the comments object. So how to judge whether the data are the same and need to be merged? In fact, mybatis determines whether the two rows of data are the same based on RowKey. The RowKey is generally specified based on the configuration, but sometimes it is not specified. In this case, other mapping fields will be used to create the RowKey. The specific rules are as follows:

Corresponding source code:

 private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(resultMap.getId());
        List<ResultMapping> resultMappings = this.getResultMappingsForRowKey(resultMap);
        if (resultMappings.isEmpty()) {
            if (Map.class.isAssignableFrom(resultMap.getType())) {
                this.createRowKeyForMap(rsw, cacheKey);
            } else {
                this.createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
            }
        } else {
            this.createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
        }

        return cacheKey.getUpdateCount() < 2 ? CacheKey.NULL_CACHE_KEY : cacheKey;
    }

Result set parsing process

Here, the 1-to-many case is directly used for analysis, because 1-to-1 is a simplified version of 1-to-many. The query results are as follows:

The whole analysis process is as follows:

All mapping processes are parsed in the DefaultResultSetHandler. The main methods are as follows:
handleRowValuesForNestedResultMap()

Nested result set parsing entry, where all rows in the result set will be traversed. And create a RowKey object for each row. Then call getRowValue() to get the result object. Finally, save it to ResultHandler. The corresponding source code is as follows:

Note: before calling getRowValue, the resolved object will be obtained based on the RowKey, and then sent to getRowValue as partialObject parameter

getRowValue()

This method will eventually generate a parsed object based on the current line. Specific responsibilities include
1. Create objects
2. Fill in general attributes
3. Fill in nested attributes.
When parsing nested properties, getRowValue will be called recursively to get child objects.
4. Finally, the current resolved object is temporarily stored based on RowKey.

If the partialObject parameter is not empty, only step 3 will be executed. Because 1 and 2 have been implemented.
The corresponding source code is as follows:

applyNestedResultMappings()

Resolve and populate nested result set mappings, traverse all nested mappings, and then obtain their nested resultmaps. Then create a RowKey to get the value of the staging area. Then call getRowValue to get the attribute object. Finally, fill to the parent object.

If the property object can be obtained through RowKey, it will still call getRowsValue because there may be unresolved properties under it.
The corresponding source code is as follows:

The documents referenced in this article are: www.coderead.cn

Topics: Mybatis