mybatis关联查询问题(一对多、多对一)

mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的Java Bean 中。下面通过一个实例,来展示一下Mybatis对于常见的一对多和多对一关系复杂映射是怎样处理的。

设计一个简单的博客系统,一个用户可以开多个博客,在博客中可以发表文章,允许发表评论,可以为文章加标签。博客系统主要有以下几张表构成:

Author表:作者信息表,记录作者的信息,用户名和密码,邮箱等。

Blog表   :  博客表,一个作者可以开多个博客,即Author和Blog的关系是一对多。

Post表  : 文章记录表,记录文章发表时间,标题,正文等信息;一个博客下可以有很多篇文章,Blog 和Post的关系是一对多。

Comments表:文章评论表,记录文章的评论,一篇文章可以有很多个评论:Post和Comments的对应关系是一对多。

Tag表:标签表,表示文章的标签分类,一篇文章可以有多个标签,而一个标签可以应用到不同的文章上,所以Tag和Post的关系是多对多的关系;(Tag和Post的多对多关系通过Post_Tag表体现)

Post_Tag表: 记录 文章和标签的对应关系。

一般情况下,我们会根据每一张表的结构 创建与此相对应的JavaBean(或者Pojo),来完成对表的基本CRUD操作。

上述对单个表的JavaBean定义有时候不能满足业务上的需求。在业务上,一个Blog对象应该有其作者的信息和一个文章列表,如下图所示:

如果想得到这样的类的实例,则最起码要有一下几步:

1. 通过Blog 的id 到Blog表里查询Blog信息,将查询到的blogId 和title 赋到Blog对象内;

2. 根据查询到到blog信息中的authorId 去 Author表获取对应的author信息,获取Author对象,然后赋到Blog对象内;

3. 根据 blogId 去 Post表里查询 对应的 Post文章列表,将List<Post>对象赋到Blog对象中;

这样的话,在底层最起码调用三次查询语句,请看下列的代码:

	/*
	 * 通过blogId获取BlogInfo对象
	 */
	public static BlogInfo ordinaryQueryOnTest(String blogId)
	{
		BigDecimal id = new BigDecimal(blogId);
		SqlSession session = sqlSessionFactory.openSession();
		BlogInfo blogInfo = new BlogInfo();
		//1.根据blogid 查询Blog对象,将值设置到blogInfo中
		Blog blog = (Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id);
		blogInfo.setBlogId(blog.getBlogId());
		blogInfo.setTitle(blog.getTitle());

		//2.根据Blog中的authorId,进入数据库查询Author信息,将结果设置到blogInfo对象中
		Author author = (Author)session.selectOne("com.foo.bean.AuthorMapper.selectByPrimaryKey",blog.getAuthorId());
		blogInfo.setAuthor(author);

		//3.查询posts对象,设置进blogInfo中
		List posts = session.selectList("com.foo.bean.PostMapper.selectByBlogId",blog.getBlogId());
		blogInfo.setPosts(posts);
		//以JSON字符串的形式将对象打印出来
		JSONObject object = new JSONObject(blogInfo);
		System.out.println(object.toString());
		return blogInfo;
	}

点击我下载源码

从上面的代码可以看出,想获取一个BlogInfo对象比较麻烦,总共要调用三次数据库查询,得到需要的信息,然后再组装BlogInfo对象。

嵌套语句查询

mybatis提供了一种机制,叫做嵌套语句查询,可以大大简化上述的操作,加入配置及代码如下:

	<resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">
		<id column="blog_id" property="blogId" />
		<result column="title" property="title" />
		<association property="author" column="blog_author_id"
			javaType="com.foo.bean.Author" select="com.foo.bean.AuthorMapper.selectByPrimaryKey">
		</association>
		<collection property="posts" column="blog_id" ofType="com.foo.bean.Post"
			select="com.foo.bean.PostMapper.selectByBlogId">
		</collection>
	</resultMap>

	<select id="queryBlogInfoById" resultMap="BlogInfo" parameterType="java.math.BigDecimal">
		SELECT
		B.BLOG_ID,
		B.TITLE,
		B.AUTHOR_ID AS BLOG_AUTHOR_ID
		FROM LOULUAN.BLOG B
		where B.BLOG_ID = #{blogId,jdbcType=DECIMAL}
	</select>
	/*
	 * 通过blogId获取BlogInfo对象
	 */
	public static BlogInfo nestedQueryOnTest(String blogId)
	{
		BigDecimal id = new BigDecimal(blogId);
		SqlSession session = sqlSessionFactory.openSession();
		BlogInfo blogInfo = new BlogInfo();
		blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);
		JSONObject object = new JSONObject(blogInfo);
		System.out.println(object.toString());
		return blogInfo;
	}

通过上述的代码完全可以实现前面的那个查询。这里我们在代码里只需要 blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);一句即可获取到复杂的blogInfo对象。

嵌套语句查询的原理

在上面的代码中,Mybatis会执行以下流程:

1.先执行 queryBlogInfoById 对应的语句从Blog表里获取到ResultSet结果集;

2.取出ResultSet下一条有效记录,然后根据resultMap定义的映射规格,通过这条记录的数据来构建对应的一个BlogInfo 对象。

3. 当要对BlogInfo中的author属性进行赋值的时候,发现有一个关联的查询,此时Mybatis会先执行这个select查询语句,得到返回的结果,将结果设置到BlogInfo的author属性上;

4. 对BlogInfo的posts进行赋值时,也有上述类似的过程。

5. 重复2步骤,直至ResultSet. next () == false;

以下是blogInfo对象构造赋值过程示意图:

这种关联的嵌套查询,有一个非常好的作用就是:可以重用select语句,通过简单的select语句之间的组合来构造复杂的对象。上面嵌套的两个select语句com.foo.bean.AuthorMapper.selectByPrimaryKey和com.foo.bean.PostMapper.selectByBlogId完全可以独立使用。

N+1问题

它的弊端也比较明显:即所谓的N+1问题。关联的嵌套查询显示得到一个结果集,然后根据这个结果集的每一条记录进行关联查询。

现在假设嵌套查询就一个(即resultMap 内部就一个association标签),现查询的结果集返回条数为N,那么关联查询语句将会被执行N次,加上自身返回结果集查询1次,共需要访问数据库N+1次。如果N比较大的话,这样的数据库访问消耗是非常大的!所以使用这种嵌套语句查询的使用者一定要考虑慎重考虑,确保N值不会很大。

以上面的例子为例,select 语句本身会返回com.foo.bean.BlogMapper.queryBlogInfoById 条数为1 的结果集,由于它有两条关联的语句查询,它需要共访问数据库 1*(1+1)=3次数据库。

嵌套结果查询

嵌套语句的查询会导致数据库访问次数不定,进而有可能影响到性能。Mybatis还支持一种嵌套结果的查询:即对于一对多,多对多,多对一的情况的查询,Mybatis通过联合查询,将结果从数据库内一次性查出来,然后根据其一对多,多对一,多对多的关系和ResultMap中的配置,进行结果的转换,构建需要的对象。

重新定义BlogInfo的结果映射 resultMap

  <resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">
      <id column="blog_id" property="blogId"/>
      <result column="title" property="title"/>
      <association property="author" column="blog_author_id" javaType="com.foo.bean.Author">
          <id column="author_id" property="authorId"/>
          <result column="user_name" property="userName"/>
          <result column="password" property="password"/>
          <result column="email" property="email"/>
          <result column="biography" property="biography"/>
      </association>
      <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">
          <id column="post_id" property="postId"/>
          <result column="blog_id" property="blogId"/>
          <result column="create_time" property="createTime"/>
          <result column="subject" property="subject"/>
          <result column="body" property="body"/>
          <result column="draft" property="draft"/>
      </collection>

  </resultMap>

对应的sql语句如下:

  <select id="queryAllBlogInfo" resultMap="BlogInfo">
      SELECT
       B.BLOG_ID,
       B.TITLE,
       B.AUTHOR_ID AS BLOG_AUTHOR_ID,
       A.AUTHOR_ID,
       A.USER_NAME,
       A.PASSWORD,
       A.EMAIL,
       A.BIOGRAPHY,
       P.POST_ID,
       P.BLOG_ID   AS BLOG_POST_ID ,
	   P.CREATE_TIME,
       P.SUBJECT,
       P.BODY,
       P.DRAFT
  FROM BLOG B
  LEFT OUTER JOIN AUTHOR A
    ON B.AUTHOR_ID = A.AUTHOR_ID
  LEFT OUTER JOIN POST P
    ON P.BLOG_ID = B.BLOG_ID
  </select>
	/*
	 * 获取所有Blog的所有信息
	 */
	public static BlogInfo nestedResultOnTest()
	{
		SqlSession session = sqlSessionFactory.openSession();
		BlogInfo blogInfo = new BlogInfo();
		blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryAllBlogInfo");
		JSONObject object = new JSONObject(blogInfo);
		System.out.println(object.toString());
		return blogInfo;
	}

嵌套结果查询的执行步骤:

1.根据表的对应关系,进行join操作,获取到结果集;

2. 根据结果集的信息和BlogInfo 的resultMap定义信息,对返回的结果集在内存中进行组装、赋值,构造BlogInfo;

3. 返回构造出来的结果List<BlogInfo> 结果。

对于关联的结果查询,如果是多对一的关系,则通过形如 <association property="author" column="blog_author_id" javaType="com.foo.bean.Author"> 进行配置,Mybatis会通过column属性对应的author_id 值去从内存中取数据,并且封装成Author对象;

如果是一对多的关系,就如Blog和Post之间的关系,通过形如 <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">进行配置,MyBatis通过 blog_Id去内存中取Post对象,封装成List<Post>;

对于关联结果的查询,只需要查询数据库一次,然后对结果的整合和组装全部放在了内存中。

以上是通过查询Blog所有信息来演示了一对多和多对一的映射对象处理。

mybatis关联查询问题(一对多、多对一)

时间: 2024-10-21 13:50:05

mybatis关联查询问题(一对多、多对一)的相关文章

MyBatis关联查询,一对多关联查询

实体关系图,一个国家对应多个城市 一对多关联查询可用三种方式实现: 单步查询,利用collection标签为级联属性赋值: 分步查询: 利用association标签进行分步查询: 利用collection标签进行分步查询 单步查询 利用collection标签实现一对多单步关联查询: 指定进行关联查询的Java Bean字段,即collection标签的 property 属性: 指定集合中的Java Bean类型,即collection标签的 ofType属性: 实体类 public cla

MyBatis 关联查询的实现:多对多

2个实体:订单.商品,一个订单可以包含多种商品,同时一种商品可以属于多个订单,即多对多. 商品表goods_tb: 订单表order_tb: no是订单编号,user_id与用户表的id关联. 需要新建一张中间表order_item_tb,引入2个“多”的主键作为外键,把这2个“多”联系起来: purchase_amount是该种商品的购买数量. 使用嵌套结果实现一对多 (一)编写pojo类 package com.chy.pojo; public class Goods { private I

8.MyBatis 关联查询(一对多)

ResultMap的collection的嵌套结果 在OrdersExt类中添加以下属性,并提供get/set方法: // 订单明细 private List<Orderdetail> detailList; 编写mapper接口 //查询订单信息,包括用户名称.用户地址,订单商品信息(嵌套结果) public List<OrdersExt> findOrdersResultMapOfCollection(); 编写映射文件 <!-- 定义orderUserItemResul

【Mybatis】Mybatis关联查询一对一和一对多的实现

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文主要讲了使用Mybatis实现关联查询,分为一对一和一对多两种情况,最后并对ResultMap进行一个简要说明. 一.创建表.分析 下面是两表,一个是顾客表,一个是车票表.一个顾客可以对应多张车票,但是一张车票只能对应一个顾客 t_customer:顾客表,一个顾客可以对应多张车票 t_ticket:车票表,一张车票只能对应一个顾客 1.创建数据表及插入初始数据 创建数据表 use te

mybatis关联查询,一对一,一对多

注:这篇文章的代码有部分删减,不能直接使用,不过关键代码都存在  应用场景: 想用mybatis做关联查询,并且把查询出的数据自动组装成对象可以使用关联查询. 1.一对一实现 例如:一部小说,属于一个分类,查询小说的时候想同时查询出所属分类. 1)实体定义: public class Book { private static final long serialVersionUID = 1L; /** *小说ID */ private Integer bookId; /** *作者 */ pri

Mybatis关联查询一对一和一对多的实现

Mybatis一对一关联查询 有两张表,老师表teacher和班级表class,一个class班级对应一个teacher,一个teacher对应一个class 需求是根据班级id查询班级信息(带老师的信息) 创建teacher和class表: CREATE TABLE teacher ( t_id INT PRIMARY KEY AUTO_INCREMENT, t_name VARCHAR(20) ); CREATE TABLE class ( c_id INT PRIMARY KEY AUTO

Mybatis关联查询(转载)

原文地址: http://www.cnblogs.com/xiaolang8762400/p/7399892.html mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的Java Bean 中.下面通过一个实例,来展示一下Mybatis对于常见的一对多和多对一关系复杂映射是怎样处理的. 设计一个简单的博客系统,一个用户可以开多个博客,在博客中可以发表文章,允许发表评论,可以为文章加标签.博客系统主要有以下几张表构成: Author表:作者信息表,记录作者的信息

Spring+SpringMVC+MyBatis深入学习及搭建(六)——MyBatis关联查询(转发同上)

原地址:http://www.cnblogs.com/shanheyongmu/p/7122520.html 1.商品订单数据模型 1.1数据模型分析思路 (1)每张表记录的数据内容 分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程. (2)每张表重要的字段设置 非空字段.外键字段 (3)数据库级别表与表之间的关系 外键关系 (4)表与表之间的业务关系 在分析表与表之间的业务关系时,一定要建立在某个业务意义基础上去分析. 1.2属性模型分析 2.一对一查询 2.1需求 查询订

MyBatis关联查询分页

背景:单表好说,假如是MySQL的话,直接limit就行了. 对于多对多或者一对多的情况,假如分页的对象不是所有结果集,而是对一边分页,那么可以采用子查询分页,再与另外一张表关联查询,比如: select * from (select * from teacher t limit 0, 2) tt left join clazz ttt on tt.id = ttt.teacher_id; 先对teacher分页,再关联查询.但是这样一来就不太好用mybatis的分页插件统一分页,并且需要自己去