高级结果映射之一对多 2个月前

1 数据准备

创建以下的名为 mybatis 的数据库, 并在其下创建4个表。

数据库结构

在此就不贴出来建表的 SQL 语句了 , 感兴趣的可以去我的 Github:mybatis-mapping 中获取。

1.2 实体类, 接口和XML

使用 mybatis-代码生成器 生成相应的实体类, 接口和XML。

代码结构

以上为生成的项目结构。

2 一对多映射

2.1 collection集合映射

2.1.1 创建结果实体类

我们需要创建一个 BlogPostBO,

public class BlogPostBO extends Blog {

    private List<Post> posts;

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }
}

该类继承于 Blog 类, 并多了一个链表成员变量 posts, 我们后续获取到的发布的文章都在此链表中。

2.1.2 创建结果集

刚开始时, 是这样子创建的。

<resultMap id="BlogPostBO"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" columnPrefix="post_" ofType="com.homejim.mybatis.entity.Post">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="blog_id" jdbcType="INTEGER" property="blogId" />
        <result column="draft" jdbcType="INTEGER" property="draft" />
        <result column="content" jdbcType="LONGVARCHAR" property="content" />
    </collection>
</resultMap>

此处注意一个问题, 在 collection 中, 我们用的是 ofType 来表示 List 中的 Pojo 的属性。而不是 type

因为我们内部 Post 有对应的结果集, 可以引用另一个 Mapper 中的结果集, 就可以简化变成下面这样子:

<resultMap id="BlogPostBO"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" columnPrefix="post_" resultMap="com.homejim.mybatis.mapper.PostMapper.ResultMapWithBLOBs" />
</resultMap>

可以简单了好多。

2.1.3 创建对应的方法和XML

首先就是在对应的 Mapper 接口下创建方法:

/**
     * 获取博客及其发布的文章内容 一对多
     * @return
     */
    List<BlogPostBO> selectBlogAndPostList();

同时在 XML 中创建对应的 SQL 语句:

<select id="selectBlogAndPostList"  resultMap="BlogPostBO">
    SELECT
    b.id,
    b.title,
    b.author_id,
    p.id as post_id,
    p.blog_id as post_blog_id,
    p.draft as post_draft,
    p.content as post_content
  FROM blog b left join post p on p.blog_id=b.id
  </select>

2.1.4 测试

/**
     *  resultMap + collection 一对多映射
     */
    @Test
    public void testSelectBlogAndPostListByBlog() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        List<BlogPostBO> blogPostBOs = blogMapper.selectBlogAndPostList();

        sqlSession.close();

        for (int i = 0; i < blogPostBOs.size(); i++) {
            BlogPostBO blogPostBO = blogPostBOs.get(i);
            System.out.println(blogPostBO.getTitle());

            for (int j = 0; j < blogPostBO.getPosts().size(); j++) {
                System.out.println(blogPostBO.getPosts().get(j).getContent());
            }

            System.out.println("=============这是对象分割线===============");
        }
    }

测试结果

运行结果

可以看出, 已经获取到了。

2.1.5 结果合并原理

刚开始的时候, 我们获取到的结果在数据库中应该是这样子的

数据库结果

那么, 结果是怎么变成下面这样子的呢?

代码结果

2.1.5.1 合并的依据

mybatis 在处理结果时, 会判断对象是否相同, 如果相同则会将结果与之前的结果进行合并。

那么, 结果是否相同是如何判断的呢?

首先是通过 id的比较, 如果 id 没有配置则比较每一个字段(此时, 只要有一个字段不同的对象不同)

2.1.5.2 id 的作用

在生成的 XML 中, 如果我们有主键, 一般会生成一个对应的 id 属性。

id 属性就可以用来 判断获取到的数据是否属于同一个对象

在以上的例子中, 数据库中查询出来有三个 id=1, 则 mybatis 在处理结果时就可以知道这三条数据对应相同的对象, 从而将他们合并。

2.1.5.3 id 的作用验证

我们可以测试一下, 将 BlogMapper.xmlBaseResultMap 改成 titleid , 然后将数据库改一下

更改后的数据

那么, 此时博客 id=1 和 博客 id=2title 都为 “ 小明的博客 ”, 那么, 按照以上的推论, 此两博客的对象应该合二为一。

真相揭晓

更改后的结果

小王的博客消失了, 小明的博客发表文章数量从 3 变为 5 , 因为合并了小王博客发布的文章数量。

2.1.5.4 建议

建议尽量配置 id 的属性 , 如果没有这个属性, 则 mybatis 在进行结果合并时效率会低很多。

2.2 collection 嵌套查询方式

2.2.1 创建结果实体类

我们需要创建一个 BlogPostBO,

public class BlogPostBO extends Blog {

    private List<Post> posts;

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }
}

该类继承于 Blog 类, 并多了一个链表成员变量 posts, 我们后续获取到的发布的文章都在此链表中。

2.2.2 创建结果集

结果集中多了一个 select 的属性, 同时, 需要相应的指定 postscolumn

<resultMap id="BlogPostCustom"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" column="{blogId=id}"
    select="com.homejim.mybatis.mapper.PostMapper.selectPostByBlogId" fetchType="lazy" />
</resultMap>

重要说明:

  1. column 中, 需要填写 {属性名=列名(别名)} 的方式(如果只有一个参数, 也可以直接填列名传入参数的即可)。 如果是传递多个参数, 则是 {属性名1=列名1, 属性名2=列名2}
  2. select 属性值 com.homejim.mybatis.mapper.PostMapper.selectPostByBlogId, 对应的 parameterTypejava.util.Map(也可以不填)。
  3. fetchType 的属性值 lazy (延迟加载, 还需要相应的配置) 或 eager 。(更详细的参考我上篇文章[mybatis-高级结果映射之一对一]

2.2.3 创建对应的方法和XML

主方法

/**
     * 获取博客及其发布的文章内容 一对多:延迟加载
     * @return
     */
    List<BlogPostBO> selectBlogAndPostListLazy();

主方法对应的 XML

<select id="selectBlogAndPostListLazy"  resultMap="BlogPostCustom">
    SELECT
      b.id,
      b.title,
      b.author_id
    FROM blog b
</select>

嵌套方法

/**
     *  根据博客 id 获取发布的文章信息
     * @param blogId 博客 id
     * @return
     */
    List<Post> selectPostByBlogId(int blogId);

嵌套方法对应 XML

<select id="selectPostByBlogId" parameterType="java.util.Map" resultMap="ResultMapWithBLOBs">
    select
    <include refid="Base_Column_List" />
    ,
    <include refid="Blob_Column_List" />
    from post
    where blog_id = #{blogId,jdbcType=INTEGER}
  </select>

2.2.4 测试

/**
     *  resultMap + collection 一对多映射, 嵌套查询
     */
    @Test
    public void testSelectBlogAndPostListByBlogLazy() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        List<BlogPostBO> blogPostBOs = blogMapper.selectBlogAndPostListLazy();

        for (int i = 0; i < blogPostBOs.size(); i++) {
            BlogPostBO blogPostBO = blogPostBOs.get(i);
            System.out.println(blogPostBO.getTitle());
            List<Post> posts = blogPostBO.getPosts();

            for (int j = 0; j < posts.size(); j++) {
                System.out.println(blogPostBO.getPosts().get(j).getContent());
            }

            System.out.println("=============这是对象分割线===============");
        }
    }

延迟加载结果

可以看到, 只有在需要使用对象的时候, 才会进行延迟加载。

image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223623
累计阅读

热门教程文档

Spring Cloud
8小节
Typescript
31小节
QT
33小节
Linux
51小节
Dart
35小节