MyBatis 的高级结果映射

为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化

MyBatis 的高级结果映射主要是针对一对一,一对多的表关系结果映射。

一对一结果映射

在使用多表联查的时候,想要将联查出来的另一个表的数据映射到主表映射对象上,需要在主表的实体对象中增加一个从表的对象属性,并添加 set、get 方法。

实现一对一映射共有三种方式:

  • SQL 别名方式实现映射
  • SQL 别名与 resultMap 标签配合实现映射
  • association 标签实现映射

通过 SQL 别名方式实现映射

示例代码如下:

// 实体类
public class SysUser {
    // 从表对象属性
    private SysRole role;
    public SysRole getRole() {
        return role;
    }

    public void setRole(SysRole role) {
        this.role = role;
    }
}
XMl SQL :

<select id="selectUserInfoAndRoleInfo" resultType="SysUser">
    select
        user.id,user.user_name,user.user_email,
        role.id "role.id",role.role_name "role.roleName",role.enabled "role.enabled"
    from sys_user user
    inner join sys_user_role userRole on user.id = userRole.user_id
    inner join sys_role role on userRole.role_id = role.id
    where
    1 = 1
    <if test="userId != null" >
    	and user.id = #{userId}
    </if>
</select>
SysUserMapper.class

public interface SysUserMapper {
    SysUser selectUserInfoAndRoleInfo(@Param("userId") Integer id);
}

之后,调试代码便可正常查询。


SQL 别名与 resultMap 标签配合实现映射

相比 通过 SQL 别名方式实现映射 与 resultMap 标签配合 方式只需要改动一下 XML 中的查询 SQL 的返回类型和 resultMap 标签中的值映射,如下:

<resultMap id="UserEntity" type="SysUser" >
    <id column="id" property="id"/>
    <result property="userName" column="user_name" />
    <result property="userPassword" column="user_password" />
    <result property="userEmail" column="user_email" />
    <result property="userInfo" column="user_info" />
    <result property="headImg" column="head_img" jdbcType="BLOB" />
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
    <result property="role.id" column="role_id" />
    <result property="role.roleName" column="roleName" />
    <result property="role.enabled" column="enabled" />
    <result property="role.createBy" column="role.createBy" />
    <result property="role.createTime" column="role.createTime" />
</resultMap>



<select id="selectUserInfoAndRoleInfoUseTag" resultMap="UserEntity">
    select
        user.id,
    	user.user_name,
    	user.user_email,
        role.id "role_id",
    	role.role_name "roleName",
    	role.enabled "enabled",
    	role.create_By "role.createby",
    	role.create_time "role.createTime"
    from sys_user user
    inner join sys_user_role userRole on user.id = userRole.user_id
    inner join sys_role role on userRole.role_id = role.id
    where
    1 = 1
    <if test="userId != null" >
    	and user.id = #{userId}
    </if>
</select>

之后,增加一个接口方法,方法名与 select 标签的 id 对应,便可调用测试了。

association 标签实现映射

首先,先介绍一下 association 标签中常用到的属性值:

  • property : 子表结果要映射到的对象属性
  • columnPrefix : 对应的 SQL 查询字段的列名前缀
  • resultMap:可以使用现有的 resultMap ,不需要再次配置,引用时为被引用 resultMap 的 id,如果在同一个 XML 文件中,仅 id 即可,再其他 XML 文件中,则需要 namespace + id 来进行引用
  • select : 子查询时执行对应的 SQL,通过其 value 和已有的 select 标签的 id 对应来进行关联,如果在同一个 XML 文件中,仅 id 即可,再其他 XML 文件中,则需要 namespace + id 来进行引用
  • column : 列名/别名,将主查询中列的结果作为嵌套查询的参数,column = “{子查询条件列名 = 主查询列名/别名}”,当子查询参数为多个时,用逗号隔开,如:column = “{子查询条件列名 = 主查询列名/别名,子查询条件列名 = 主查询列名/别名}”
  • fetchType : 值分为 lazy|eager;设置为 lazy,则表示只有当调用实体类对象中的子查询对象时,才会去真正执行子查询

association 标签的使用分为两种情况:

  • 不使用子查询

  • 使用子查询


不使用子查询的方式:

示例如下:

<resultMap id="UserEntity" type="SysUser" >
            <id column="id" property="id"/>
            <result property="userName" column="user_name" />
            <result property="userPassword" column="user_password" />
            <result property="userEmail" column="user_email" />
            <result property="userInfo" column="user_info" />
            <result property="headImg" column="head_img" jdbcType="BLOB" />
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
<!--        <result property="role.id" column="role_id" />-->
<!--        <result property="role.roleName" column="roleName" />-->
<!--        <result property="role.enabled" column="enabled" />-->
<!--        <result property="role.createBy" column="role.createBy" />-->
<!--        <result property="role.createTime" column="role.createTime" />-->
            <association property="role" columnPrefix="role_" >
                <result property="id" column="id" />
                <result property="roleName" column="role_name" />
                <result property="enabled" column="enabled" />
                <result property="createBy" column="role_createBy" />
                <result property="createTime" column="role_createTime" />
            </association>
</resultMap>

以上的这个 resultMap 是以 SQL 别名与 resultMap 标签配合实现映射 这一节中的 XML 为模板,进行改造的,只是注释掉了原来直接 result 对应的返回 role 属性上的映射,新增了 association 标签,将原来 role 属性的字段映射放到 association 标签中,由于 association 设置了 columnPrefixcolumnPrefix 属性,该属性对应 SQL 别名列前缀,故此,SQL 中对应列的列别名应该是 columnPrefixcolumnPrefix + association 标签下 result 标签的 column 属性值;SQL 如下:

<select id="selectUserInfoAndRoleInfoUseTag2" resultMap="UserEntity">
     select
         user.id,
         user.user_name,
         user.user_email,
         role.id "role_id",	<!-- 求留意此处的 role_id:其中 role_ 对应 <association> 标签的 columnPrefix 值,id 对应 <association> 标签下 <result> 的 column 的值;以下 role 表中的别名属性同理 -->
         role.role_name "role_role_name",
         role.enabled "role_enabled",
         role.create_By "role_role_createBy",
         role.create_time "role_role_createTime"
     from sys_user user
     inner join sys_user_role userRole on user.id = userRole.user_id
     inner join sys_role role on userRole.role_id = role.id
     where
         1 = 1
         <if test="userId != null" >
         	and user.id = #{userId}
         </if>
 </select>

配置完后,便可以正常使用。


使用子查询:

示例如下:

<resultMap id="UserRoleEntity" type="SysUser" ><!-- autoMapping="true"-->
            <id column="id" property="id"/>
            <result property="userName" column="user_name" />
            <result property="userPassword" column="user_password" />
            <result property="userEmail" column="user_email" />
            <result property="userInfo" column="user_info" />
            <result property="headImg" column="head_img" jdbcType="BLOB" />
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
            <association property="role" column="{id = role_id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectById">
            </association>
</resultMap>

简单设置之后,便可正常使用了。

这里,我们使用的是子查询的方式,通过查询出主表的数据后,将主表查询出来的 role_id 字段的值作为 com.mybatis.simple.mapper.SysRoleMapper.selectById 查询的值,通过 column="{id = role_id}" 来对应,再进行一次查询,所以,控制台会打印出两条 SQL 查询语句。

下面再来看一下 fatchType 属性,我们在上面的 association 标签中增加 fatchType = “lazy” 属性,如下:

<association property="role" column="{id = role_id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectById" fetchType="lazy" >
</association>

<!-- 还需要将对应的 SQL 修改为 查询多条数据,因为当查询只返回一条数据时,MyBatis 查询单条数据和多条数据都会调用 selectList 的方法,只不过单挑数据的返会最后会调用一个 list.get(0) 来进行返回,而在这一步,会默认调用子查询,这里为了方便看设置的懒加载的效果,我们将 SQL 修改为查询多条数据 -->
<select id="selectUserInfoAndRoleInfoUseTag2" resultMap="UserRoleEntity" ><!-- resultMap="UserEntity" -->
        select
        user.id,
        user.user_name,
        user.user_email,
        role.id "role_id"
        from sys_user user
        inner join sys_user_role userRole on user.id = userRole.user_id
        inner join sys_role role on userRole.role_id = role.id
        where
        1 = 1
        <if test="list != null" >
            and user.id in
            <foreach collection="list" open="(" close=")" separator="," item="userId" >
                #{userId}
            </foreach>
        </if>
    </select>
// 接口方法如下:
List<SysUser> selectUserInfoAndRoleInfoUseTag2(List<Integer> userId);

在测试类中打个断点,运行,可以看到只有我们在调用 userList.get(0).getRole() 时,才会进行子查询,

我的测试方法如下:

@Test
public void selectUserInfoAndRoleInfoUseTag2() {
    SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
    List<Integer> list = new ArrayList<>();
    list.add(1001);
    list.add(3);
    List<SysUser> userList = mapper.selectUserInfoAndRoleInfoUseTag2(list);
    System.out.println("====================主查询结束===============");
    userList.get(0).getRole();
    Assert.assertNotNull(userList);
}

控制台打印效果如下:

DEBUG [main] - ==>  Preparing: select user.id, user.user_name, user.user_email, role.id "role_id" from sys_user user inner join sys_user_role userRole on user.id = userRole.user_id inner join sys_role role on userRole.role_id = role.id where 1 = 1 and user.id in ( ? , ? ) 
DEBUG [main] - ==> Parameters: 1001(Integer), 3(Integer)
TRACE [main] - <==    Columns: id, user_name, user_email, role_id
TRACE [main] - <==        Row: 1001, test, test@mybatis.com, 2
TRACE [main] - <==        Row: 3, 猪八戒, BJ@xiyouji.com, 1
DEBUG [main] - <==      Total: 2
====================主查询结束===============
DEBUG [main] - ==>  Preparing: select id,role_name roleName,enabled enabled,create_by createBy,create_time createTime from sys_role where id = ? 
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <==    Columns: id, roleName, enabled, createBy, createTime
TRACE [main] - <==        Row: 2, 普通用户, 1, 1, 2020-06-13 21:15:49
DEBUG [main] - <==      Total: 1

一对多结果映射

MyBatis 的 一对多结果映射是通过使用 collection 标签来完成的,collection 标签的属性基本与 association 一致。

下面通过两个示例来看一下:

不使用子查询

<resultMap id="UserRoleUseCollectionTag" type="SysUser" extends="sysUser">
        <collection property="roleList" columnPrefix="role_" resultMap="com.mybatis.simple.mapper.SysRoleMapper.RoleEntity">
        </collection>
    </resultMap>
    <select id="userRoleByCollection" resultMap="UserRoleUseCollectionTag">
        select
        user.id,
        user.user_name,
        user.user_email,
        role.id "role_id",
        role.role_name "role_role_name",
        role.enabled "role_enabled",
        role.create_by "role_createBy"
        from sys_user user
        inner join sys_user_role userRole on user.id = userRole.user_id
        inner join sys_role role on userRole.role_id = role.id
        where
        1 = 1
        <if test="list != null" >
            and user.id in
            <foreach collection="list" open="(" close=")" separator="," item="userId" >
                #{userId}
            </foreach>
        </if>
    </select>
List<SysUser> userRoleByCollection(List<Integer> userId);

然后测试,便可以看到,控制台一共查询出来四条数据;经 MyBatis 的 collection 标签合并处理后,userList.size() 为 2 ,Role 的数据被合并到对应的 user 对象的 roleIist 属性中。

DEBUG [main] - ==>  Preparing: select user.id, user.user_name, user.user_email, role.id "role_id", role.role_name "role_role_name", role.enabled "role_enabled", role.create_by "role_createBy" from sys_user user inner join sys_user_role userRole on user.id = userRole.user_id inner join sys_role role on userRole.role_id = role.id where 1 = 1 and user.id in ( ? , ? ) 
DEBUG [main] - ==> Parameters: 1004(Integer), 1(Integer)
TRACE [main] - <==    Columns: id, user_name, user_email, role_id, role_role_name, role_enabled, role_createBy
TRACE [main] - <==        Row: 1, admin, admin@mybatis.com, 1, 管理员, 1, 1
TRACE [main] - <==        Row: 1, admin, admin@mybatis.com, 2, 普通用户, 1, 1
TRACE [main] - <==        Row: 1004, 孙悟空, SWK@xiyouji.com, 1, 管理员, 1, 1
TRACE [main] - <==        Row: 1004, 孙悟空, SWK@xiyouji.com, 2, 普通用户, 1, 1
DEBUG [main] - <==      Total: 4
userList.size() : 2

上面的示例中,是通过列名映射的方式,来完成一对多关系的映射的;在 collectio 标签使用过程中,其是通过上一级的 resultMap 的 id 标签设置的列的值来确定数据是否一致的,如果 resultMap 中未设置 id 标签,则会去比较上一级的所有的列,如果相同则合并,不同则不合并。

使用子查询

使用 collection 的子查询,需要在原有的基础上做一些改动,将表关系的关联放到子查询的 SQL 中,示例如下:

SysUserMapper.xml

<resultMap id="UserRoleUseCollectionTag2" type="SysUser" extends="sysUser">
    <collection property="roleList" column="{userId = id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectByUserId">
    </collection>
</resultMap>
<select id="userRoleByCollection2" resultMap="UserRoleUseCollectionTag2">
    select
        user.id,
        user.user_name,
        user.user_email
        from sys_user user
    where
        1 = 1
        <if test="list != null" >
        	and user.id in
            <foreach collection="list" open="(" close=")" separator="," item="userId" >
            	#{userId}
            </foreach>
        </if>
</select>
SysRoleMapper.xml 对应的 SQL 如下:

<select id="selectByUserId" resultType="SysRole">
    select
        id,
        role_name roleName,
        enabled enabled,
        create_by createBy,
        create_time createTime
    from sys_role role
    inner join sys_user_role userRole on userRole.role_id = role.id
    where userRole.user_id = #{userId}
</select>

之后,便可以通过测试代码进行测试。

上面的示例可以得知,在主查询中,不用关心关联子表的问题,只用查询主表,之后通过 collection 标签中设置的查询方法进行联查,将联查操作放到子查询中完成。collection 标签的懒加载属性设置与 association 标签的设置方法相同,这里不做介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值