在一对多的关系中, 主表的数据回对应关联表中的多条数据。 因此, 查询时就会查询出多条结果, 所以, 向类似的情况我们会使用 List
来进行存储关联表中获取到的信息。
创建以下的名为 mybatis 的数据库, 并在其下创建4个表。
在此就不贴出来建表的 SQL 语句了 , 感兴趣的可以去我的 Github:mybatis-mapping 中获取。
使用 mybatis-代码生成器 生成相应的实体类, 接口和XML。
以上为生成的项目结构。
我们需要创建一个 BlogPostBO
,
public class BlogPostBO extends Blog {
private List posts;
public List getPosts() {
return posts;
}
public void setPosts(List posts) {
this.posts = posts;
}
}
该类继承于 Blog
类, 并多了一个链表成员变量 posts
, 我们后续获取到的发布的文章都在此链表中。
刚开始时, 是这样子创建的。
此处注意一个问题, 在 collection 中, 我们用的是 ofType 来表示 List
中的 Pojo
的属性。而不是 type。
因为我们内部 Post
有对应的结果集, 可以引用另一个 Mapper
中的结果集, 就可以简化变成下面这样子:
可以简单了好多。
首先就是在对应的 Mapper 接口下创建方法:
/**
* 获取博客及其发布的文章内容 一对多
* @return
*/
List selectBlogAndPostList();
同时在 XML 中创建对应的 SQL 语句:
/**
* resultMap + collection 一对多映射
*/
@Test
public void testSelectBlogAndPostListByBlog() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
List 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("=============这是对象分割线===============");
}
}
测试结果
可以看出, 已经获取到了。
刚开始的时候, 我们获取到的结果在数据库中应该是这样子的
那么, 结果是怎么变成下面这样子的呢?
mybatis 在处理结果时, 会判断对象是否相同, 如果相同则会将结果与之前的结果进行合并。
那么, 结果是否相同是如何判断的呢?
首先是通过 id的比较, 如果 id 没有配置则比较每一个字段(此时, 只要有一个字段不同的对象不同)。
在生成的 XML 中, 如果我们有主键, 一般会生成一个对应的 id 属性。
而 id 属性就可以用来判断获取到的数据是否属于同一个对象。
在以上的例子中, 数据库中查询出来有三个 id=1, 则 mybatis 在处理结果时就可以知道这三条数据对应相同的对象, 从而将他们合并。
我们可以测试一下, 将 BlogMapper.xml 的 BaseResultMap 改成 title 为 id, 然后将数据库改一下
那么, 此时博客 id=1 和 博客 id=2 的 title 都为 “小明的博客”, 那么, 按照以上的推论, 此两博客的对象应该合二为一。
真相揭晓
小王的博客消失了, 小明的博客发表文章数量从 3 变为 5 , 因为合并了小王博客发布的文章数量。
建议尽量配置 id 的属性, 如果没有这个属性, 则 mybatis 在进行结果合并时效率会低很多。
我们需要创建一个 BlogPostBO
,
public class BlogPostBO extends Blog {
private List posts;
public List getPosts() {
return posts;
}
public void setPosts(List posts) {
this.posts = posts;
}
}
该类继承于 Blog
类, 并多了一个链表成员变量 posts
, 我们后续获取到的发布的文章都在此链表中。
结果集中多了一个 select 的属性, 同时, 需要相应的指定 posts 和 column
重要说明:
column 中, 需要填写 {属性名=列名(别名)}
的方式(如果只有一个参数, 也可以直接填列名传入参数的即可)。 如果是传递多个参数, 则是 {属性名1=列名1, 属性名2=列名2}
。
select 属性值 com.homejim.mybatis.mapper.PostMapper.selectPostByBlogId
, 对应的 parameterType 是 java.util.Map
(也可以不填)。
fetchType 的属性值 lazy(延迟加载, 还需要相应的配置) 或 eager。(更详细的参考我上篇文章[mybatis-高级结果映射之一对一])
主方法
/**
* 获取博客及其发布的文章内容 一对多:延迟加载
* @return
*/
List selectBlogAndPostListLazy();
主方法对应的 XML
嵌套方法
/**
* 根据博客 id 获取发布的文章信息
* @param blogId 博客 id
* @return
*/
List selectPostByBlogId(int blogId);
嵌套方法对应 XML
/**
* resultMap + collection 一对多映射, 嵌套查询
*/
@Test
public void testSelectBlogAndPostListByBlogLazy() {
SqlSession sqlSession = sqlSessionFactory.openSession();
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
List blogPostBOs = blogMapper.selectBlogAndPostListLazy();
for (int i = 0; i < blogPostBOs.size(); i++) {
BlogPostBO blogPostBO = blogPostBOs.get(i);
System.out.println(blogPostBO.getTitle());
List posts = blogPostBO.getPosts();
for (int j = 0; j < posts.size(); j++) {
System.out.println(blogPostBO.getPosts().get(j).getContent());
}
System.out.println("=============这是对象分割线===============");
}
}
可以看到, 只有在需要使用对象的时候, 才会进行延迟加载。
我的 Github:mybatis-mapping