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

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所有信息来演示了一对多和多对一的映射对象处理。

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>

(0)

相关推荐

  • Mybatis 一对多和多对一关联查询问题

    首先  数据库量表之间字段关系(没有主外键) studentmajor表的id字段对应student表里major字段 两个实体类 package com.model; import java.util.Date; public class Student { private Integer sno; private String sname; private String ssex; private Integer sclass; private StudentMajor studentmaj

  • MyBatis学习笔记(二)之关联关系

    今天主要学习的关联关系是一对一关系与一对多关系. 一.一对一关系 还是通过例子来解释说明.(一个妻子对应一个丈夫). 1)数据库信息 create table t_wife( id int primary key auto_increment, wife_name varchar(), fk_husband_id int ); create table t_husband( id int primary key auto_increment, husband_name varchar() );

  • mybatis实现表与对象的关联关系_动力节点Java学院整理

    所需要用到的其他工具或技术: 项目管理工具 : Maven 测试运行工具 : Junit 数据库 : Derby Maven Dependencies: <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.7</version> </dependen

  • 深入浅出MyBatis中映射文件和实体类的关联性

    mybatis的映射文件写法多种多样,不同的写法和用法,在实际开发过程中所消耗的开发时间.维护时间有很大差别,今天我就把我认为比较简单的一种映射文件写法记录下来,供大家修改建议,争取找到一个最优写法~~: 以User对象和UserMap.xml为例讲解,代码如下: User为用户实体类(仅作为讲解,可以只关注引用类型变量,get/set方法省略): import com.google.common.collect.Lists; import com.gukeer.common.persisten

  • Mybatis多表关联查询的实现(DEMO)

    概要 本节要实现的是多表关联查询的简单demo.场景是根据id查询某商品分类信息,并展示该分类下的商品列表. 一.Mysql测试数据 新建表Category(商品分类)和Product(商品),并插入几条测试数据. create table Category ( Id int not null auto_increment, Name varchar(80) null, constraint pk_category primary key (Id) ); INSERT INTO category

  • MyBatis实践之动态SQL及关联查询

    序言 MyBatis,大家都知道,半自动的ORM框架,原来叫ibatis,后来好像是10年apache软件基金组织把它托管给了goole code,就重新命名了MyBatis,功能相对以前更强大了.它相对全自动的持久层框架Hibernate,更加灵活,更轻量级,这点我还是深有体会的. MyBatis的一个强大特性之一就是动态SQL能力了,能省去我们很多串联判断拼接SQL的痛苦,根据项目而定,在一定的场合下使用,能大大减少程序的代码量和复杂程度,不过还是不是过度太过复杂的使用,以免不利于后期的维护

  • MyBatis学习教程(五)-实现关联表查询方法详解

    一.一对一关联  1.1.提出需求 根据班级id查询班级信息(带老师的信息) 1.2.创建表和数据 创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关系. CREATE TABLE teacher( t_id INT PRIMARY KEY AUTO_INCREMENT, t_name VARCHAR() ); CREATE TABLE class( c_id INT PRIMARY KEY AUTO_INCREMENT, c_name VAR

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

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

  • 利用Java的MyBatis框架获取MySQL中插入记录时的自增主键

    第一步: 在Mybatis Mapper文件中添加属性"useGeneratedKeys"和"keyProperty",其中keyProperty是Java对象的属性名! <insert id="insert" parameterType="Spares" useGeneratedKeys="true" keyProperty="id"> insert into spares

  • MySQL中三种关联查询方式的简单比较

    看看下面三个关联查询的 SQL 语句有何区别? SELECT * FROM film JOIN film_actor ON (film.film_id = film_actor.film_id) SELECT * FROM film JOIN film_actor USING (film_id) SELECT * FROM film, film_actor WHERE film.film_id = film_actor.film_id 最大的不同更多是语法糖,但有一些有意思的东西值得关注. 为了

  • 实例讲解Java的Spring框架中的控制反转和依赖注入

    近来总是接触到 IoC(Inversion of Control,控制反转).DI(Dependency Injection,依赖注入)等编程原则或者模式,而这些是著名 Java 框架 Spring.Struts 等的核心所在.针对此查了 Wikipedia 中各个条目,并从图书馆借来相关书籍,阅读后有些理解,现结合书中的讲解以及自己的加工整理如下: eg1 问题描述: 开发一个能够按照不同要求生成Excel或 PDF 格式的报表的系统,例如日报表.月报表等等.   解决方案: 根据"面向接口编

  • 实例讲解Java的Spring框架中的AOP实现

    简介 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足. 除了类(classes)以外,AOP提供了 切面.切面对关注点进行模块化,例如横切多个类型和对象的事务管理. (这些关注点术语通常称作 横切(crosscutting) 关注点.) Spring的一个关键的组件就是 AOP框架. 尽管如此,Spring IoC容器并不依赖于AOP,这意味着你可以自由选择是否使用AOP,AOP提供强大的中间件解决方案,这使得Spring IoC容器更加完善

  • Java通过MyBatis框架对MySQL数据进行增删查改的基本方法

    1. 查询 除了单条记录的查询,这里我们来尝试查询一组记录. IUserMapper接口添加下面方法: List<User> getUsers(String name); 在User.xml中添加: <resultMap type="User" id="userList"><!-- type为返回列表元素的类全名或别名 --> <id column="id" property="id"

  • Java使用MyBatis框架分页的5种方式

    本文为大家分享了Java使用MyBatis框架分页的五种方式,供大家参考,具体内容如下 初始准备 1.创建分页对象类,方便模块间传值 //PageInfo.java import lombok.Data; @Data public class PageInfo { private int pageNo; private int pageSize; } 2.定义DAO层接口 import org.apache.ibatis.session.RowBounds; import org.springf

  • 实例讲解JAVA 模板方法模式

    在讲述这个模式之前,我们先看一个案例:抄题目:两个学生将老师出的题目抄写在纸上,并且写出答案 先看一个比较笨的写法 public class TestPaperA { public void testQuestion1(){ System.out.println("1+1等于几? a.1 b.2 c.3 d.4"); System.out.println("答案:b"); } public void testQuestion2(){ System.out.print

  • 实例讲解JAVA 适配器模式

    在讲述这个模式之前,我们先看一个案例:中国球员去NBA打篮球 中国球员去NBA打篮球,可是他不懂英语,所以听不懂教练安排的战术,所以现在有三种解决方式 1.球员学会英语.2.教练学会中文.3.请个翻译. 1和2是长久之计,但不能解决迫在眉睫的问题.请个翻译是短暂的更好的选择. 放在软件设计层面上,这就叫做适配器模式.https://www.jb51.net/article/189484.htm 将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以

  • 实例讲解JAVA设计模式之备忘录模式

    在讲述这个模式之前,我们先看一个案例:游戏回档 游戏的某个场景,一游戏角色有生命力.攻击力.防御力等数据,在打Boss前和后会不一样,我们允许玩家如果感觉与Boss决斗的效果不理想,可以让游戏恢复到决斗前.下面是代码: 游戏角色类,用来存储角色的生命力.攻击力.防御力的数据. public class GameRole { private int vit;//生命力 private int atk;//攻击力 private int def;//防御力 //状态显示 public void st

随机推荐