原文地址: http://www.cnblogs.com/xiaolang8762400/p/7399892.html

 

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对象中;

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

   

  1. /* 
  2.  * 通过blogId获取BlogInfo对象 
  3.  */  
  4. public static BlogInfo ordinaryQueryOnTest(String blogId)  
  5. {  
  6.     BigDecimal id = new BigDecimal(blogId);  
  7.     SqlSession session = sqlSessionFactory.openSession();  
  8.     BlogInfo blogInfo = new BlogInfo();  
  9.     //1.根据blogid 查询Blog对象,将值设置到blogInfo中  
  10.     Blog blog = (Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id);  
  11.     blogInfo.setBlogId(blog.getBlogId());  
  12.     blogInfo.setTitle(blog.getTitle());  
  13.         
  14.     //2.根据Blog中的authorId,进入数据库查询Author信息,将结果设置到blogInfo对象中  
  15.     Author author = (Author)session.selectOne("com.foo.bean.AuthorMapper.selectByPrimaryKey",blog.getAuthorId());  
  16.     blogInfo.setAuthor(author);  
  17.         
  18.     //3.查询posts对象,设置进blogInfo中  
  19.     List posts = session.selectList("com.foo.bean.PostMapper.selectByBlogId",blog.getBlogId());  
  20.     blogInfo.setPosts(posts);  
  21.     //以JSON字符串的形式将对象打印出来  
  22.     JSONObject object = new JSONObject(blogInfo);  
  23.     System.out.println(object.toString());  
  24.     return blogInfo;  
  25. }  

 

 

 

  1. /* 
  2.  * 通过blogId获取BlogInfo对象 
  3.  */  
  4. public static BlogInfo ordinaryQueryOnTest(String blogId)  
  5. {  
  6.     BigDecimal id = new BigDecimal(blogId);  
  7.     SqlSession session = sqlSessionFactory.openSession();  
  8.     BlogInfo blogInfo = new BlogInfo();  
  9.     //1.根据blogid 查询Blog对象,将值设置到blogInfo中  
  10.     Blog blog = (Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id);  
  11.     blogInfo.setBlogId(blog.getBlogId());  
  12.     blogInfo.setTitle(blog.getTitle());  
  13.         
  14.     //2.根据Blog中的authorId,进入数据库查询Author信息,将结果设置到blogInfo对象中  
  15.     Author author = (Author)session.selectOne("com.foo.bean.AuthorMapper.selectByPrimaryKey",blog.getAuthorId());  
  16.     blogInfo.setAuthor(author);  
  17.         
  18.     //3.查询posts对象,设置进blogInfo中  
  19.     List posts = session.selectList("com.foo.bean.PostMapper.selectByBlogId",blog.getBlogId());  
  20.     blogInfo.setPosts(posts);  
  21.     //以JSON字符串的形式将对象打印出来  
  22.     JSONObject object = new JSONObject(blogInfo);  
  23.     System.out.println(object.toString());  
  24.     return blogInfo;  
  25. }  

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

嵌套语句查询

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

   
 

  1. <resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  
  2.     <id column="blog_id" property="blogId" />  
  3.     <result column="title" property="title" />  
  4.     <association property="author" column="blog_author_id"  
  5.         javaType="com.foo.bean.Author" select="com.foo.bean.AuthorMapper.selectByPrimaryKey">  
  6.     </association>  
  7.     <collection property="posts" column="blog_id" ofType="com.foo.bean.Post"  
  8.         select="com.foo.bean.PostMapper.selectByBlogId">  
  9.     </collection>  
  10. </resultMap>  
  11.     
  12. <select id="queryBlogInfoById" resultMap="BlogInfo" parameterType="java.math.BigDecimal">  
  13.     SELECT  
  14.     B.BLOG_ID,  
  15.     B.TITLE,  
  16.     B.AUTHOR_ID AS BLOG_AUTHOR_ID  
  17.     FROM LOULUAN.BLOG B  
  18.     where B.BLOG_ID = #{blogId,jdbcType=DECIMAL}  
  19. </select>  

 

 

  1. <resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  
  2.     <id column="blog_id" property="blogId" />  
  3.     <result column="title" property="title" />  
  4.     <association property="author" column="blog_author_id"  
  5.         javaType="com.foo.bean.Author" select="com.foo.bean.AuthorMapper.selectByPrimaryKey">  
  6.     </association>  
  7.     <collection property="posts" column="blog_id" ofType="com.foo.bean.Post"  
  8.         select="com.foo.bean.PostMapper.selectByBlogId">  
  9.     </collection>  
  10. </resultMap>  
  11.     
  12. <select id="queryBlogInfoById" resultMap="BlogInfo" parameterType="java.math.BigDecimal">  
  13.     SELECT  
  14.     B.BLOG_ID,  
  15.     B.TITLE,  
  16.     B.AUTHOR_ID AS BLOG_AUTHOR_ID  
  17.     FROM LOULUAN.BLOG B  
  18.     where B.BLOG_ID = #{blogId,jdbcType=DECIMAL}  
  19. </select>  

   
 

  1. /* 
  2.  * 通过blogId获取BlogInfo对象 
  3.  */  
  4. public static BlogInfo nestedQueryOnTest(String blogId)  
  5. {  
  6.     BigDecimal id = new BigDecimal(blogId);  
  7.     SqlSession session = sqlSessionFactory.openSession();  
  8.     BlogInfo blogInfo = new BlogInfo();  
  9.     blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);  
  10.     JSONObject object = new JSONObject(blogInfo);  
  11.     System.out.println(object.toString());  
  12.     return blogInfo;  
  13. }  

 

 

  1. /* 
  2.  * 通过blogId获取BlogInfo对象 
  3.  */  
  4. public static BlogInfo nestedQueryOnTest(String blogId)  
  5. {  
  6.     BigDecimal id = new BigDecimal(blogId);  
  7.     SqlSession session = sqlSessionFactory.openSession();  
  8.     BlogInfo blogInfo = new BlogInfo();  
  9.     blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);  
  10.     JSONObject object = new JSONObject(blogInfo);  
  11.     System.out.println(object.toString());  
  12.     return blogInfo;  
  13. }  

 

通过上述的代码完全可以实现前面的那个查询。这里我们在代码里只需要 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 

   
 

  1. <resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  
  2.     <id column="blog_id" property="blogId"/>  
  3.     <result column="title" property="title"/>  
  4.     <association property="author" column="blog_author_id" javaType="com.foo.bean.Author">  
  5.         <id column="author_id" property="authorId"/>  
  6.         <result column="user_name" property="userName"/>  
  7.         <result column="password" property="password"/>  
  8.         <result column="email" property="email"/>  
  9.         <result column="biography" property="biography"/>  
  10.     </association>  
  11.     <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">  
  12.         <id column="post_id" property="postId"/>  
  13.         <result column="blog_id" property="blogId"/>  
  14.         <result column="create_time" property="createTime"/>  
  15.         <result column="subject" property="subject"/>  
  16.         <result column="body" property="body"/>  
  17.         <result column="draft" property="draft"/>  
  18.     </collection>  
  19.         
  20. </resultMap>  

 

 

  1. <resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  
  2.     <id column="blog_id" property="blogId"/>  
  3.     <result column="title" property="title"/>  
  4.     <association property="author" column="blog_author_id" javaType="com.foo.bean.Author">  
  5.         <id column="author_id" property="authorId"/>  
  6.         <result column="user_name" property="userName"/>  
  7.         <result column="password" property="password"/>  
  8.         <result column="email" property="email"/>  
  9.         <result column="biography" property="biography"/>  
  10.     </association>  
  11.     <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">  
  12.         <id column="post_id" property="postId"/>  
  13.         <result column="blog_id" property="blogId"/>  
  14.         <result column="create_time" property="createTime"/>  
  15.         <result column="subject" property="subject"/>  
  16.         <result column="body" property="body"/>  
  17.         <result column="draft" property="draft"/>  
  18.     </collection>  
  19.         
  20. </resultMap>  

对应的sql语句如下:

   
 

  1. <select id="queryAllBlogInfo" resultMap="BlogInfo">  
  2.     SELECT   
  3.      B.BLOG_ID,  
  4.      B.TITLE,  
  5.      B.AUTHOR_ID AS BLOG_AUTHOR_ID,  
  6.      A.AUTHOR_ID,  
  7.      A.USER_NAME,  
  8.      A.PASSWORD,  
  9.      A.EMAIL,  
  10.      A.BIOGRAPHY,  
  11.      P.POST_ID,  
  12.      P.BLOG_ID   AS BLOG_POST_ID ,  
  13.   P.CREATE_TIME,  
  14.      P.SUBJECT,  
  15.      P.BODY,  
  16.      P.DRAFT  
  17. FROM BLOG B  
  18. LEFT OUTER JOIN AUTHOR A  
  19.   ON B.AUTHOR_ID = A.AUTHOR_ID  
  20. LEFT OUTER JOIN POST P  
  21.   ON P.BLOG_ID = B.BLOG_ID  
  22. </select>  

 

 

  1. <select id="queryAllBlogInfo" resultMap="BlogInfo">  
  2.     SELECT   
  3.      B.BLOG_ID,  
  4.      B.TITLE,  
  5.      B.AUTHOR_ID AS BLOG_AUTHOR_ID,  
  6.      A.AUTHOR_ID,  
  7.      A.USER_NAME,  
  8.      A.PASSWORD,  
  9.      A.EMAIL,  
  10.      A.BIOGRAPHY,  
  11.      P.POST_ID,  
  12.      P.BLOG_ID   AS BLOG_POST_ID ,  
  13.   P.CREATE_TIME,  
  14.      P.SUBJECT,  
  15.      P.BODY,  
  16.      P.DRAFT  
  17. FROM BLOG B  
  18. LEFT OUTER JOIN AUTHOR A  
  19.   ON B.AUTHOR_ID = A.AUTHOR_ID  
  20. LEFT OUTER JOIN POST P  
  21.   ON P.BLOG_ID = B.BLOG_ID  
  22. </select>  

   
 

  1. /* 
  2.  * 获取所有Blog的所有信息 
  3.  */  
  4. public static BlogInfo nestedResultOnTest()  
  5. {  
  6.     SqlSession session = sqlSessionFactory.openSession();  
  7.     BlogInfo blogInfo = new BlogInfo();  
  8.     blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryAllBlogInfo");  
  9.     JSONObject object = new JSONObject(blogInfo);  
  10.     System.out.println(object.toString());  
  11.     return blogInfo;  
  12. }  

 

 

  1. /* 
  2.  * 获取所有Blog的所有信息 
  3.  */  
  4. public static BlogInfo nestedResultOnTest()  
  5. {  
  6.     SqlSession session = sqlSessionFactory.openSession();  
  7.     BlogInfo blogInfo = new BlogInfo();  
  8.     blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryAllBlogInfo");  
  9.     JSONObject object = new JSONObject(blogInfo);  
  10.     System.out.println(object.toString());  
  11.     return blogInfo;  
  12. }  

 

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

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>;

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

Mybatis关联查询(转载)的更多相关文章

  1. Mybatis关联查询和数据库不一致问题分析与解决

    Mybatis关联查询和数据库不一致问题分析与解决 本文的前提是,确定sql语句没有问题,确定在数据库中使用sql和项目中结果不一致. 在使用SpringMVC+Mybatis做多表关联时候,发现也不 ...

  2. MyBatis基础:MyBatis关联查询(4)

    1. MyBatis关联查询简介 MyBatis中级联分为3中:association.collection及discriminator. ◊ association:一对一关联 ◊ collecti ...

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

    实体关系图,一个国家对应多个城市 一对多关联查询可用三种方式实现: 单步查询,利用collection标签为级联属性赋值: 分步查询: 利用association标签进行分步查询: 利用collect ...

  4. mybatis 关联查询实现一对多

    场景:最近接到一个项目是查询管理人集合  同时每一个管理人还存在多个出资人   要查询一个管理人列表  每个管理人又包含了出资人列表 采用mybatis关联查询实现返回数据. 实现方式: 1 .在实体 ...

  5. MyBatis关联查询、多条件查询

    MyBatis关联查询.多条件查询 1.一对一查询 任务需求; 根据班级的信息查询出教师的相关信息 1.数据库表的设计 班级表: 教师表: 2.实体类的设计 班级表: public class Cla ...

  6. Mybatis关联查询之二

    Mybatis关联查询之多对多 多对多 一.entity实体类 public class Student { private Integer stuid; private String stuname ...

  7. mybatis关联查询基础----高级映射

    本文链接地址:mybatis关联查询基础----高级映射(一对一,一对多,多对多) 前言: 今日在工作中遇到了一个一对多分页查询的问题,主表一条记录对应关联表四条记录,关联分页查询后每页只显示三条记录 ...

  8. MyBatis关联查询和懒加载错误

    MyBatis关联查询和懒加载错误 今天在写项目时遇到了个BUG.先说一下背景,前端请求更新生产订单状态,后端从前端接收到生产订单ID进行查询,然后就有问题了. 先看控制台报错: org.apache ...

  9. Spring+SpringMVC+MyBatis深入学习及搭建(六)——MyBatis关联查询

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6923464.html 前面有将到:Spring+SpringMVC+MyBatis深入学习及搭建(五)--动 ...

随机推荐

  1. IDEA 2019.2.2破解激活方法(激活到2089年8月,亲测有效)

    本来笔者这边是有个正版激活码可以使用的,但是,2019.9月3号的时候,一些小伙伴反映这个注册码已经失效了,于是拿着自己的 IDEA, 赶快测试了一下,果不其然,已然是不能用了. 好在,笔者又找到了新 ...

  2. nRF5 SDK Bootloader and DFU moudles(2)

    镜像的验证 在执行设备固件更新之前,应验证新映像. 在传输实际固件(预验证)之前,可以检查某些信息(例如,兼容性). 其他信息,例如图像的散列,应在传输(验证后)后进行验证. Init packet ...

  3. TP5 isEmpty() 判空方法 所用场景

    1, { }类型 { "id": 1, "name": "首页置顶", "description": "首页轮 ...

  4. 2019牛客暑期多校训练营(第八场)-A All-one Matrices (单调栈+前缀和)

    题目链接:https://ac.nowcoder.com/acm/contest/888/A 题意:给n×m的01矩阵,求出其中全为1的矩阵个数(不被其它全1矩阵包括). 思路:和第二场多校的次大子矩 ...

  5. [转帖]为何 linux 要用 tar.gz,而不用 7z 或 zip?

    为何 linux 要用 tar.gz,而不用 7z 或 zip? http://embeddedlinux.org.cn/emb-linux/entry-level/201908/13-8776.ht ...

  6. 避免同一个文件被#include多次,可以用以下两种方式

    1.#ifndef方式 2.#pragma once方式 方式一: #ifndef  _SOMEFILE_H_ #define _SOMEFILE_H_ ... ...//一些声明语句    #end ...

  7. python3列表、元组

    列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作.列表中的每个元素都分配一个数字也就是它的位置,或叫索引,第一个索引是0,第二个索引是1,依此类推. ...

  8. Codeforces 1244D. Paint the Tree

    传送门 首先如果某个点的度数大于 $2$ 那么显然无解 然后考虑点的度数小于等于 $2$ 的情况 发现其实是一条链 一旦确定了链开头的两个点,后面的点的颜色都可以通过之前的点推出 所以直接枚举即可 # ...

  9. JSONObject 的使用

    1. 导入依赖 这里以 20180813 的 json 版本为例 <dependency> <groupId>org.json</groupId> <arti ...

  10. X-Router软路由设置

    一 内网:     ip   192.168.0.1      掩码  255.255.255.0      网关   (空)     DNS   202.96.128.68(佛山的)手动写入 二 外 ...