mybatis复杂结果映射

19 篇文章 0 订阅


mybatis-3中文文档

高级结果映射

一个博客只会关联一个作者
一个博客中可以有多个文章
一个文章可以有多个评论;(暂时不考虑父子级评论)
一个文章也可以有多个标签

关系如下图

在这里插入图片描述

<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">

  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  
  <result property="title" column="blog_title"/>
  
  <association property="author" javaType="Author">
  
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
    
  </association>
  
  <!-- 一个博客 有 多个文章 -->
  <collection property="posts" ofType="Post">
  
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    
    <!-- 一个文章 只能有 一个作者 -->
    <association property="author" javaType="Author"/>
    
    <!-- 一个文章 可以有 多个评论 -->
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    
    <!-- 一个文章 可以有 多个标签 -->
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
    
  </collection>
  
</resultMap>

<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B  <!-- 博客 -->
       left outer join Author A on B.author_id = A.id  <!-- 博客作者 -->
       left outer join Post P on B.id = P.blog_id      <!-- 博客文章 -->
       left outer join Comment C on P.id = C.post_id   <!-- 文章评论 -->
       left outer join Post_Tag PT on PT.post_id = P.id<!-- 文章标签关系表 -->
       left outer join Tag T on PT.tag_id = T.id       <!-- 文章标签 -->
  where B.id = #{id}
</select>

先看如果使用连接查询,它们的数据大致可以如下描述,可以看出来mybatis提供的强大的数据封装能力:

文章A    评论1               (A 有评论1、评论2、评论3)
文章A    评论1   
文章A    评论2  


文章B    评论4               (B 有评论1)
文章B    评论4    

Post{ List<Comment> comments }

-------------------------

文章A    评论1   标签a       (A 有评论1、评论2、评论3;标签a、标签b)
文章A    评论1   标签b
文章A    评论2   标签a
文章A    评论2   标签b
文章A    评论3   标签a
文章A    评论3   标签b

文章B    评论4   标签a       (B 有评论1;标签a、标签b)
文章B    评论4   标签b

Post{ List<Comment> comments, List<Tag> tags }

-------------------------

博客Ⅰ     文章A     评论1    (博客Ⅰ 有文章A、文章B;文章A 有评论1、评论2、评论3)
博客Ⅰ     文章A     评论2
博客Ⅰ     文章A     评论3
博客Ⅰ     文章B     评论4

博客Ⅱ     文章C     评论5    (博客Ⅱ 有文章C;文章C 有评论5、评论6)
博客Ⅱ     文章C     评论6

Blog{ List<Post> posts},Post{ List<Comment> comments}

-------------------------

博客Ⅰ  文章A    评论1   标签a (博客Ⅰ 有文章A、文章B;文章A 有评论1、评论2、评论3;文章A 有标签a、标签b)
博客Ⅰ  文章A    评论1   标签b 
博客Ⅰ  文章A    评论2   标签a
博客Ⅰ  文章A    评论2   标签b
博客Ⅰ  文章A    评论3   标签a
博客Ⅰ  文章A    评论3   标签b
博客Ⅰ  文章B    评论4   标签a     
博客Ⅰ  文章B    评论4   标签b

博客Ⅱ   文章C   评论5   标签a (博客Ⅱ 有文章C;文章C 有评论5、评论6;文章C 有标签a、标签b))
博客Ⅱ   文章C   评论6   标签a 

Blog{ List<Post> posts},Post{ List<Comment> comments, List<Tag> tags }

association

关联的嵌套 Select 查询

查询指定id的博客,并且查询出这个博客的作者

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

<resultMap id="blogResult" type="Blog">
  <!-- 以column为条件, 使用另外一条sql去作查询(上面那条) -->
  <!-- 1. 补充:这里的column可以写成:column="{prop1=col1,prop2=col2}"形式,
               将prop1和prop2作为另外一条sql查询的查询参数,其中prop1的值取得是当前查询的col1列的值,prop2的值同理 -->
  <!-- 2. 但这样会造成N+1的问题:本来只要查一次就完了,可是这样,就对每条结果都要来这么一次“关联的嵌套 Select 查询”.
         mybatis里面可以使用延迟加载, 即设置 fetchType="lazy", 当需要用到数据时,才会发起关联查询的语句,
         但是,如果查完之后要遍历的话,那就没啥大用的,只不过它这里用了动态代理实现的-->
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>
关联的嵌套结果映射

依然是:查询指定id的博客,并且查询出这个博客的作者
但是mybatis给出了另外一种映射方案,我们可以使用连接查询的方式并且修改映射方式

<resultMap id="blogResult" type="Blog">

  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
  
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  select
	    B.id            as blog_id,
	    B.title         as blog_title,
	    B.author_id     as blog_author_id,
	    A.id            as author_id,
	    A.username      as author_username,
	    A.password      as author_password,
	    A.email         as author_email,
	    A.bio           as author_bio
  from 
  		Blog B 
		left outer join Author A on B.author_id = A.id
  where 
  		B.id = #{id}
</select>

<!-- 我们也可以如下这样写, 目的是让这个id=“authorResult”的resultMap能够复用,但感觉没啥必要~

	<resultMap id="authorResult" type="Author">
	  <id property="id" column="author_id"/>
	  <result property="username" column="author_username"/>
	  <result property="password" column="author_password"/>
	  <result property="email" column="author_email"/>
	  <result property="bio" column="author_bio"/>
	</resultMap>
	
	<resultMap id="blogResult" type="Blog">
	
	  <id property="id" column="blog_id" />
	  <result property="title" column="blog_title"/>
	  
	  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
	  
	</resultMap>
-->

collection

集合的嵌套 Select 查询

查询指定id的博客,并且查询该博客下的所有文章

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

<resultMap id="blogResult" type="Blog">
   <!-- 以column为条件, 使用另外一条sql去作查询(上面那条) -->
   <!-- 其中,有一个“ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开,
        读作: “posts 是一个存储 Post 的 ArrayList 集合” -->
  <!-- 在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写 -->
  <!-- 1. 补充:这里的column可以写成:column="{prop1=col1,prop2=col2}"形式,
               将prop1和prop2作为另外一条sql查询的查询参数,其中prop1的值取得是当前查询的col1列的值,prop2的值同理 -->
  <!-- 2. 但这样会造成N+1的问题:本来只要查一次就完了,可是这样,就对每条结果都要来这么一次“关联的嵌套 Select 查询”.
         mybatis里面可以使用延迟加载, 即设置 fetchType="lazy", 当需要用到数据时,才会发起关联查询的语句,
         但是,如果查完之后要遍历的话,那就没啥大用的,只不过它这里用了动态代理实现的-->
  <collection property="posts" javaType="ArrayList" 
  			  column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>
集合的嵌套结果映射

依然是:查询指定id的博客,并且查询该博客下的所有文章
但是mybatis给出了另外一种映射方案,我们可以使用连接查询的方式并且修改映射方式

<resultMap id="blogResult" type="Blog">

  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
  
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  select
		B.id as blog_id,
		B.title as blog_title,
		B.author_id as blog_author_id,
		P.id as post_id,
		P.subject as post_subject,
		P.body as post_body,
  from 
  		Blog B
  		left outer join Post P on B.id = P.blog_id
  where 
  		B.id = #{id}
</select>

<!-- 我们也可以如下这样写, 目的是让这个id=“blogResult”的resultMap能够复用,但感觉没啥必要~

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>
-->
注意点

虽然这种方式避免了N+1的问题,但是我们应当注意到,当我们需要对主表,比如说Blog表,进行分页查询时,这个时候,使用上面的集合的嵌套结果映射就有问题了。假设我们使用的PageHelper分页插件,一个Blog查出来,它会对应好多个文章,也就是数据库中的一条Blog记录,但是这条Blog记录连接查询后的结果,由于一对多的关系,形成了多条Blog记录(一个Blog记录对应多个文章),那这里这么分页肯定就不对了(除非你又对他按博客id分组,但是又只能对关联的文章作聚合函数查询),因此,这种情况,只能用上面的集合的嵌套Select查询,因为PageHelper是把分页信息存储在ThreadLocal中,并且只会在开启分页后第一次执行查询时才会拼接分页sql,因此,后面的嵌套Select查询就没分页了。

注意点补充

如果不使用分页插件,使用手动分页也是可以做的,做法如下:

文章只能有一个分类,可以有多个标签,分类和标签都不存在上下级关系。以 categoryId 和 tagId为条件分页查询文章,并且将文章关联到的所有标签给查询出来(这文章关联到的标签会是一个集合),因为里面是个集合,所以并不能直接就使用分页插件来玩。可以考虑使用下面的手动分页,先按条件查询出满足条件的文章(此时已完成分页),再使用关联查询出所有标签

<select id="listArticleBacks" resultMap="articleBackResultMap">
      SELECT
       a.id,
       article_cover,
       article_title,
       type,
       is_top,
       a.is_delete,
       a.status,
       a.create_time,
       category_name,
       t.id AS tag_id,
       t.tag_name
      FROM
      	(
        SELECT
        id,
        article_cover,
        article_title,
        type,
        is_top,
        is_delete,
        status,
        create_time,
        category_id
        FROM
       	tb_article
      <where>
          is_delete = #{condition.isDelete}
          <if test="condition.keywords != null">
              and article_title like concat('%',#{condition.keywords},'%')
          </if>
          <if test="condition.status != null">
              and status = #{condition.status}
          </if>
          <if test="condition.categoryId != null">
              and category_id = #{condition.categoryId}
          </if>
          <if test="condition.type != null">
              and type = #{condition.type}
          </if>
          <if test="condition.tagId != null">
              and id in
               (
                SELECT
                  article_id
                FROM
                  tb_article_tag
                WHERE
                  tag_id = #{condition.tagId}
               )
          </if>
      </where>
      ORDER BY
        is_top DESC,
        id DESC
      LIMIT #{current},#{size}
      ) a
      LEFT JOIN tb_category c ON a.category_id = c.id
      LEFT JOIN tb_article_tag atg ON a.id = atg.article_id
      LEFT JOIN tb_tag t ON t.id = atg.tag_id
      ORDER BY
        is_top DESC,
        a.id DESC
  </select>
RBAC权限Collection嵌套查询示例

有如下表:
sys_user_info 用户表
sys_user_role 用户-角色关系表
sys_role 角色表
sys_role_menu 角色-菜单关系表
sys_menu 菜单表

  • 一个用户,可以有多个角色,一个角色可以关联多个菜单(菜单即权限)。
  • 那么对于一个用户来说,它可以有多个角色,它也可以有多个菜单(通过角色关联到多个菜单)
  • 那么,可以确定这样的关系:
    • UserPermDTO{ Integer userInfoId , List<String> roles,List<String> perms}

数据说明:

  • 有2个用户
    • userInfoId为1的用户,有admin的角色和user的角色;
    • userInfoId为2的用户,有user的角色
  • admin的角色有:user:list, role:list, menu:list 这3个权限
  • user的角色有: role_menu:list 这1个权限

现要求查询所有人,并且每个人的角色集合和菜单(权限标识符)集合,如下:

UserInfoMapper.xml
<resultMap id="userPpermDTOMap" type="com.zzhua.blog.config.security.UserPermDTO">

    <id property="userInfoId" column="user_info_id"/>
    
    <collection property="roles" ofType="java.lang.String">
        <constructor>
            <arg column="role_label"/>
        </constructor>
    </collection>
    
    <collection property="perms" ofType="java.lang.String">
        <constructor>
            <arg column="perm"/>
        </constructor>
    </collection>
    
</resultMap>

<select id="listPermsForUser" resultMap="userPpermDTOMap">
    SELECT
        ui.id AS user_info_id,
        r.role_label,
        m.`perm`
    FROM
        sys_user_info ui
        	
	        LEFT JOIN sys_user_role ur1 ON ui.id = ur1.user_info_id
	        INNER JOIN sys_role r ON ur1.role_id = r.id
	        
	        LEFT JOIN sys_user_role ur2 ON ui.id = ur2.user_info_id
	        LEFT JOIN sys_role_menu rm ON ur2.role_id = rm.role_id
	        INNER JOIN sys_menu m ON rm.menu_id = m.id
    WHERE
        ui.id = #{userInfoId}
</select>
UserInfoMapper
public interface UserInfoMapper extends BaseMapper<UserInfoEntity> {

    List<UserPermDTO> listPermsForUser(@Param("userInfoId") Integer userInfoId);
}
BootBlogApplicationTests
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootBlogApplication.class)
public class BootBlogApplicationTests {

    @Autowired
    private UserInfoMapper userInfoMapper;
    @Test
    public void test002() {
        List<UserPermDTO> userPermDTOS = userInfoMapper.listPermsForUser();
        System.out.println(userPermDTOS);
    }


}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis提供了多种方式来处理复杂的结果集映射。以下是一些常见的技术和方法: 1. 嵌套查询(Nested Queries:可以使用嵌套来处理一对一、一对多和多对多的关联关系。通过定义嵌套的resultMap,可以将结果集映射到包含嵌套对象的复杂数据结构中。 2. 关联查询(Association):通过使用association标签,可以将关联对象映射到主对象中。这适用于一对一和多对一的关联关系。 3. 集合查询(Collection):通过使用collection标签,可以将集合对象映射到主对象中。这适用于一对多和多对多的关联关系。 4. resultMap继承(ResultMap Inheritance):可以通过定义一个基础的resultMap,并在子resultMap中使用继承关系来重用已定义的映射规则。这样可以减少重复的配置。 5. 枚举类型映射(Enum Mapping):如果结果集中包含枚举类型的字段,可以使用类型处理器或者自定义的类型处理器来将其映射为对应的Java枚举类型。 6. 自定义映射器(Custom Mappers):如果默认的映射方式无法满足需求,可以通过自定义映射器来实现复杂的结果集映射。自定义映射器可以通过实现ResultMapResolver接口来定义自己的映射规则。 这些只是一些常见的技术和方法,MyBatis在结果集映射方面提供了很多灵活的功能和选项,可以根据具体的需求选择适合的方式来处理复杂的结果集映射

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值