【MyBatis】MyBatis多表操作

MyBatis多表操作

前言

在前面的两个小节里,我们已经初步接触到MyBatis,并且通过MyBatis实现了单表的增删改查操作,但在实际开发过程中,经常遇到的是多表之间的操作,MyBatis在多表操作方面也提供非常方便的工具用于将结果集映射到对象中,这一节,我们将详细学习这一部分。

多表操作

由于本节涉及到多表操作,在前面建立的数据表明显不符合,所以这里我们需要再建立一些表以及插入一些数据

本节所使用的表以及数据均来自刘增辉老师的《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 '创建时间',
  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 '创建人',
  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(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 '用户角色关联表';

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
    (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, '系统日志', '/logs'),
    (4, '人员维护', '/persons'),
    (5, '单位维护', '/companies');

insert sys_role_privilege
  values (1, 1), (1, 3), (1, 2), (2, 4), (2, 5);

对应的实体类根据数据库的字段建立就好了。

关于每个表的单表操作,在前面一个小节已经研究过了,所以在这个小节里,就不演示单表的操作了。

多表操作,本质上其实就是连接多个表,然后查询出数据,根据关联对象之间的关系,又可以分为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中的resultMapassocation以及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中的associationcollection,可以实现一对一,一对多查询中结果的自动封装,而通过discriminator则可以根据不同的数值来选择返回不同的resultMap,通过resultMap中的extends属性,可以复用一个已经存在的resultMap,通过多个resultMap的复用,可以极大地提高代码的复用率,使得代码更加简洁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值