MyBatis学习——高级结果映射

前面我们讲解了一下关于MyBatis的动态SQL查询,对MyBatis的基本用法有了大致的了解,如果忘记了可以去复习一下,MyBatis学习——动态SQL。今天我们将讲解一下MyBatis的高级查询知识点。

前言:在关系型数据库中,我们经常要处理一对一 、一对多的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。但是在一般的企业级应用中,使用MyBatis的高级结果映射便可以轻松地处理这种一对一 、 一对多的关系。

比如,现在我有两张表,一张表示用户相关信息,另一张表示用户角色相关信息,一个用户可以对应多个角色。

用户表:
user

角色表:
role

下面的内容我们将根据这两张表用户与角色的内容进行讲解。

一、一对一映射

1、使用自动映射处理一对一关系(对象与数据库字段映射)

比如,这里我们使用了role表的查询,并关联user表,将我们需要的结果全部都查询出来,然后再在对应的实体中进行映射,所以在User实体当中,我们对role进行映射。查询如下:

 <select id="selectUserAndRoleById" resultType="User">
      select
        u.*,
        r.id "role.id",
        r.role "role.role",
        r.enabled "role.enabled"
        from user u
        inner join role r on u.id = r.user_id
        where u.id = #{id}
    </select>

我们在User实体中对Role进行映射。如下:
role映射
当我们查询一对一的关联数据时,就可以自动映射到role实体上去。MyBatis还支持复杂的属性映射 , 可以多层嵌套。 例如,将 role.role 映射到 role.role上。 MyBatis 会先查找role属性,如果存在role属性就创建 role对象, 然后在role对象中继续查找role,将role的值绑定到role对象的role属性上。

2、使用resultMap配置一对一映射

在上面我们直接通过在实体中进行了映射,除此之外,我们还可以在resultMap中进行配置,对多个不同的属性进行配置。

    <resultMap id="userMap" type="net.anumbrella.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="expried" column="expried"/>
        <result property="disabled" column="disabled"/>
        <result property="role.id" column="role.id"/>
        <result property="role.role" column="role.role"/>
        <result property="role.enabled" column="role.enabled"/>
    </resultMap>

同时因为MyBatis是支持resultMap映射继承的, 因此要先简化上面的resultMap配置。我们可以把角色抽出来单独作为一个resultMap,然后继承(在resultMap中使用extends标签继承)用户属性的resultMap即可。由于这个内容比较简单,这里就不过多叙述。

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

在resultMap中,assocation标签用于和一个复杂的类型进行关联, 即用于一对一的关联配置。

assocation标签包含以下属性:

  • property:对应实体类中的属性名,必填项。
  • javaType:属性对应的Java类型。
  • resultMap:可以直接使用现有的resultMap,而不需要在这里配置。
  • columPrefix:查询列的前缀,配置前缀后,在子标签result的colum时可以省略前缀。

因此上面的resultMap可以更改如下:

 <resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role" columnPrefix="role_" javaType="net.anumbrella.mybatis.entity.Role">
            <result property="role.id" column="role.id"/>
            <result property="role.role" column="role.role"/>
            <result property="role.enabled" column="role.enabled"/>
        </association>
    </resultMap>

注意:这里是带表前缀的,所以在查出的结果对应列中需要加入role_前缀。

同时我们还可以进一步精简,如下:

    <resultMap id="roleMap" type="net.anumbrella.mybatis.entity.Role">
        <id property="id" column="id"/>
        <result property="role" column="role"/>
        <result property="enabled" column="enabled"/>
    </resultMap>


	<resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role" columnPrefix="role_" resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
    </resultMap>
4、association标签的嵌套查询

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

  • select:另一个映射查询的id, MyBatis会额外执行这个查询获取嵌套对象的结果。
  • column:列名(或别名),将主查询中列的结果作为嵌套查询的参数,配置方式如column={propl=coll,prop2=col2}, propl和prop2将作为嵌套查询的参数。
  • fetchType:数据加载方式,可选值为lazy和eager ,分别为延迟加载和积极加载,这个配置会覆盖全局的 lazyLoadingEnabled配置。

比如下面的查询结果配置,同时我们也在select中配置了子查询。

 <resultMap id="userRoleMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role"
                     fetchType="lazy"
                     select="net.anumbrella.mybatis.dao.RoleDao.selectRoleById"
                     column="{id=role.id}"/>
    </resultMap>

子查询在RoleDao.xml配置,如下:

    <select id="selectRoleById" resultMap="roleMap">
      select * from role where id = #{id}
    </select>

因为第一个SQL的查询结果只有一条,role.id关联了另一个查询,因此执行了两次SQL。

如果查询的不是1条数据,而是N条数据,那就会出现N+1问题,主SQL会查询一次,查询出N条结果, 这N条结果要各自执行一次查询,那就需要进行N次查询。 如何解决这个问题呢?

在上面介绍association标签的属性时, 介绍了fetchType数据加载方式, 这个方式可以帮我们实现延迟加载,解决N+1的问题。

在 MyBatis 的全局配置中, 有一个参数为 aggressiveLazyLoading 。这个参数的含义是,当该参数设置为 true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之, 每种属性都将按需加载。

因此当我们使用MyBatis懒加载时,需要配置aggressiveLazyLoading为false。如下:

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
</configuration>

按照上面的介绍,需要把fetchType设置为lazy(这里设置lazy,相当于把lazyLoadingEnabled设置为true),这样设置后,只有当调用getRole()方法获取role的时候, MyBatis才会执行嵌套查询去获取数据。

但是有些时候还是需要在触发某方法时将所有的数据都加载进来 , 而我们己经将aggressiveLazyLoading设置为 false ,这种情况又该怎么解决呢?

MyBatis 仍然提供了参数 lazyLoadTriggerMethods 帮助解决这个问题, 这个参数的含义是, 当调用配置中的方法时, 加载全部的延迟加载数据。 默认值为” equals , clone ,hashCode ,toString ” 。

二、一对多映射

1、collection集合的嵌套结果映射

在前文我们介绍一对一的映射关系中,有多种实现方式,但是一对多的实现方式只有一种方式————collection。

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

比如,我们在User实体中添加roleList对象,同时添加set/get方法。

public class User implements Serializable{

    private Long id;

    private String username;

    private String password;

    private Integer expired;

    private Integer disabled;

    private String email;

    private Role role;

    private List<Role> roleList;

   // set/get方法
    ...
}

然后我们添加关联查询,如下:

   <resultMap id="userRoleListMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <collection property="roleList" columnPrefix="role_"
                    resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
   </resultMap>
    
    
   <select id="selectAllUserAndRoles" resultMap="userRoleListMap">
      select
        u.*,
        r.id "role_id",
        r.role "role_role",
        r.enabled "role_enabled"
        from user u
        inner join role r on u.id = r.user_id
    </select>

接着在UserDao中添加selectAllUserAndRoles方法。

SQL执行的结果数有 3 条, 后面输出的用户数是 2 , 本来查询出的3条结果经过MyBatis对collection数据的处理后,变成了两条。

collection

因为这里使用了collection,它会把集合的数据合并。那么MyBatis的合并规则如何呢?

MyBatis 判断结果是否相同时, 最简单的情况就是在映射配置中 至少有一个id标签 , 在userMap中配置如下。

<id property =” id ” column=” id ” />

我们对 id (构造方法中为 idArg )的理解一般是 , 它配置的字段为表的主键(联合主键时可以配置多个 id 标签), 因为MyBatis的 resultMap 只用于配置结果如何映射 , 并不知道这个表具体如何。 id 的唯一作用就是 在嵌套的映射配置时判断数据是否相同, 当配置 id 标签时, MyBatis只需要逐条比较所有数据中 id 标签配置的字段值是否相同即可。 在配置嵌套结果查询时 , 配置 id 标签可以提高处理效率。

因为前两条数据的userMap部分的 id 相同 , 所以它们属于同一个用户, 因此这条数据会合并到同一个用户中。

虽然 association 和 collection 标签是分开介绍的 , 但是这两者可以组合使用或者互相嵌套使用 , 也可以使用 符合自己需要的任何数据结 构, 不需要局限于数据库表之间的关联关系 。

2、collection集合的嵌套查询

集合的嵌套查询也association类似,这里不过多讲述,这是大致提示一下。

同样的我们添加相关查询,如下:

    <resultMap id="userRoleListMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <collection property="roleList"
                    fetchType="lazy"
                    select="net.anumbrella.mybatis.dao.RoleDao.selectRoleByUserId"
                    column="{userId=id}"/>
    </resultMap>
    
    <select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
        select
        u.id,
        u.username,
        u.password,
        u.email,
        u.expired,
        u.disabled
        from user u
        where u.id = #{id}
    </select>

同理,我们在RoleDao.xml中配置selectRoleByUserId的查询,如下:

<select id="selectRoleByUserId" resultMap="roleMap">
    select * from role where user_id = #{userId}
</select>

同理因为所有嵌套查询都 配置为延迟加载 , 因此不存在 N+1 的问题 。

三、鉴别器映射

有时一个单独的数据库查询会返回很多不同数据类型(希望有些关联)的结果集。

鉴别器标签就是用来处理这种情况的。

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="net.anumbrella.mybatis.entity.Role">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
    </discriminator>
</resultMap>

比如,像上面的enabled为不同的值时会根据情况返回不同的resultMap作为结果。

在discriminator中也可以使用javaType返回属性。

<resultMap id="rolePrivilegeListMapChoose" type="net.anumbrella.mybatis.entity.Role">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
        <case value="0"  resultType="net.anumbrella.mybatis.entity.Role>
         <id property ="id" column=“id” /> 
         <result property ="role" column="role" />
        </case>
    </discriminator>
</resultMap>

鉴别器是一种很少使用的方式 , 在使用前一定要完全掌握, 没有把握 的情况下要尽可能避免使用。

关于MyBatis的高级结果映射的介绍到此为止,更多详细的用法可以去查阅相关文档。

上面的代码示例:mybatis-demo

参考

  • 《MyBatis从入门到精通》
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值