Mybatis powerful result mapper ResultMap

Keywords: Mybatis Java Attribute Database

1. Preface

The resultMap element is the most important and powerful element in MyBatis. It frees you from 90% of JDBC ResultSets data extraction code, and in some cases allows you to perform operations that JDBC does not support. In fact, when writing mapping code for complex statements such as connections, a resultMap can replace thousands of lines of code that implement the same function. The design idea of resultMap is to achieve zero configuration for simple statements. For more complex statements, it only needs to describe the relationship between statements.

resultMap can aggregate the complex data queried, such as the data of multiple tables, one-to-one mapping, one to many mapping and other complex relationships into a result set. The daily business development usually deals with it. Today I will give a detailed explanation of resultMap. DEMO at the end of the paper

2. resultMap

Next let's see how resultMap Maps map.

2.1 Getter/Setter injection

We declare an entity class corresponding to a database:

/**
 * @author felord.cn
 * @since 16:50
 **/
@Data
public class Employee implements Serializable {
    private static final long serialVersionUID = -7145891282327539285L;
    private String employeeId;
    private String employeeName;
    private Integer employeeType;
}

Then its corresponding resultMap is:

<mapper namespace="cn.felord.mybatis.mapper.EmployeeMapper">
    <resultMap id="EmployeeMap" type="cn.felord.mybatis.entity.Employee">
        <id column="employee_id" property="employeeId"/>
        <result column="employee_name" property="employeeName"/>
        <result column="employee_type" property="employeeType"/>
    </resultMap>
</mapper>

Let's explain the properties of these configurations:

<mapper namespace="Globally unique namespace">
    <resultMap id="book namespace Next unique" type="Mapped entity">
        <id column="Database primary key field name or alias, use it to improve overall performance" property="Corresponding entity attribute"/>
        <result column="Database field name or alias" property="Corresponding entity attribute"/>
    </resultMap>
</mapper>

The above method is to inject through Getter and Setter methods, that is, entity classes must have no parameter construction, and corresponding properties must have Getter and Setter methods.

2.2 structural injection

Getter and Setter methods are the most commonly used methods for injection. However, Mybatis also supports construction injection. If the Employee has the following construction methods:

public Employee(String employeeId, String employeeName, Integer employeeType) {
    this.employeeId = employeeId;
    this.employeeName = employeeName;
    this.employeeType = employeeType;
}

The corresponding resultMap can be written as follows:

<mapper namespace="cn.felord.mybatis.mapper.EmployeeMapper">
    <resultMap id="EmployeeMap" type="cn.felord.mybatis.entity.Employee">
        <constructor>
            <idArg column="employee_id" javaType="String"/>
            <arg column="employee_name" javaType="String"/>
            <arg column="employee_type" javaType="String"/>
        </constructor>
    </resultMap>
</mapper>

Careful students find that there is no property property here. In fact, when you do not declare the property property, you will inject it in the order of the parameter list of the construction method.

After the name attribute is introduced in Mybatis 3.4.3, we can disorganize the arg elements in the constructor tag.

<mapper namespace="cn.felord.mybatis.mapper.EmployeeMapper">
    <resultMap id="EmployeeConstructorMap" type="cn.felord.mybatis.entity.Employee">
        <constructor>
            <idArg column="employee_id" javaType="String" name="employeeId"/>
            <!-- You can add them out of the parameter list order-->
            <arg column="employee_type" javaType="Integer" name="employeeType"/>
            <arg column="employee_name" javaType="String" name="employeeName"/>
        </constructor>
    </resultMap>
</mapper>

2.3 inheritance relationship

Like classes in Java, resultMap can be inherited. Here are two Java classes with inheritance relationship:

The resultMap of RegularEmployee can be written as follows:

<resultMap id="RegularEmployeeMap" extends="EmployeeMap" type="cn.felord.mybatis.entity.RegularEmployee">
    <result column="level" property="level"/>
    <result column="job_number" property="jobNumber"/>
    <association property="department" javaType="cn.felord.mybatis.entity.Department">
        <id column="department_id" property="departmentId"/>
        <result column="department_name" property="departmentName"/>
        <result column="department_level" property="departmentLevel"/>
    </association>
</resultMap>

Just like Java's inheritance keyword, extends is used for inheritance.

2.4 one to one relationship

You will see clearly that there is an association tag in the last resultMap example in 2.3. What's this for? For example, each regular employee will correspond to a Department, and there will be a need to query this one-to-one relationship in the business. So association comes in handy.

<resultMap id="RegularEmployeeMap" extends="EmployeeMap" type="cn.felord.mybatis.entity.RegularEmployee">
    <result column="level" property="level"/>
    <result column="job_number" property="jobNumber"/>
    <association property="Property name" javaType="Corresponding Java type">
        <id column="department_id" property="departmentId"/>
        <result column="department_name" property="departmentName"/>
        <result column="department_level" property="departmentLevel"/>
    </association>
</resultMap>

Associations can continue to be nested, and there may be one-to-one relationships among the objects that are associated.

2.5 one to many Association

There is a one-to-one relationship, and naturally there will be a one to many relationship. We focus on customers instead. A department has multiple employees. We may need to query the information of a department and load the information of all employees into the Department and employee list.

/**
 * @author felord.cn
 * @since 15:33
 **/
public class DepartmentAndEmployeeList extends Department {
    private static final long serialVersionUID = -2503893191396554581L;
    private List<Employee> employees;

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }
}

We can use the collection keyword in resultMap to handle one to many mapping relationships:

<resultMap id="DepartmentAndEmployeeListMap" extends="DepartmentMap"
           type="cn.felord.mybatis.entity.DepartmentAndEmployeeList">
    <collection property="employees" ofType="cn.felord.mybatis.entity.RegularEmployee">
        <id column="employee_id" property="employeeId"/>
        <result column="employee_name" property="employeeName"/>
        <result column="level" property="level"/>
        <result column="job_number" property="jobNumber"/>
    </collection>
</resultMap>

2.6 discriminator

As we all know, not all employees are regular workers, but also temporary workers. Sometimes we also hope to be able to distinguish the two, as for the reason you know. There will be no further discussion on this issue. In terms of this requirement, our mapping relationship is complex again. We need to determine which data is a formal worker and which data is a temporary worker according to a certain condition, and then load them into regularEmployees and temporaryEmployees of the following entity classes.

/**
 * @author felord.cn
 * @since 15:33
 **/
public class DepartmentAndTypeEmployees extends Department {
    private static final long serialVersionUID = -2503893191396554581L;
    private List<RegularEmployee> regularEmployees;
    private List<TemporaryEmployee> temporaryEmployees;
    // getter setter
}

The discriminator element is designed to deal with this situation, as well as other situations, such as the inheritance hierarchy of a class. The concept of the discriminator is well understood - it's very much like a switch statement in the Java language.

For this reason, we need to add an employeeType attribute of type int in the Employee class to distinguish formal workers from temporary workers, where 1 represents formal workers and 0 represents temporary workers. Then we write a resultMap to query DepartmentAndTypeEmployees:

<resultMap id="DepartmentAndTypeEmployeesMap" extends="DepartmentMap"
           type="cn.felord.mybatis.entity.DepartmentAndTypeEmployees">
    <collection property="regularEmployees" ofType="cn.felord.mybatis.entity.RegularEmployee">
        <discriminator javaType="int" column="employee_type">
            <case value="1">
                <id column="employee_id" property="employeeId"/>
                <result column="employee_name" property="employeeName"/>
                <result column="employee_type" property="employeeType"/>
                <result column="level" property="level"/>
                <result column="job_number" property="jobNumber"/>
            </case>
        </discriminator>
    </collection>
    <collection property="temporaryEmployees" ofType="cn.felord.mybatis.entity.TemporaryEmployee">
        <discriminator javaType="int" column="employee_type">
            <case value="0">
                <id column="employee_id" property="employeeId"/>
                <result column="employee_name" property="employeeName"/>
                <result column="employee_type" property="employeeType"/>
                <result column="company_no" property="companyNo"/>
            </case>
        </discriminator>
    </collection>
</resultMap>

Remember to first declare two lists of DepartmentAndTypeEmployees, and then use the discriminator tag inside the collection tag.

It's easy to make the following mistakes. The following writing method can't meet the above requirements:

<resultMap id="DepartmentAndTypeEmployeesMap" extends="DepartmentMap"
               type="cn.felord.mybatis.entity.DepartmentAndTypeEmployees">
        <discriminator javaType="int" column="employee_type">
            <case value="1">
                <collection property="regularEmployees" ofType="cn.felord.mybatis.entity.RegularEmployee">
                    <!--ellipsis-->
                </collection>
            </case>
            <case value="0">
                <collection property="temporaryEmployees" ofType="cn.felord.mybatis.entity.TemporaryEmployee">
                    <!--ellipsis-->
                </collection>
            </case>
        </discriminator>
    </resultMap>

This means: when the employee in the data is found_ When type = 1, create a new list < regularemployee > and put the data in it. Each time, a new list < regularemployee > will be created_ Same for type = 0. This will return a list < departmentandtypeemployees >.

3. Summary

resultMap can meet the needs of most business scenarios for data mapping. Today, we have explained some usage of resultMap in Mybatis. In fact, there are some useful properties of resultMap, which will not be explained here for the reason of length. You can read the official documents of Mybatis. However, please note that although resultMap is powerful, it must be used reasonably. Too complex cascading will affect later maintenance and performance. For example, when one to many mapping, if the number of data entries of one party is too large, memory consumption and read-write performance will be increased. I hope today's article will help you to use resultMap. Please pay more attention to more timely technical information

Posted by neoson on Fri, 12 Jun 2020 00:27:50 -0700