一对一关联
创建评论表 posts 和 博客表 blog ,一个blog下有多个评论,每条评论都说针对一条博客。所以每一条评论和博客的关系是一对一。每个博客和评论的关系是 一对多,建表语句如下:
CREATE TABLE posts (
pid int(11) NOT NULL AUTO_INCREMENT,
post_name varchar(45) DEFAULT NULL,
blog_id int(11) DEFAULT NULL,
PRIMARY KEY (pid)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE blog (
bid int(11) NOT NULL AUTO_INCREMENT,
name varchar(45) COLLATE utf8_bin DEFAULT NULL,
author_id int(11) DEFAULT NULL,
PRIMARY KEY (bid)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Post.java 中持有一个Blog blog 属性 ,Blog.java 中持有一个 List posts属性如下:
PostMapper.xml 中 增加如下两种查询:嵌套结果(getPost)和嵌套查询(getPost2)
<!--
方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
封装联表查询的数据(去除重复的数据)
-->
<!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
<resultMap id="UnionResultMap" type="com.test.mybatis.beans.Post">
<id column="pid" jdbcType="INTEGER" property="pid" />
<result column="post_name" jdbcType="VARCHAR" property="postName" />
<result column="blog_id" jdbcType="INTEGER" property="blogId" />
<association property="blog" javaType="com.test.mybatis.beans.Blog">
<id property="bid" column="bid"/>
<result property="name" column="name"/>
</association>
</resultMap>
<select id="getPost" parameterType="int" resultMap="UnionResultMap">
select * from posts p,blog b where b.bid=p.blog_id and p.pid=#{id}
</select>
<!--
方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
select * from posts p where p.pid=#{id}
select * from blog b where b.bid=#{id}
-->
<resultMap id="UnionResultMap2" type="com.test.mybatis.beans.Post">
<id column="pid" jdbcType="INTEGER" property="pid" />
<result column="post_name" jdbcType="VARCHAR" property="postName" />
<result column="blog_id" jdbcType="INTEGER" property="blogId" />
<!--使用resultMap映射实体类和字段之间的一一对应关系-->
<association property="blog" column="blog_id" select="getBlog">
</association>
</resultMap>
<select id="getPost2" parameterType="int" resultMap="UnionResultMap2">
select * from posts p where p.pid=#{id}
</select>
<select id="getBlog" parameterType="int" resultType="com.test.mybatis.beans.Blog">
select * from blog b where b.bid=#{id}
</select>
PostMapper.java 中增加 对应的查询方法
Post getPost(Integer pid);
Post getPost2(Integer pid);
测试代码:
SqlSession session = getSqlSession();
PostMapper mapper = session.getMapper(PostMapper.class);
Post post =mapper.getPost(1);
System.out.println(post.getBlog().getName());
Post post2 =mapper.getPost2(1);
System.out.println(post2.getPostName());
System.out.println(post2.getBlog().getName());
执行结果可可以看到,嵌套结果一次查询出结果字段并将结果赋值到指定对象中,嵌套查询的方式 执行了两个sql 获取信息:
总结:
MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:
property:对象属性的名称
javaType:对象属性的类型
column:所对应的外键字段名称
select:指定一个查询来获取结果
一对多关联
继续以上边示例 ,查询blog时 blog 对post 是一对多。
BlogMapper.xml 中增加如下两种查询实现(嵌套结果和嵌套查询)
<resultMap id="UnionResultMap" type="com.test.mybatis.beans.Blog">
<id column="bid" jdbcType="INTEGER" property="bid" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="author_id" jdbcType="INTEGER" property="authorId" />
<collection property="posts" ofType="com.test.mybatis.beans.Post">
<id column="pid" property="pid" ></id>
<result column="postName" property="post_name" ></result>
</collection >
</resultMap>
<!--方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集-->
<select id="getBlog" parameterType="int" resultMap="UnionResultMap">
select * from posts p,blog b where b.bid=p.blog_id and b.bid=#{id}
</select>
<resultMap id="UnionResultMap2" type="com.test.mybatis.beans.Blog">
<id column="bid" jdbcType="INTEGER" property="bid" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="author_id" jdbcType="INTEGER" property="authorId" />
<collection property="posts" column="bid" ofType="com.test.mybatis.beans.Post" select="getPosts">
</collection >
</resultMap>
<select id="getBlog2" parameterType="int" resultMap="UnionResultMap2">
select b.bid,b.name,b.author_id from blog b where b.bid=#{id}
</select>
<select id="getPosts" parameterType="int" resultType="com.test.mybatis.beans.Post">
select p.pid,p.post_name,p.blog_id from posts p where p.blog_id=#{id}
</select>
BlogMapper.java 中增加相应查询方法
Blog getBlog(Integer bid);
Blog getBlog2(Integer bid);
测试代码:
BlogMapper bmapper = session.getMapper(BlogMapper.class);
Blog b = bmapper.getBlog(1);
System.out.println(b.getName());
System.out.println(b.getPosts().size());
Blog b2 = bmapper.getBlog2(1);
System.out.println(b2.getName());
System.out.println(b2.getPosts().size());
执行结果如下,可以看到嵌套结果执行一条sql 并将结果封装到了对应属性中.嵌套查询方式通过多个查询获取结果。
总结:
MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。
N+1问题:
对于上述示例中的嵌套查询的方式,比如我们调用getBlog2查询Blog信息时,这个时候不管我们是不是需要使用评论信息(posts属性),Mybatis 都会执行一条sql把它查询出来。如果我们的Blog 还有其他的关联信息(比如 作者,分类等属性),也会一次性全部查询出来,但是我们可能根本不需要。这就造成了性能的浪费。就是Mybatis 的 N+1(1条sql+N条关联信息sql)问题。克服N+1问题的方法就是延迟加载
Mybatis 配置文件中增加如下配置
<setting>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</setting>
执行如下测试代码
BlogMapper bmapper = session.getMapper(BlogMapper.class);
Blog b2 = bmapper.getBlog2(1);
System.out.println(b2.getName());
只执行了一条sql 并未执行关联的sql ,关联的sql查询只在需要的时候才回去执行。
配置说明:
lazyLoadingEnabled:延迟加载的全局开关,当开启时,所有关联都会延迟加载。在特定的关联中,使用fetchType属性覆盖该内容的功能。
aggressiveLazyLoading:是层级延迟加载开关,就是处于同一个层级的关联表会同时延迟加载,或者同时被加载
lazyLoadingEnabled 和 aggressiveLazyLoading 都是全局配置,全局配置让全局都适用统一的规则,不同业务或功能对延迟加载的需求不一样,所以只配置全局的开关可能会影响部分功能的性能。
fetchType属性解决全局配置的缺点。fetchType出现在级联元素association,collection中,它存在两个值
- eager:获得当前POJO后立即加载对应的数据。
- lazy:获得当前POJO后延迟加载对应的数据。
我们在BlogMapper.xml 中 的collection 元素中 配置 fetchType=‘eager’ 如下:
再次执行测试代码结果延迟加载失效,直接执行了两条sql查询出了 评论信息: