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>
本文向大家介绍Java的Hibernate框架中Criteria查询使用的实例讲解,包括了Java的Hibernate框架中Criteria查询使用的实例讲解的使用技巧和注意事项,需要的朋友参考一下 我们讲一下Criteria查询,这个对于不是太熟悉SQL语句的我们这些程序员来说是很容易上手的。 废话不多说,看一下例子: 实体类如下: 映射文件我们就不写了,很简单的一个实体,如果不懂的童鞋
本文向大家介绍实例讲解Java的Spring框架中的AOP实现,包括了实例讲解Java的Spring框架中的AOP实现的使用技巧和注意事项,需要的朋友参考一下 简介 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)以外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。 (这些关注点术语通常
本文向大家介绍Mybatis自关联查询一对多查询的实现示例,包括了Mybatis自关联查询一对多查询的实现示例的使用技巧和注意事项,需要的朋友参考一下 注:代码已托管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,项目是mybatis-13-oneself-one2many,需要自取,需要配置maven环境以及mysql环境(sql
本文向大家介绍写简单的mvc框架实例讲解,包括了写简单的mvc框架实例讲解的使用技巧和注意事项,需要的朋友参考一下 这一章先把支持注解的功能加上,这样就不需要经常地修改配置文件了。 至于视图处理的地方,就还是先用json吧,找时间再写。 项目地址在:https://github.com/hjx601496320/aMvc 。 测试代码在:https://github.com/hjx60149632
本文向大家介绍Java的MyBatis框架中对数据库进行动态SQL查询的教程,包括了Java的MyBatis框架中对数据库进行动态SQL查询的教程的使用技巧和注意事项,需要的朋友参考一下 其实MyBatis具有的一个强大的特性之一通常是它的动态 SQL 能力。 如果你有使用 JDBC 或其他 相似框架的经验,你就明白要动态的串联 SQL 字符串在一起是十分纠结的,确保不能忘了空格或在列表的最后省略
本文向大家介绍Java通过MyBatis框架对MySQL数据进行增删查改的基本方法,包括了Java通过MyBatis框架对MySQL数据进行增删查改的基本方法的使用技巧和注意事项,需要的朋友参考一下 1. 查询 除了单条记录的查询,这里我们来尝试查询一组记录。 IUserMapper接口添加下面方法: 在User.xml中添加: 测试方法: 如果联表查询,返回的是复合对象,需要用associati
本文向大家介绍用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化,包括了用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化的使用技巧和注意事项,需要的朋友参考一下 实例的背景说明 假定一个个人信息系统,需要记录系统中各个人的故乡、居住地、以及到过的城市。数据库设计如下: Models.py 内容
本文向大家介绍SpringMVC+Mybatis实现的Mysql分页数据查询的示例,包括了SpringMVC+Mybatis实现的Mysql分页数据查询的示例的使用技巧和注意事项,需要的朋友参考一下 周末这天手痒,正好没事干,想着写一个分页的例子出来给大家分享一下。 这个案例分前端和后台两部分,前端使用面向对象的方式写的,里面用到了一些回调函数和事件代理,有兴趣的朋友可以研究一下。后台的实现技术是