欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

实例讲解Java的MyBatis框架对MySQL中数据的关联查询

程序员文章站 2024-03-12 14:01:20
mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的java bean 中。下面通过一个实例,来展示一下mybatis对于常见的一对多和多...

mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的java bean 中。下面通过一个实例,来展示一下mybatis对于常见的一对多和多对一关系复杂映射是怎样处理的。
设计一个简单的博客系统,一个用户可以开多个博客,在博客中可以发表文章,允许发表评论,可以为文章加标签。博客系统主要有以下几张表构成:
author表:作者信息表,记录作者的信息,用户名和密码,邮箱等。
blog表   :  博客表,一个作者可以开多个博客,即author和blog的关系是一对多。
post表  : 文章记录表,记录文章发表时间,标题,正文等信息;一个博客下可以有很多篇文章,blog 和post的关系是一对多。
comments表:文章评论表,记录文章的评论,一篇文章可以有很多个评论:post和comments的对应关系是一对多。
tag表:标签表,表示文章的标签分类,一篇文章可以有多个标签,而一个标签可以应用到不同的文章上,所以tag和post的关系是多对多的关系;(tag和post的多对多关系通过post_tag表体现)
post_tag表: 记录 文章和标签的对应关系。

实例讲解Java的MyBatis框架对MySQL中数据的关联查询

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

实例讲解Java的MyBatis框架对MySQL中数据的关联查询

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

实例讲解Java的MyBatis框架对MySQL中数据的关联查询

如果想得到这样的类的实例,则最起码要有一下几步:
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对象构造赋值过程示意图:

实例讲解Java的MyBatis框架对MySQL中数据的关联查询

这种关联的嵌套查询,有一个非常好的作用就是:可以重用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所有信息来演示了一对多和多对一的映射对象处理。

ps:自身关联映射示例:
实体类

public class module { 
 
 private int id; 
 private string key; 
 private string name; 
 private module parentmodule; 
 private list<module> childrenmodules; 
 private string url; 
 private int sort; 
 private string show; 
 private string del; 
 
 public int getid() { 
  return id; 
 } 
 
 public void setid(int id) { 
  this.id = id; 
 } 
 
 public string getkey() { 
  return key; 
 } 
 
 public void setkey(string key) { 
  this.key = key; 
 } 
 
 public string getname() { 
  return name; 
 } 
 
 public void setname(string name) { 
  this.name = name; 
 } 
 
 public module getparentmodule() { 
  return parentmodule; 
 } 
 
 public void setparentmodule(module parentmodule) { 
  this.parentmodule = parentmodule; 
 } 
 
 public string geturl() { 
  return url; 
 } 
 
 public void seturl(string url) { 
  this.url = url; 
 } 
 
 public int getsort() { 
  return sort; 
 } 
 
 public void setsort(int sort) { 
  this.sort = sort; 
 } 
 
 public string getshow() { 
  return show; 
 } 
 
 public void setshow(string show) { 
  this.show = show; 
 } 
 
 public string getdel() { 
  return del; 
 } 
 
 public void setdel(string del) { 
  this.del = del; 
 } 
 
 public list<module> getchildrenmodules() { 
  return childrenmodules; 
 } 
 
 public void setchildrenmodules(list<module> childrenmodules) { 
  this.childrenmodules = childrenmodules; 
 } 
} 

xml代码:
<mapper namespace="com.sagaware.caraccess.mapper.modulemapper"> 
 
 <resultmap type="module" id="moduleresultmap"> 
  <id property="id" column="module_id"/> 
  <result property="key" column="module_key"/> 
  <result property="name" column="module_name"/> 
  <result property="url" column="module_url"/> 
  <result property="sort" column="module_sort"/> 
  <result property="show" column="module_show"/> 
  <result property="del" column="module_del"/> 
   
  <!-- 查询父模块 --> 
  <association property="parentmodule" column="module_parent_id" select="getmodulesbyid" /> 
   
  <!-- 查询子模块 --> 
  <collection property="childrenmodules" column="module_id" select="getchildrenmodues" /> 
   
 </resultmap> 
  
 <select id="getmodules" parametertype="string" resultmap="moduleresultmap"> 
  select * from tb_module where module_id=2 
 </select> 
  
 <select id="getmodulesbyid" parametertype="int" resultmap="moduleresultmap"> 
  select * from tb_module where module_id = #{module_id} 
 </select> 
  
 <select id="getchildrenmodues" parametertype="int" resultmap="moduleresultmap"> 
  select * from tb_module where module_parent_id = #{module_id} 
 </select> 
</mapper>