MyBatis多表操作
前言
在前面的兩個小節(jié)里,我們已經(jīng)初步接觸到MyBatis,并且通過MyBatis實現(xiàn)了單表的增刪改查操作,但在實際開發(fā)過程中浸遗,經(jīng)常遇到的是多表之間的操作,MyBatis在多表操作方面也提供非常方便的工具用于將結果集映射到對象中箱亿,這一節(jié)跛锌,我們將詳細學習這一部分。
多表操作
由于本節(jié)涉及到多表操作,在前面建立的數(shù)據(jù)表明顯不符合髓帽,所以這里我們需要再建立一些表以及插入一些數(shù)據(jù)
本節(jié)所使用的表以及數(shù)據(jù)均來自劉增輝老師的《MyBatis從入門到精通》
create table sys_user (
id bigint not null auto_increment comment '用戶ID',
user_name varchar(50) comment '用戶名',
user_password varchar(50) comment '密碼',
user_email varchar(50) comment '郵箱',
create_time datetime comment '創(chuàng)建時間',
primary key (id)
);
alter table sys_user comment '用戶表';
create table sys_role (
id bigint not null auto_increment comment '角色ID',
role_name varchar(50) comment '角色名',
enabled int comment '有效標志',
create_by bigint comment '創(chuàng)建人',
create_time datetime comment '創(chuàng)建時間',
primary key (id)
);
alter table sys_role comment '角色表';
create table sys_privilege (
id bigint not null auto_increment comment '權限ID',
privilege_name varchar(50) comment '權限名稱',
privilege_url varchar(200) comment '權限URL',
primary key (id)
);
alter table sys_privilege comment '權限表';
create table sys_user_role (
user_id bigint comment '用戶ID',
role_id bigint comment '角色ID'
);
alter table sys_user_role comment '用戶角色關聯(lián)表';
create table sys_role_privilege (
role_id bigint comment '角色ID',
privilege_id bigint comment '權限ID'
);
alter table sys_role_privilege comment '角色權限關聯(lián)表';
測試數(shù)據(jù)
insert into `sys_user`
values
(1, 'admin', '123456', 'admin@mybatis', '管理員', null, now()),
(1001, 'test', '123456', 'test@mybatis', '測試用戶', null, now());
insert into sys_role
values
(1, '管理員', '1', '1', now()),
(2, '普通用戶', '1', '1', now());
insert into sys_user_role values (1, 1), (1, 2), (1001, 2);
insert sys_privilege
values
(1, '用戶管理', '/users'),
(2, '角色管理', '/roles'),
(3, '系統(tǒng)日志', '/logs'),
(4, '人員維護', '/persons'),
(5, '單位維護', '/companies');
insert sys_role_privilege
values (1, 1), (1, 3), (1, 2), (2, 4), (2, 5);
對應的實體類根據(jù)數(shù)據(jù)庫的字段建立就好了菠赚。
關于每個表的單表操作,在前面一個小節(jié)已經(jīng)研究過了郑藏,所以在這個小節(jié)里衡查,就不演示單表的操作了。
多表操作必盖,本質(zhì)上其實就是連接多個表拌牲,然后查詢出數(shù)據(jù),根據(jù)關聯(lián)對象之間的關系歌粥,又可以分為1對1操作塌忽,1對多操作,多對多操作(本質(zhì)上而言其實也是1對多)失驶,所以接下來土居,我們分兩個部分來看如何通過MyBatis來操作
1對1操作
假設我們要根據(jù)用戶的ID查詢出用戶的角色,并且假定一個用戶只有一個角色(當然嬉探,實際上不止)擦耀,這里以1001號用戶為例,其在數(shù)據(jù)庫中也僅有一個角色涩堤,所以符合我們操作的要求眷蜓。
為了能通過MyBatis自動封裝,我們在SysUser
中增加一個字段SysRole
public class SysUser {
// 其他字段與數(shù)據(jù)庫保持一致即可
private SysRole role;
// set() get() toString()
}
在查詢操作中定躏,我們可以通過下面的方式來獲取數(shù)據(jù)
<select id="selectUserAndRoleById" resultType="domain.SysUser">
select
u.id,
u.user_name userName,
u.user_password userPassword,
u.user_email userEmail,
u.create_time createTime,
<!--
注意從這里開始的別名是"role.XXX"账磺,因為字段中是role
為了能夠自動注入汉矿,所以需要采用obj.attr的形式捧书,
如果有多級對象塘雳,則是 a.b.c這種形式
-->
r.id "role.id",
r.role_name "role.roleName",
r.enabled "role.enable",
r.create_by "role.createBy",
r.create_time "role.createTime"
from sys_user u
join sys_user_role ur on u.id = ur.user_id
join sys_role r on r.id = ur.role_id
where u.id = #{id}
</select>
上面的實現(xiàn)方式從結果來看是沒有問題的,但是從工程的角度來講碧聪,其實不太好,尤其是當存在多個不同類型的查詢液茎,比如根據(jù)ID逞姿,根據(jù)名稱,根據(jù)郵箱地址等捆等,我們需要編寫多份的代碼滞造,并且其中的select部分基本上是不變的,也就是帶來非常明顯的冗余了栋烤。
更好地解決方案是使用MyBatis中的resultMap
谒养,通過resultMap
來封裝,可以實現(xiàn)代碼復用的目的
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<result property="role.id" column="r.id"/>
<!--其他的字段-->
</resultMap>
<select id="selectUserAndRoleById" resultMap="userRoleMap">
...
這里根據(jù)對應的字段調(diào)整一下明郭,只需要能正確映射就行
</select>
不過上面的內(nèi)容語義不明顯买窟,更好的方式是使用resutlMap
的<association>
標簽來關聯(lián)對象丰泊,如下
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<!--
注意這里,使用的是association始绍,association的使用跟resultMap是類似的
并且使用多了columnPrefix屬性瞳购,為了區(qū)分來自不同表的字段,
如果是多級的嵌套亏推,則需要指定多級学赛,如 role_pri_XXX,一個層次的columnPrefix會
去過濾每一次匹配的前綴
當然径簿,在查詢的時候也需要將對應的前綴標注出來
-->
<association property="role" javaType="domain.SysRole" columnPrefix="role_">
<result property="id" column="id"/>
<!--其他的字段-->
</association>
</resultMap>
<!--注意下面的內(nèi)容 role_也即是columnPrefix=""中指定的字段-->
<select>
r.id role_id,
r.role_name role_role_name,
r.enabled role_enabled,
r.create_by role_create_by,
r.create_time role_create_time
</select>
通過上面的方式罢屈,當需要的時候,就可以直接指定查詢的resultMap="userRoleMap"
即可篇亭,已經(jīng)減少了一部分的重復操作了缠捌,但是,上面的方式仍然不是合適的译蒂,因為既然有user對應的map曼月,那實際上將role對應的字段也封裝到map中,然后直接調(diào)用即可柔昼,這樣哑芹,多個使用到role的地方都可以直接使用了
首先在SysRoleMapper.xml定義對應的roleMap,當然捕透,放在其他的mapper里也是可以聪姿,但是放在SysRoleMapper.xml是最合適的
<mapper namespace="mapper.SysRoleMapper">
<resultMap id="roleMapper" type="domain.SysRole">
<id property="id" column="id"/>
<!--其他的字段-->
</resultMap>
</mapper>
整理完之后的userRoleMap
內(nèi)容如下
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<!--這里使用resultMap來指定其他的resultMap,如果不在本文件乙嘀,則使用全限定名-->
<association property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.roleMapper"/>
</resultMap>
經(jīng)過上面的整理之后末购,現(xiàn)在的整體結構就變得非常靈活了,特別是當我們需要組合多個對象的時候虎谢,通過這種方式盟榴,可以實現(xiàn)只需要定義一個resultMap,然后在多處使用
1對多操作
有了上面封裝1對1的操作過程作為基礎婴噩,實現(xiàn)一對多就容易很多了擎场,只需要將<association>
替換為<collection>
即可,當然几莽,由于上面為了方便迅办,直接在SysUser中定義了一個SysRole對象,但實際上我們知道章蚣,一個用戶是可以對應多個角色的站欺,所以,在SysUser中應該定義的是一個SysRole容器,比如list或者set等镊绪,也就是實際上1對多的操作啦
<resultMap id="userRoleMap" type="domain.SysUser">
<!--注意這里-->
<collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.roleMapper"/>
</resultMap>
可以看到匀伏,因為為role對象定義roleMap,所以蝴韭,當改動userRole時够颠,其他的內(nèi)容完全不需要改動
一個完整的例子
在上面的兩步操作中,我們已經(jīng)充分體驗到了MyBatis中的resultMap
榄鉴、assocation
以及collection
提供的便利履磨,下面我們通過完整的例子,來加深對其認識
這里通過用戶ID庆尘,獲取其所有的角色以及所有角色對應的權限
將對應的實體類調(diào)整為如下
SysUser
public class SysUser {
// 一個用戶可能對應多個角色
private List<SysRole> role;
}
SysRole
public class SysRole {
// 一個角色可能有多個權限
private List<SysPrivilege> privilegeList;
}
然后為每個實體類編寫對應的resultMap
剃诅,這個參考上面的編寫方式就行啦,這里就不貼代碼了
接下來組合多個resultMap驶忌,這里我們采用自底向上的方式
<resultMap id="rolePrivilegeMap" type="domain.SysRole">
<id property="id" column="id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<!--組裝privilegeMap-->
<collection property="privilegeList" columnPrefix="pri_" resultMap="mapper.SysUserMapper.privilegeMap"/>
</resultMap>
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<!--組裝rolePrivilegeMap-->
<collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.rolePrivilegeMap"/>
</resultMap>
通過上面的兩層組裝之后矛辕,當我們需要使用的時候,就可以直接指定resultMap="userRoleMap"
即可啦
關于ResultMap付魔,還有一個小點需要注意聊品,如果查詢的數(shù)據(jù)中不包含某些字段,而resultMap中有該字段時几苍,MyBatis會忽略該字段翻屈,所以,一個resultMap可以復用在其他場景妻坝,即使查詢的字段跟resultMap中的字段不完全匹配伸眶,只要resultMap中包含我們需要的字段即可
discriminator
在ResultMap中,還有一個<discriminator>
刽宪,該標簽的用途在于厘贼,根據(jù)不同的字段值進行分類,比如在上面的案例中纠屋,有一些角色是啟用的涂臣,有一些是不允許啟用的盾计,那么售担,對于不允許啟用的角色,我們就不需要獲取其角色以及權限信息署辉,所以族铆,這時,可以通過discriminator來實現(xiàn)根據(jù)不同的值來映射到不同的resutMap中哭尝,如下面所示
<resultMap id="userMap" type="domain.SysUser">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<resultMap id="userRoleMap" type="domain.SysUser">
<!--根據(jù)role_enabled的狀態(tài)來選擇不同的查詢-->
<discriminator javaType="int" column="role_enabled">
<case value="1" resultMap="userRoleMapSelect" />
<!--只獲取用戶的基本信息哥攘,不獲取角色以及權限信息-->
<case value="0" resultMap="userMap"/>
</discriminator>
</resultMap>
<!--直接繼承userMap,可以避免編寫過多的result標簽-->
<resultMap id="userRoleMapSelect" type="domain.SysUser" extends="userMap">
<collection property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.rolePrivilegeMap"/>
</resultMap>
通過上面的例子,可以看到discriminator
的強大之處了逝淹,在使用discriminator
的時候需要注意耕姊,discriminator
是作用在當前的resultMap的,也就是說栅葡,discriminator
中的resultMap
封裝的是當前的result
中的內(nèi)容茉兰,而不是決定子查詢中的內(nèi)容
總結
本小節(jié)主要學習了MyBatis中的多表查詢,通過MyBatis中的resultMap
以及resultMap
中的association
欣簇、collection
规脸,可以實現(xiàn)一對一,一對多查詢中結果的自動封裝熊咽,而通過discriminator
則可以根據(jù)不同的數(shù)值來選擇返回不同的resultMap
莫鸭,通過resultMap
中的extends
屬性,可以復用一個已經(jīng)存在的resultMap
横殴,通過多個resultMap
的復用被因,可以極大地提高代碼的復用率,使得代碼更加簡潔衫仑。