MyBatis 07-高级查询

http://www.broadview.com.cn/book/3643
https://github.com/mybatis-book/book

一对一映射

使用自动映射处理一对一关系

假设一个用户只有一个角色

public class SysUser {
    private Long id;
    private String userName;
    private String userPassword;
    private String userEmail;

    /**
     * 用户角色
     */
    private SysRole role;

    // ...
}

MyBatis 支持多层嵌套,role.roleName 将会绑定到 role 对象的 roleName 属性上。

<select id="selectUserAndRoleById" resultType="tk.mybatis.simple.model.SysUser">
    select
        u.id,
        u.user_name userName,
        u.user_password userPassword,
        u.user_email userEmail,
        r.id "role.id",
        r.role_name "role.roleName",
        r.enabled "role.enabled",
        r.create_by "role.createBy",
        r.create_time "role.createTime"
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{id}
</select>

使用 resultMap 配置一对一映射

<resultMap id="userMap" type="tk.mybatis.simple.model.SysUser">
    <id property="id" column="id"/>
    <result property="userName" column="user_name"/>
    <result property="userPassword" column="user_password"/>
    <result property="userEmail" column="user_email"/>

    <!-- role 部分,避免列名重复,可能重复的列名增加了 "role_" 前缀,并在 SQL 中设置别名 -->
    <result property="role.id" column="role_id"/>
    <result property="role.roleName" column="role_name"/>
    <result property="role.enabled" column="enabled"/>
    <result property="role.createBy" column="create_by"/>
    <result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>

<select id="selectUserAndRoleById2" resultMap="userRoleMap">
    select
        u.id,
        u.user_name,
        u.user_password,
        u.user_email,
        r.id role_id,
        r.role_name role_name,
        r.enabled enabled,
        r.create_by create_by,
        r.create_time role_create_time
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{id}
</select>

相比自动映射,这样会更麻烦。
可简单的部分,MyBatis 支持 resultMap 映射继承的。

<resultMap id="userMap" type="tk.mybatis.simple.model.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="userInfo" column="user_info"/>
    <result property="headImg" column="head_img" jdbcType="BLOB"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>

<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <result property="role.id" column="role_id"/>
    <result property="role.roleName" column="role_name"/>
    <result property="role.enabled" column="enabled"/>
    <result property="role.createBy" column="create_by"/>
    <result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>

使用 resultMap 的 association 标签配置一对一映射

修改上面的配置

<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <association property="role" columnPrefix="role_" javaType="tk.mybatis.simple.model.SysRole"/>
    <result property="id" column="id"/>
    <result property="roleName" column="role_name"/>
    <result property="enabled" column="enabled"/>
    <result property="createBy" column="create_by"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>

association 标签包含以下属性:

  • property:对应实体类中的属性名
  • javaType:属性对应的 Java 类型
  • resultMap:可以直接使用现有的 resultMap,而不需要在这里配置
  • columnPrefix:查询列的前缀,配置前缀后,在子标签配置 result 的 column 时可以省略前缀
<!-- 将 sys_role 相关所有列的别名修改为 "role_" 前缀 -->
<select id="selectUserAndRoleById2" resultMap="userRoleMap">
    select
        u.id,
        u.user_name,
        u.user_password,
        u.user_email,
        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
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
    where u.id = #{id}
</select>

使用 association 配置 resultMap 属性配置一个已经存在的 resultMap 映射。

<!-- 这个应该放到 RoleMapper.xml 更合理 -->
<resultMap id="roleMap" type="tk.mybatis.simple.model.SysRole">
    <id property="id" column="id"/>
    <result property="roleName" column="role_name"/>
    <result property="enabled" column="enabled"/>
    <result property="createBy" column="create_by"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>

<!-- 这里还是在 UserMapper.xml 中,所有需要加上 roleMap 的命名空间 -->
<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <association property="role" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>

association 标签的嵌套查询

association 标签的嵌套查询常用的属性如下:

  • select:另一个映射查询的 id,MyBatis 会额外执行这个查询获取嵌套对象的结果
  • column:列名,将主查询中列的结果作为嵌套查询的参数,配置方式:column={prop1=col1, prop2=col2},prop1,prop2 将作为嵌套查询的参数
  • fetchType:数据加载方式,可选值为 lazyeager,分别为延迟加载和积极加载,将覆盖全局配置 lazyLoadingEnabled
<resultMap id="userRoleMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <association property="role" fetchType="lazy"
                    select="tk.mybatis.simple.mapper.RoleMapper.selectRoleById"
                    column="{id=role_id}"/>
</resultMap>

<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
    select
        u.id,
        u.user_name,
        u.user_password,
        u.user_email,
        u.user_info,
        u.head_img,
        u.create_time,
        ur.role_id
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    where u.id = #{id}
</select>

<!-- RoleMapper.xml -->
<select id="selectRoleById" resultMap="roleMap">
    select * from sys_role where id = #{id}
</select>

嵌套查询会多执行 SQL,查询出来的 N 条数据,就会执行 N+1 次查询,设置 fetchType="lazy",这样只有调用 getRole() 方法时,才会去执行嵌套查询。

另外还需设置 aggressiveLazyLoading 才会生效,在 mybatis-config.xml 中添加配置:

<settings>
    <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

在和 spring 集成时,要确保只能在 Service 层调用延迟加载的属性,因为在 Control 层获取延迟加载的属性值,会因为 SqlSession 已经关闭而报错。

另外 MyBatis 还提供了 lazyLoadTriggerMethods 参数,指定哪个对象的方法触发一次延迟加载,默认值:equals,clone,hashCode,toString。如执行了 equals 方法就会加载数据。

一对多映射

collection 集合的嵌套结果映射

和 association 类似,集合的嵌套结果映射就是指通过一次 SQL 查询将所有的结果查询出来,然后通过配置的结果映射,将数据映射到不同的对象中去。

public class SysUser {
    // 原有属性
    private List<SysRole> roleList;
    // setter getter
}
<resultMap id="userRoleListMap" extends="userMap" type="tk.mybatis.simple.model.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="userInfo" column="user_info"/>
    <result property="headImg" column="head_img" jdbcType="BLOB"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    <collection property="roleList" columnPrefix="role_" javaType="tk.mybatis.simple.model.SysRole">
        <id property="id" column="id"/>
        <result property="roleName" column="role_name"/>
        <result property="enabled" column="enabled"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </collection>
</resultMap>

只是把上文 [使用 resultMap 的 association 标签配置一对一映射] 中 association 改成 collection

collection 支持的属性以及属性的作用和 association 完全相同。

<!-- 使用 extends,简化 resultMap -->
<resultMap id="userRoleListMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <collection property="roleList" columnPrefix="role_"
                resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>

仿照 selectUserAndRoleById2 方法,创建 selectAllUserAndRoles

<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
    select
        u.id, u.user_name,
        u.user_password, u.user_email,
        u.user_info, u.head_img, u.create_time,
        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
    from sys_user u
    inner join sys_user_role ur on u.id = ur.user_id
    inner join sys_role r on ur.role_id = r.id
</select>

admin 用户,对应角色:管理员,普通用户
test 用户,对应角色:普通用户

SQL 执行结果数有 3 条,通过 selectAllUserAndRoles 查询出来用户数是 2。

MyBatis 在处理结果的时候,会判断结果是否相同,如果是相同的结果,则只会保留第一个结果。MyBatis 判断结果是否相同时,最简单的情况就是在映射配置中至少有一个 id 标签,在 userMap 中配置如下:

<id property='id' column='id'>

当配置 id 标签时,MyBatis 只需要逐条比较所以数据中 id 标签配置的字段值是否相同即可。在配置嵌套结果查询时,配置 id 标签可以提高处理效率。

在嵌套结果配置 id 属性时,如果查询语句中没有查询 id 属性配置的列,会导致 id 对应的值未 null,所有值 id 都相同,会使嵌套集合中只有一条数据。所以查询语句中必须包含该列。

如果将 id 标签改为 result,MyBatis 会对所有字段进行比较,当字段数为 M,查询结果为 N 条时,需要进行 M*N 次比较。

如果要配置一个相当复杂的映射,一定要从基础映射开始配置,每增加一些配置就进行对应的测试,在循序渐进的过程中更容易发现和解决问题。

collection 集合的嵌套查询

和 association 相似

有一个新的 ofType 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。

<!-- PrivilegeMapper.xml -->
<resultMap id="privilegeMap" type="tk.mybatis.simple.model.SysPrivilege">
    <id property="id" column="id"/>
    <result property="privilegeName" column="privilege_name"/>
    <result property="privilegeUrl" column="privilege_url"/>
</resultMap>

<select id="selectPrivilegeByRoleId" resultMap="privilegeMap">
    select p.*
    from sys_privilege p
    inner join sys_role_privilege rp on rp.privilege_id = p.id
    where role_id = #{roleId}
</select>
<!-- RoleMapper.xml collection 中的 javaType 和 ofType 可以省略-->
<resultMap id="rolePrivilegeListMapSelect" extends="roleMap" type="tk.mybatis.simple.model.SysRole">
    <collection property="privilegeList"
                fetchType="lazy"
                ofType="tk.mybatis.simple.model.SysPrivilege"
                javaType="ArrayList"
                select="tk.mybatis.simple.mapper.PrivilegeMapper.selectPrivilegeByRoleId"
                column="{roleId=id}"/>
</resultMap>

<select id="selectRoleByUserId" resultMap="rolePrivilegeListMapSelect">
    select
        r.id, r.role_name, r.enabled,
        r.create_by, r.create_time
    from sys_role r
    inner join sys_user_role ur on ur.role_id = r.id
    where ur.user_id = #{userId}
</select>
<!-- UserMapper.xml -->
<resultMap id="userRoleListMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
    <collection property="roleList"
                fetchType="lazy"
                ofType="tk.mybatis.simple.model.SysRole"
                select="tk.mybatis.simple.mapper.RoleMapper.selectRoleByUserId"
                column="{userId=id}"/>
</resultMap>

<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
    select
        u.id, u.user_name, u.user_password,
        u.user_email, u.user_info,
        u.head_img, u.create_time
    from sys_user u
    where u.id = #{id}
</select>

鉴别器映射

鉴别器很像 Java 中的 switch 语句

discriminator 标签常用的两个属性:

  • column:该属性用于设置要进行鉴别比较值的列
  • javaType:该属性用于指定列的类型,保证使用相同的 Java 类型来比较值。

discriminator 标签可以有 1 个或者多个 case 标签,case 标签包含以下三个属性:

  • value:该值未 discriminator 指定 column 用来匹配的值。
  • resultMap:当 column 的值和 value 的值匹配时,可以配置使用 resultMap 指定的映射,resultMap 优先级 高于 resultType。
  • resultType:当 column 的值和 value 的值匹配时,用于配置使用 resultType 指定的映射。
<resultMap id="rolePrivilegeListMapChoose" type="tk.mybatis.simple.model.SysRole">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
    </discriminator>
</resultMap>

角色的属性 enable 值为 1 的时候表示状态可用,为 0 不可用。当不可用时,只能获取角色的基本信息,不能获得角色的权限信息。

<select id="selectRoleByUserIdChoose" resultMap="rolePrivilegeListMapChoose">
    select
        r.id, r.role_name, r.enabled,
        r.create_by, r.create_time
    from sys_role r
    inner join sys_user_role ur on ur.role_id = r.id
    where ur.user_id = #{userId}
</select>

鉴别器特殊的地方

<resultMap id="rolePrivilegeListMapChoose" type="tk.mybatis.simple.model.SysRole">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultType="SysRole">
            <id property="id" column="id"/>
            <result property="roleName" column="role_name"/>
        </case>
    </discriminator>
</resultMap>

使用 resultType,并在 case 中配置了两个属性的映射,这时 MyBatis 只会对列举出来的配置进行映射,不像使用 resultMap 配置时会自动映射其他的字段。

鉴别器很少使用,尽可能避免使用。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:MyBatis 07-高级查询

文章字数:3.2k

本文作者:Bin

发布时间:2019-11-17, 21:04:43

最后更新:2019-12-03, 20:27:57

原始链接:http://coolview.github.io/2019/11/17/MyBatis/MyBatis%2007-%E9%AB%98%E7%BA%A7%E6%9F%A5%E8%AF%A2/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录