Mybatis中強大的resultMap

前言

Mybatis中,有一個強大的功能元素resultMap。當咱們但願將JDBC ResultSets中的數據,轉化爲合理的Java對象時,你就能感覺到它的非凡之處。正如其官方所述的那樣:前端

resultMap元素是 MyBatis 中最重要最強大的元素。它可讓你從 90% 的 JDBC ResultSets 數據提取代碼中解放出來,並在一些情形下容許你進行一些 JDBC 不支持的操做。實際上,在爲一些好比鏈接的複雜語句編寫映射代碼的時候,一份 resultMap 可以代替實現同等功能的長達數千行的代碼。ResultMap 的設計思想是,對於簡單的語句根本不須要配置顯式的結果映射,而對於複雜一點的語句只須要描述它們的關係就好了。java

1、字段映射

Mybatis中,最簡單的結果映射方式,就是經過類型別名typeAliases來處理。數據庫

若是要這樣作,那麼第一步須要配置實體類包的路徑:bash

mybatis.type-aliases-package=com.xxx.entitymybatis

該路徑下的全部類,就會被註冊到TYPE_ALIASES容器。咱們在指定返回值類型的時候,就直接用別名便可。ui

好比,咱們有一個User類:this

@Data
public class User {
    private String id;
    private String username;
    private String password;
    private String address;
    private String email;
}
複製代碼

若是數據庫中表的字段與User類的屬性名稱一致,咱們就可使用resultType來返回。url

<select id="getUsers" resultType="User">
	SELECT
		u.id,
		u.username,
		u.password,
		u.address,
		u.email
	FROM
		USER u
</select>
複製代碼

固然,這是理想狀態下,屬性和字段名都徹底一致的狀況。但事實上,不一致的狀況是有的,這時候咱們的resultMap就要登場了。spa

若是User類保持不變,但SQL語句發生了變化,將id改爲了uid設計

<select id="getUsers" resultType="User">
	SELECT
		u.id as uid,
		u.username,
		u.password,
		u.address,
		u.email
	FROM
		USER u
</select>
複製代碼

那麼,在結果集中,咱們將會丟失id數據。這時候咱們就能夠定義一個resultMap,來映射不同的字段。

<resultMap id="getUserByIdMap" type="User">
	<result property="id" column="uid"></result>
</resultMap>
複製代碼

而後,咱們把上面的select語句中的resultType修改成resultMap="getUserByIdMap"

這裏面column對應的是數據庫的列名或別名;property對應的是結果集的字段或屬性。

這就是resultMap最簡單,也最基礎的用法:字段映射。

下面,咱們看看其餘幾種標籤都是怎麼應用的。

元素名稱 描述
constructor 用於在實例化類時,注入結果到構造方法中
association 關聯一個對象
collection 關聯多個對象

2、構造方法

若是你但願將結果注入構造方法裏,就能夠用到constructor元素。

好比,咱們的User類增長了一個構造方法:

public User(String id, String name) {
	this.id = id+"--------";
	this.username = name+"--------";
}
複製代碼

咱們須要在resultMap中定義constructor元素:

<resultMap id="getUserByIdMap" type="User">
	<constructor>
		<idArg column="id" name="id" javaType="string"></idArg>
		<arg column="username" name="name" javaType="string"></arg>
	</constructor>
</resultMap>
複製代碼

其中,column表明數據庫字段名稱或者別名;name則是構造方法中的參數名稱;javaType指定了參數的類型。

如你所想,這樣指定構造方法後,咱們結果集中的id和username屬性都會發生變化。

{
    "id": "1001--------",
    "username": "后羿--------",
    "password": "123456",
    "address": "北京市海淀區",
    "email": "510273027@qq.com"
}
複製代碼

3、關聯

在實際的業務中,咱們的用戶通常都會有一個角色。那麼在User類裏面通常也是以一個實體類來表示。

@Data
public class User {
    //省略用戶屬性...
	
    //角色信息
    private Role role;
}
複製代碼

咱們在查詢用戶的時候,若是也但願看到它的角色信息,咱們會這樣來寫查詢語句:

<select id="getUserById" resultType="User">
        SELECT
            u.id,
            u.username,
            u.password,
            u.address,
            u.email,
            r.id as 'role_id',
            r.name as 'role_name'
        FROM
            USER u
                LEFT JOIN user_roles ur ON u.id = ur.user_id
                LEFT JOIN role r ON r.id = ur.role_id
        where u.id=#{id}
    </select>
複製代碼

如上,就要查詢單個用戶以及用戶的角色信息。不過在這裏,咱們不能用resultType=User來返回。

畢竟,User類中只有一個Role對象,並無role_id和role_name字段屬性。

因此,咱們要使用association來關聯它們。

<resultMap id="userMap" type="User">
	<id property="id" column="id"></id>
	<result property="username" column="username"></result>
	<result property="password" column="password"></result>
	<result property="address" column="address"></result>
	<result property="email" column="email"></result>
	
	<association property="role" javaType="Role">
		<id property="id" column="role_id"></id>
		<result property="name" column="role_name"></result>
	</association>
</resultMap>
複製代碼

最後咱們就能夠將角色信息一塊顯示出來:

{
    "id": "1001",
    "username": "后羿",
    "password": "123456",
    "address": "北京市海淀區",
    "email": "510273027@qq.com",
    "role": {
        "id": "3",
        "name": "射手"
    }
}
複製代碼

事實上,若是你肯定關聯信息是一對一的狀況,有個更簡便的方法能夠替代association,咱們在本文的第五部分-自動填充關聯對象再看它是怎麼實現的。

4、集合

一、集合的嵌套結果映射

上面咱們看到一個用戶后羿,它的角色是射手;但大部分時候,咱們每一個人都不可能只擁有一種角色。因此,咱們須要將User類中的角色屬性的類型改爲List

@Data
public class User {
    //省略用戶屬性...
	
    //角色信息
    private List<Role> roles;
}
複製代碼

如今就變成了一個用戶對應多個角色,因此就不是簡單的association

由於association處理的是有一個類型的關聯;而咱們這裏是有多個類型的關聯,因此就須要用到collection屬性。

咱們總體的resultMap會變成下面這樣:

<resultMap id="userMap" type="User">
	<id property="id" column="id"></id>
	<result property="username" column="username"></result>
	<result property="password" column="password"></result>
	<result property="address" column="address"></result>
	<result property="email" column="email"></result>
	
	<collection property="roles" ofType="Role">
		<id property="id" column="role_id"></id>
		<result property="name" column="role_name"></result>
	</collection>
</resultMap>
複製代碼

這樣的話,即使你有多個角色也能夠被正確顯示:

{
    "id": "1003",
    "username": "貂蟬",
    "password": "123456",
    "address": "北京市東城區",
    "email": "510273027@qq.com",
    "roles": [
        {
            "id": "1",
            "name": "中單"
        },
        {
            "id": "2",
            "name": "打野"
        }
    ]
}
複製代碼

二、集合的嵌套 Select 查詢

在大部分業務系統中,咱們都會有一個菜單的表,好比像下面這樣,一張Menu表:

id name url parent_id
1 系統管理 0
1001 用戶管理 /user 1
1002 角色管理 /role 1
1003 單位管理 /employer 1
2 平臺監控 0
2001 系統監控 /system/monitor 2
2002 數據監控 /data/monitor 2

這裏咱們給菜單分爲兩級。咱們給前端返回菜單的時候,也是須要分級的,不可能將這7條數據平級展現。那麼,在這裏咱們的Menu實體類以下:

@Data
public class Menu {
    private String id;
    private String name;
    private String url;
    private String parent_id;
    private List<Menu> childMenu;
}
複製代碼

一級菜單,包含一個二級菜單的列表,這裏用childMenu來表示。

SQL語句中,若是沒有parent_id字段屬性,咱們就先查全部的一級菜單:

<select id="getMenus" resultMap="menusMap">
	SELECT
		m.id,
		m.name,
		m.url,
		m.parent_id
	FROM
		m_menu m
	where 1=1
	<choose>
		<when test="parent_id!=null">
			and m.parent_id = #{parent_id}
		</when>
		<otherwise>
			and m.parent_id = '0'
		</otherwise>
	</choose>
</select>
複製代碼

這個查詢語句,在不傳輸任何參數的狀況下,咱們會獲得兩條一級菜單的數據。

那麼在只調用此方法一次的狀況下,怎麼把全部的菜單信息查詢出來,並按層級展現呢?

咱們來看menusMap的定義:

<resultMap id="menusMap" type="Menu">
	<id property="id" column="id"></id>
	<result property="name" column="name"></result>
	<result property="url" column="url"></result>
	<result property="m_desc" column="m_desc"></result>
	<result property="parent_id" column="parent_id"></result>
	
	<collection property="childMenu" ofType="Menu" select="getMenus"  column="{parent_id=id}"></collection>
</resultMap>
複製代碼

重點來看collection元素:

property="childMenu" 對應的是菜單中的子級菜單列表;

ofType="Menu" 對應返回數據的類型;

select="getMenus" 指定了SELECT語句的id;

column="{parent_id=id}" 則是參數的表達式。

這個collection總體的含義能夠這樣理解:

經過getMenus這個SELECT語句來獲取一級菜單中的childMenu屬性結果;在上面的SELECT語句中,須要傳遞一個parent_id參數;這個參數的值就是一級菜單中的id

經過這種方式,咱們就能夠獲得已分級的全部菜單信息。

[
    {
        "id": "1",
        "name": "系統管理",
        "parent_id": "0",
        "childMenu": [
            {
                "id": "1001",
                "name": "用戶管理",
                "url": "/user",
                "parent_id": "1"
            },
            {
                "id": "1002",
                "name": "角色管理",
                "url": "/role",
                "parent_id": "1"
            },
            {
                "id": "1003",
                "name": "單位管理",
                "url": "/employer",
                "parent_id": "1"
            }
        ]
    },
    {
        "id": "2",
        "name": "平臺監控",
        "parent_id": "0",
        "childMenu": [
            {
                "id": "2001",
                "name": "系統監控",
                "url": "/system/monitor",
                "parent_id": "2"
            },
            {
                "id": "2002",
                "name": "數據監控",
                "url": "/data/monitor",
                "parent_id": "2"
            }
        ]
    }
]
複製代碼

5、自動填充關聯對象

咱們知道,在Mybatis解析返回值的時候。

第一步是獲取返回值類型,拿到Class對象,而後獲取構造器,設置可訪問並返回實例,而後又把它包裝成MetaObject對象。

從數據庫rs中拿到結果以後,會調用MetaObject.setValue(String name, Object value) 來填充對象。

在這過程當中,有趣的是,它會以.來分隔這個name屬性。

若是name屬性中包含.符號,就找到.符號以前的屬性名稱,把它當作一個實體對象來處理。

可能筆者在這裏描述的不夠直觀,咱們仍是來看例子。

在本文第三部分中,咱們有一個用戶對應一個角色的例子。

其中,User類定義以下:

@Data
public class User {
    //省略用戶屬性...
	
    //角色信息
    private Role role;
}
複製代碼

在這裏,咱們無需定義resultMap,直接返回resultType=User便可。不過須要把role信息的別名修改一下,重點是.符號

<select id="getUserList" resultType="User">
	SELECT
		u.id,
		u.username,
		u.password,
		u.address,
		u.email,
		r.id as 'role.id',
		r.name as 'role.name'
	FROM
		USER u
			LEFT JOIN user_roles ur ON u.id = ur.user_id
			LEFT JOIN role r ON r.id = ur.role_id
</select>
複製代碼

這樣,在Mybatis解析到role.id屬性的時候,以.符號分隔以後發現,role別名對應的是Role對象,則會先初始化Role對象,並將值賦予id屬性。

相關代碼如圖:

相關文章
相關標籤/搜索