1. 前言
resultMap
元素是 MyBatis 中最重要最強(qiáng)大的元素圃郊。它可以讓你從 90% 的 JDBCResultSets
數(shù)據(jù)提取代碼中解放出來案怯,并在一些情形下允許你進(jìn)行一些 JDBC 不支持的操作夹囚。實(shí)際上养渴,在為一些比如連接的復(fù)雜語句編寫映射代碼的時(shí)候介劫,一份resultMap
能夠代替實(shí)現(xiàn)同等功能的數(shù)千行代碼包归。ResultMap 的設(shè)計(jì)思想是苇经,對(duì)簡單的語句做到零配置赘理,對(duì)于復(fù)雜一點(diǎn)的語句,只需要描述語句之間的關(guān)系就行了扇单。
resultMap
可以將查詢到的復(fù)雜數(shù)據(jù)商模,比如多張表的數(shù)據(jù)、一對(duì)一映射蜘澜、一對(duì)多映射等復(fù)雜關(guān)系聚合到一個(gè)結(jié)果集當(dāng)中施流。日常的業(yè)務(wù)開發(fā)通常都會(huì)和它打交道,今天就對(duì) resultMap
進(jìn)行一個(gè)詳細(xì)講解鄙信。
2. resultMap
接下來我們來看看 resultMap
是如何進(jìn)行映射的瞪醋。
2.1 Getter/Setter 注入
我們聲明一個(gè)數(shù)據(jù)庫對(duì)應(yīng)的實(shí)體類:
/**
* @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;
}
那么它對(duì)應(yīng)的 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="對(duì)應(yīng)映射的實(shí)體">
<id column="數(shù)據(jù)庫主鍵字段名或者別名,使用它提高整體性能" property="對(duì)應(yīng)實(shí)體屬性"/>
<result column="數(shù)據(jù)庫字段名或者別名" property="對(duì)應(yīng)實(shí)體屬性"/>
</resultMap>
</mapper>
以上方式是通過 Getter 和 Setter 方法進(jìn)行注入装诡,也就是實(shí)體類必須有無參構(gòu)造银受,對(duì)應(yīng)屬性必須有Getter 和 Setter 方法践盼。
2.2 構(gòu)造注入
Getter 和 Setter 方法進(jìn)行注入是我們最常用的方式。但是 Mybatis 同樣支持構(gòu)造注入宾巍,如果 Employee
存在如下構(gòu)造方法:
public Employee(String employeeId, String employeeName, Integer employeeType) {
this.employeeId = employeeId;
this.employeeName = employeeName;
this.employeeType = employeeType;
}
那么對(duì)應(yīng)的 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>
細(xì)心的同學(xué)發(fā)現(xiàn)這里并沒有 property
屬性咕幻,其實(shí)當(dāng)你不聲明property
屬性時(shí)會(huì)按照構(gòu)造方法的參數(shù)列表順序進(jìn)行注入。
在 Mybatis 3.4.3 引入了 name
屬性后我們就可以打亂 constructor
標(biāo)簽內(nèi)的 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"/>
<!-- 你可以不按參數(shù)列表順序添加-->
<arg column="employee_type" javaType="Integer" name="employeeType"/>
<arg column="employee_name" javaType="String" name="employeeName"/>
</constructor>
</resultMap>
</mapper>
2.3 繼承關(guān)系
像 Java 中的類一樣肄程,resultMap
也是可以繼承的。下面是兩個(gè)有繼承關(guān)系的 Java 類:
那么 RegularEmployee
的 resultMap
就可以這么寫:
<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 的繼承關(guān)鍵字一樣使用 extends
來進(jìn)行繼承确丢。
2.4 一對(duì)一關(guān)聯(lián)
明眼人會(huì)看出來 2.3 最后一個(gè) resultMap
示例中有一個(gè) association
標(biāo)簽绷耍。這個(gè)用來做什么用呢吐限?打個(gè)比方鲜侥,每一個(gè)正式員工 RegularEmployee
會(huì)對(duì)應(yīng)一個(gè)部門 Department
,業(yè)務(wù)中會(huì)有把這種 一對(duì)一 關(guān)系查詢出來的需求诸典。所以 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="對(duì)應(yīng)的Java類型">
<id column="department_id" property="departmentId"/>
<result column="department_name" property="departmentName"/>
<result column="department_level" property="departmentLevel"/>
</association>
</resultMap>
association
可以繼續(xù)嵌套下去,有可能關(guān)聯(lián)的對(duì)象中還有一對(duì)一關(guān)系狐粱。
2.5 一對(duì)多關(guān)聯(lián)
有一對(duì)一關(guān)聯(lián)舀寓,自然會(huì)有一對(duì)多關(guān)聯(lián)。我們反客為主肌蜻,一個(gè)部門有多個(gè)員工互墓,我們可能需要查詢一個(gè)部門的信息以及所有員工的信息裝載到 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
關(guān)鍵字來處理一對(duì)多映射關(guān)系:
<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 鑒別器
大家都知道蒋搜,員工并不都是正式工篡撵,還有臨時(shí)工。有時(shí)候我們也期望能夠?qū)⑦@兩種區(qū)分開來豆挽,至于原因你懂的育谬。不深入討論這個(gè)問題了。就這個(gè)需求而言我們的映射關(guān)系又復(fù)雜了帮哈,我們需要根據(jù)某個(gè)條件來判斷哪條數(shù)據(jù)是正式工膛檀,哪條數(shù)據(jù)是臨時(shí)工,然后分別裝入下面這個(gè)實(shí)體類的 regularEmployees
娘侍、temporaryEmployees
中咖刃。
/**
* @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
)元素就是被設(shè)計(jì)來應(yīng)對(duì)這種情況的,另外也能處理其它情況憾筏,例如類的繼承層次結(jié)構(gòu)僵缺。 鑒別器的概念很好理解——它很像 Java 語言中的 switch 語句。
為此我們需要在 Employee
類中增加一個(gè) int
類型的 employeeType
屬性來區(qū)分正式工和臨時(shí)工踩叭,其中 1
代表正式工磕潮,而 0
代表臨時(shí)工翠胰。然后我們來編寫查詢 DepartmentAndTypeEmployees
的 resultMap
:
<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
的兩個(gè) List
,然后在 collection
標(biāo)簽內(nèi)部使用 discriminator
標(biāo)簽自脯。
這里很容易犯以下錯(cuò)誤之景,下面的寫法雖然可以查詢出數(shù)據(jù)但是滿足不了上述需求:
<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>
這種寫法的意思是:當(dāng)發(fā)現(xiàn)該條數(shù)據(jù)中 employee_type=1
時(shí),就新建一個(gè) List<RegularEmployee>
并把該條數(shù)據(jù)放進(jìn)去膏潮,每次都會(huì)新建一個(gè) List<RegularEmployee>
锻狗;當(dāng)employee_type=0
時(shí)也一樣。這樣的話最終就會(huì)返回一個(gè) List<DepartmentAndTypeEmployees>
焕参。
3. 總結(jié)
resultMap
能夠滿足大部分業(yè)務(wù)場景對(duì)于數(shù)據(jù)映射的需求轻纪,今天我們對(duì) Mybatis 中 resultMap
的一些用法進(jìn)行了講解,其實(shí) resultMap
還有一些有用的屬性叠纷,基于篇幅的原因這里不再講解刻帚,可閱讀 Mybatis 官方文檔。但是請(qǐng)注意雖然 resultMap
功能強(qiáng)大涩嚣,一定要合理使用崇众,級(jí)聯(lián)過于復(fù)雜會(huì)影響后期維護(hù)和性能。比如當(dāng)一對(duì)多映射時(shí)航厚,多的一方如果數(shù)據(jù)條數(shù)過大顷歌,會(huì)增加內(nèi)存消耗和讀寫性能。希望今天的文章對(duì)你使用 resultMap
有所幫助幔睬,更及時(shí)的技術(shù)資訊請(qǐng)多多關(guān)注:碼農(nóng)小胖哥眯漩。
本次文章的 DEMO ,可關(guān)注公眾號(hào):Felordcn 回復(fù) resultMap 獲取麻顶。
關(guān)注公眾號(hào):碼農(nóng)小胖哥赦抖,獲取更多資訊