MyBatis多表操作
前言
在前面的兩個小節裡,我們已經初步接觸到MyBatis,并且通過MyBatis實作了單表的增删改查操作,但在實際開發過程中,經常遇到的是多表之間的操作,MyBatis在多表操作方面也提供非常友善的工具用于将結果集映射到對象中,這一節,我們将詳細學習這一部分。
多表操作
由于本節涉及到多表操作,在前面建立的資料表明顯不符合,是以這裡我們需要再建立一些表以及插入一些資料
本節所使用的表以及資料均來自劉增輝老師的《MyBatis從入門到精通》
create table sys_user (
id bigint not null auto_increment comment '使用者ID',
user_name varchar() comment '使用者名',
user_password varchar() comment '密碼',
user_email varchar() comment '郵箱',
create_time datetime comment '建立時間',
primary key (id)
);
alter table sys_user comment '使用者表';
create table sys_role (
id bigint not null auto_increment comment '角色ID',
role_name varchar() comment '角色名',
enabled int comment '有效标志',
create_by bigint comment '建立人',
create_time datetime comment '建立時間',
primary key (id)
);
alter table sys_role comment '角色表';
create table sys_privilege (
id bigint not null auto_increment comment '權限ID',
privilege_name varchar() comment '權限名稱',
privilege_url varchar() 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 '使用者角色關聯表';
create table sys_role_privilege (
role_id bigint comment '角色ID',
privilege_id bigint comment '權限ID'
);
alter table sys_role_privilege comment '角色權限關聯表';
測試資料
insert into `sys_user`
values
(, 'admin', '123456', '[email protected]', '管理者', null, now()),
(, 'test', '123456', '[email protected]', '測試使用者', null, now());
insert into sys_role
values
(, '管理者', '1', '1', now()),
(, '普通使用者', '1', '1', now());
insert into sys_user_role values (, ), (, ), (, );
insert sys_privilege
values
(, '使用者管理', '/users'),
(, '角色管理', '/roles'),
(, '系統日志', '/logs'),
(, '人員維護', '/persons'),
(, '機關維護', '/companies');
insert sys_role_privilege
values (, ), (, ), (, ), (, ), (, );
對應的實體類根據資料庫的字段建立就好了。
關于每個表的單表操作,在前面一個小節已經研究過了,是以在這個小節裡,就不示範單表的操作了。
多表操作,本質上其實就是連接配接多個表,然後查詢出資料,根據關聯對象之間的關系,又可以分為1對1操作,1對多操作,多對多操作(本質上而言其實也是1對多),是以接下來,我們分兩個部分來看如何通過MyBatis來操作
1對1操作
假設我們要根據使用者的ID查詢出使用者的角色,并且假定一個使用者隻有一個角色(當然,實際上不止),這裡以1001号使用者為例,其在資料庫中也僅有一個角色,是以符合我們操作的要求。
為了能通過MyBatis自動封裝,我們在
SysUser
中增加一個字段
SysRole
public class SysUser {
// 其他字段與資料庫保持一緻即可
private SysRole role;
// set() get() toString()
}
在查詢操作中,我們可以通過下面的方式來擷取資料
<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>
上面的實作方式從結果來看是沒有問題的,但是從工程的角度來講,其實不太好,尤其是當存在多個不同類型的查詢,比如根據ID,根據名稱,根據郵箱位址等,我們需要編寫多份的代碼,并且其中的select部分基本上是不變的,也就是帶來非常明顯的備援了。
更好地解決方案是使用MyBatis中的
resultMap
,通過
resultMap
來封裝,可以實作代碼複用的目的
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<result property="role.id" column="r.id"/>
<!--其他的字段-->
</resultMap>
<select id="selectUserAndRoleById" resultMap="userRoleMap">
...
這裡根據對應的字段調整一下,隻需要能正确映射就行
</select>
不過上面的内容語義不明顯,更好的方式是使用
resutlMap
的
<association>
标簽來關聯對象,如下
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<!--
注意這裡,使用的是association,association的使用跟resultMap是類似的
并且使用多了columnPrefix屬性,為了區分來自不同表的字段,
如果是多級的嵌套,則需要指定多級,如 role_pri_XXX,一個層次的columnPrefix會
去過濾每一次比對的字首
當然,在查詢的時候也需要将對應的字首标注出來
-->
<association property="role" javaType="domain.SysRole" columnPrefix="role_">
<result property="id" column="id"/>
<!--其他的字段-->
</association>
</resultMap>
<!--注意下面的内容 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"
即可,已經減少了一部分的重複操作了,但是,上面的方式仍然不是合适的,因為既然有user對應的map,那實際上将role對應的字段也封裝到map中,然後直接調用即可,這樣,多個使用到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
内容如下
<resultMap id="userRoleMap" type="domain.SysUser">
<id property="id" column="id"/>
<!--其他的字段-->
<!--這裡使用resultMap來指定其他的resultMap,如果不在本檔案,則使用全限定名-->
<association property="role" columnPrefix="role_" resultMap="mapper.SysRoleMapper.roleMapper"/>
</resultMap>
經過上面的整理之後,現在的整體結構就變得非常靈活了,特别是當我們需要組合多個對象的時候,通過這種方式,可以實作隻需要定義一個resultMap,然後在多處使用
1對多操作
有了上面封裝1對1的操作過程作為基礎,實作一對多就容易很多了,隻需要将
<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時,其他的内容完全不需要改動
一個完整的例子
在上面的兩步操作中,我們已經充分體驗到了MyBatis中的
resultMap
、
assocation
以及
collection
提供的便利,下面我們通過完整的例子,來加深對其認識
這裡通過使用者ID,擷取其所有的角色以及所有角色對應的權限
将對應的實體類調整為如下
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,還有一個小點需要注意,如果查詢的資料中不包含某些字段,而resultMap中有該字段時,MyBatis會忽略該字段,是以,一個resultMap可以複用在其他場景,即使查詢的字段跟resultMap中的字段不完全比對,隻要resultMap中包含我們需要的字段即可
discriminator
在ResultMap中,還有一個
<discriminator>
,該标簽的用途在于,根據不同的字段值進行分類,比如在上面的案例中,有一些角色是啟用的,有一些是不允許啟用的,那麼,對于不允許啟用的角色,我們就不需要擷取其角色以及權限資訊,是以,這時,可以通過discriminator來實作根據不同的值來映射到不同的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">
<!--根據role_enabled的狀态來選擇不同的查詢-->
<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
中的内容,而不是決定子查詢中的内容
總結
本小節主要學習了MyBatis中的多表查詢,通過MyBatis中的
resultMap
以及
resultMap
中的
association
、
collection
,可以實作一對一,一對多查詢中結果的自動封裝,而通過
discriminator
則可以根據不同的數值來選擇傳回不同的
resultMap
,通過
resultMap
中的
extends
屬性,可以複用一個已經存在的
resultMap
,通過多個
resultMap
的複用,可以極大地提高代碼的複用率,使得代碼更加簡潔。