Mybatis 強(qiáng)大的結(jié)果集映射器resultMap

1. 前言

resultMap 元素是 MyBatis 中最重要最強(qiáng)大的元素圃郊。它可以讓你從 90% 的 JDBC ResultSets 數(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>

以上方式是通過 GetterSetter 方法進(jìn)行注入装诡,也就是實(shí)體類必須有無參構(gòu)造银受,對(duì)應(yīng)屬性必須有GetterSetter 方法践盼。

2.2 構(gòu)造注入

GetterSetter 方法進(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 類:

那么 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 的繼承關(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í)工翠胰。然后我們來編寫查詢 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的兩個(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ì) MybatisresultMap 的一些用法進(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)小胖哥赦抖,獲取更多資訊

個(gè)人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市澈蚌,隨后出現(xiàn)的幾起案子摹芙,更是在濱河造成了極大的恐慌,老刑警劉巖宛瞄,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浮禾,死亡現(xiàn)場離奇詭異,居然都是意外死亡份汗,警方通過查閱死者的電腦和手機(jī)盈电,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杯活,“玉大人匆帚,你說我怎么就攤上這事∨跃” “怎么了吸重?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵互拾,是天一觀的道長。 經(jīng)常有香客問我嚎幸,道長颜矿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任嫉晶,我火速辦了婚禮骑疆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘替废。我一直安慰自己箍铭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布椎镣。 她就那樣靜靜地躺著诈火,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衣陶。 梳的紋絲不亂的頭發(fā)上柄瑰,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天闸氮,我揣著相機(jī)與錄音剪况,去河邊找鬼。 笑死蒲跨,一個(gè)胖子當(dāng)著我的面吹牛译断,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播或悲,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼孙咪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巡语?” 一聲冷哼從身側(cè)響起翎蹈,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎男公,沒想到半個(gè)月后荤堪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枢赔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年澄阳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踏拜。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碎赢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出速梗,到底是詐尸還是另有隱情肮塞,我是刑警寧澤襟齿,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站枕赵,受9級(jí)特大地震影響蕊唐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烁设,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一替梨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧装黑,春花似錦副瀑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疚颊,卻和暖如春狈孔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背材义。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工均抽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人其掂。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓油挥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親款熬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子深寥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355