Mybatis 強大的結果集對映器resultMap

碼農小胖哥發表於2020-05-24

1. 前言

resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來,並在一些情形下允許你進行一些 JDBC 不支援的操作。實際上,在為一些比如連線的複雜語句編寫對映程式碼的時候,一份 resultMap 能夠代替實現同等功能的數千行程式碼。ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。

resultMap 可以將查詢到的複雜資料,比如多張表的資料、一對一對映、一對多對映等複雜關係聚合到一個結果集當中。日常的業務開發通常都會和它打交道,今天就對 resultMap 進行一個詳細講解。

2. resultMap

接下來我們來看看 resultMap 是如何進行對映的。

2.1 Getter/Setter 注入

我們宣告一個資料庫對應的實體類:

/**
 * @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;
}

那麼它對應的 resultMap 為:

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

我們來解釋這些配置的屬性:

<mapper namespace="全域性唯一的名稱空間">
    <resultMap id="本namespace下唯一" type="對應對映的實體">
        <id column="資料庫主鍵欄位名或者別名,使用它提高整體效能" property="對應實體屬性"/>
        <result column="資料庫欄位名或者別名" property="對應實體屬性"/>
    </resultMap>
</mapper>

以上方式是通過 GetterSetter 方法進行注入,也就是實體類必須有無參構造,對應屬性必須有GetterSetter 方法。

2.2 構造注入

GetterSetter 方法進行注入是我們最常用的方式。但是 Mybatis 同樣支援構造注入,如果 Employee 存在如下構造方法:

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

那麼對應的 resultMap 可以這樣寫:

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

細心的同學發現這裡並沒有 property 屬性,其實當你不宣告property 屬性時會按照構造方法的引數列表順序進行注入。

Mybatis 3.4.3 引入了 name 屬性後我們就可以打亂 constructor 標籤內的 arg 元素的順序了。

<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"/>
            <!-- 你可以不按引數列表順序新增-->
            <arg column="employee_type" javaType="Integer" name="employeeType"/>
            <arg column="employee_name" javaType="String" name="employeeName"/>
        </constructor>
    </resultMap>
</mapper>

2.3 繼承關係

Java 中的類一樣,resultMap 也是可以繼承的。下面是兩個有繼承關係的 Java 類:

那麼 RegularEmployeeresultMap 就可以這麼寫:

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

Java 的繼承關鍵字一樣使用 extends 來進行繼承。

2.4 一對一關聯

明眼人會看出來 2.3 最後一個 resultMap 示例中有一個 association 標籤。這個用來做什麼用呢?打個比方,每一個正式員工 RegularEmployee會對應一個部門 Department,業務中會有把這種 一對一 關係查詢出來的需求。所以 association 就派上了用場。

<resultMap id="RegularEmployeeMap" extends="EmployeeMap" type="cn.felord.mybatis.entity.RegularEmployee">
    <result column="level" property="level"/>
    <result column="job_number" property="jobNumber"/>
    <association property="屬性名稱" javaType="對應的Java型別">
        <id column="department_id" property="departmentId"/>
        <result column="department_name" property="departmentName"/>
        <result column="department_level" property="departmentLevel"/>
    </association>
</resultMap>

association 可以繼續巢狀下去,有可能關聯的物件中還有一對一關係。

2.5 一對多關聯

有一對一關聯,自然會有一對多關聯。我們反客為主,一個部門有多個員工,我們可能需要查詢一個部門的資訊以及所有員工的資訊裝載到 DepartmentAndEmployeeList中去。

/**
 * @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;
    }
}

我們可以在 resultMap 中使用 collection 關鍵字來處理一對多對映關係:

<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 鑑別器

大家都知道,員工並不都是正式工,還有臨時工。有時候我們也期望能夠將這兩種區分開來,至於原因你懂的。不深入討論這個問題了。就這個需求而言我們的對映關係又複雜了,我們需要根據某個條件來判斷哪條資料是正式工,哪條資料是臨時工,然後分別裝入下面這個實體類的 regularEmployeestemporaryEmployees中。

/**
 * @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
}

鑑別器(discriminator)元素就是被設計來應對這種情況的,另外也能處理其它情況,例如類的繼承層次結構。 鑑別器的概念很好理解——它很像 Java 語言中的 switch 語句。

為此我們需要在 Employee 類中增加一個 int型別的 employeeType屬性來區分正式工和臨時工,其中 1代表正式工,而 0代表臨時工。然後我們來編寫查詢 DepartmentAndTypeEmployeesresultMap :

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

切記一定是先宣告 DepartmentAndTypeEmployees的兩個 List ,然後在 collection 標籤內部使用 discriminator 標籤。

這裡很容易犯以下錯誤,下面的寫法雖然可以查詢出資料但是滿足不了上述需求

<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">
                    <!--省略-->
                </collection>
            </case>
            <case value="0">
                <collection property="temporaryEmployees" ofType="cn.felord.mybatis.entity.TemporaryEmployee">
                    <!--省略-->
                </collection>
            </case>
        </discriminator>
    </resultMap>

這種寫法的意思是:當發現該條資料中 employee_type=1 時,就新建一個 List<RegularEmployee> 並把該條資料放進去,每次都會新建一個 List<RegularEmployee> ;當employee_type=0 時也一樣。這樣的話最終就會返回一個 List<DepartmentAndTypeEmployees>

3. 總結

resultMap 能夠滿足大部分業務場景對於資料對映的需求,今天我們對 MybatisresultMap 的一些用法進行了講解,其實 resultMap 還有一些有用的屬性,基於篇幅的原因這裡不再講解,可閱讀 Mybatis 官方文件。但是請注意雖然 resultMap 功能強大,一定要合理使用,級聯過於複雜會影響後期維護和效能。比如當一對多對映時,多的一方如果資料條數過大,會增加記憶體消耗和讀寫效能。希望今天的文章對你使用 resultMap 有所幫助,更及時的技術資訊請多多關注:碼農小胖哥

本次文章的 DEMO ,可關注公眾號:Felordcn 回覆 resultMap 獲取。

關注公眾號:Felordcn 獲取更多資訊

個人部落格:https://felord.cn

相關文章