天天看點

【MyBatis】MyBatis多表操作MyBatis多表操作

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

的複用,可以極大地提高代碼的複用率,使得代碼更加簡潔。

繼續閱讀