高级结果映射之一对多

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.xml 的 BaseResultMap 改成 title 为 id , 然后将数据库改一下

更改后的数据

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

真相揭晓

更改后的结果

小王的博客消失了, 小明的博客发表文章数量从 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 的属性, 同时, 需要相应的指定 posts 和 column

<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, 对应的 parameterType 是 java.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("=============这是对象分割线==============="); } }

延迟加载结果

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