复杂查询
resultMap解决包含对象的复杂映射
- association : 包含对象
- collection : 包含对象的列表
关联
id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/> <!--就作用上和result一样-->
<result property="username" column="author_username"/>
</association>
关联(association)元素处理==“有一个”==类型的关系。 比如,在我们的示例中,一个学生有一个老师。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType
(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
-
嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
-
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
association元素属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定 |
关联的嵌套 Select 查询
属性 | 描述 |
---|---|
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 |
fetchType | 可选的。有效值为 lazy 和 eager 。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled ,使用属性的值。 |
多对一处理
1.构建数据库多对一关联 (学生对老师)
2.设计pojo类
Teacher.class
package com.liu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
Student.class
package com.liu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher ;
}
3.查询所有的学生以及其老师信息
public interface StudentMapper {
//查询所有学生以及其老师信息
List<Student> getStudents();
}
<mapper namespace="com.liu.dao.StudentMapper">
<select id="getStudents" resultType="student">
select * from student
</select>
</mapper>
public List<Student> getStudents() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List<Student> t = studentMapper.getStudents();
sqlSession.close();
return t;
}
/*
Student(id=1, name=张三, teacher=null)
Student(id=2, name=李四, teacher=null)
Student(id=3, name=王五, teacher=null)
*/
这个时候就会出现问题 : Student类中有一个Teacher属性 ,
而数据表中的tid显然无法在一条语句中新建出一个完整的Teacher对象,因此获取的Student,teacher属性为null
解决方法
1、通过resultMap按照查询进行嵌套处理 嵌套查询
- 查询学生的信息 ,包括tid
- 根据查出来的tid,继续查对应的Teacher
<mapper namespace="com.liu.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{tid}
</select>
<!--想实现返回一个完整的Student,需要利用resultMap-->
<resultMap id="StudentTeacher" type="student"> <!--type是返回的类型-->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<!--复杂的属性,需要单独处理-->
<!--
1.对象 association
2.集合 collection
-->
<association column="tid" property="teacher" javaType="teacher" select="getTeacher"></association>
<!--tid对应的是teacher属性-->
<!--column:'外键约束' property:'类名映射(teacher) javaType:'对应的全限定类名或类别名' select:'获取映射类的语句 会使用tid来作为getTeacher的参数tid -->
<!--
这条语句的执行顺序是 : 先将column tid传递到getTeacher语句,将取出的数据转化为 javaType teacher类型,作为column tid 的对应属性赋值给返回的studen
-->
</resultMap>
</mapper>
结果
/*
Student(id=1, name=张三, teacher=Teacher(id=1, name=张老师))
Student(id=2, name=李四, teacher=Teacher(id=2, name=王老师))
Student(id=3, name=王五, teacher=Teacher(id=1, name=张老师))
*/
2、按照结果进行嵌套处理
先把需要的信息全部都查出来,然后对查出来的属性进行操作
<!--按照结果进行嵌套处理-->
<select id="getUsers2" resultMap="StudentTeacher2">
select s.id as sid,s.name as sname,t.name as tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="student">
<result column="sid" property="id"></result>
<result column="sname" property="name"></result>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"></result>
</association>
</resultMap>
就是这么简单。我们有两个 select 查询语句:一个用来加载学生(Student),另外一个用来加载老师(Teacher),而且学生的结果映射描述了应该使用 select
语句加载它的teacher属性。
其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。
这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:
- 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。
2.嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
关联的嵌套结果映射
属性 | 描述 |
---|---|
resultMap | 结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet 。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。 |
columnPrefix | 当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 详细说明请参考后面的例子。 |
notNullColumn | 默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。 |
<mapper namespace="com.liu.dao.StudentMapper">
<select id="getStudents2" resultMap="StudentTeacher2">
select s.id as sid,
s.name as sname,
s.tid as stid,
t.id as tid,
t.name as tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="student">
<result column="sid" property="id"></result> <!--student 的 id sid——>id -->
<result column="sname" property="name"></result> <!--student 的 name sname——>name -->
<association property="teacher" column="stid" javaType="teacher" resultMap="getTeacher">
<!--student 的 stid(student表中的列名)——>teacher属性
javaType 表明这里映射的是Teacher类(核心配置文件采取包名,默认首字母小写)
resultMap 提示程序到getTeacher这个映射中去构建所需的Teacher类
-->
</association>
</resultMap>
<!--getTeacher是一个针对被包含对象teacher的映射-->
<resultMap id="getTeacher" type="teacher"> <!--type=teacher 返回一个teacher类型-->
<id column="tid" property="id"></id> <!--teacher的 id tid——>id -->
<result column="tname" property="name"></result> <!--teacher的 name tname——>name-->
</resultMap>
</mapper>
可以简化为 :
<mapper namespace="com.liu.dao.StudentMapper">
<select id="getStudents2" resultMap="StudentTeacher2">
select s.id as sid,
s.name as sname,
s.tid as stid,
t.id as tid,
t.name as tname from student s,teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="student">
<result column="sid" property="id"></result> <!--student 的 id sid——>id -->
<result column="sname" property="name"></result> <!--student 的 name sname——>name -->
<association property="teacher" javaType="teacher">
<id property="id" column="tid"></id>
<result property="name" column="tname"></result>
</association>
</resultMap>
</mapper>
上面的示例使用了外部的结果映射元素来映射关联。这使得 Student、Teacher 的结果映射可以被重用,不必再去重新查表,节约了资源
一对多处理
集合
<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
集合元素和关联元素几乎是一样的,它们相似的程度之高,以致于没有必要再介绍集合元素的相似部分。 所以让我们来关注它们的不同之处吧。
我们来继续上面的示例,一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:
private List<Post> posts;
要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。 和关联元素一样,我们可以使用嵌套 Select 查询,或基于连接的嵌套结果映射集合。
集合的嵌套 Select 查询
首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
你可能会立刻注意到几个不同,但大部分都和我们上面学习过的关联元素非常相似。 首先,你会注意到我们使用的是集合元素。 接下来你会注意到有一个新的 ofType
属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。 所以你可以按照下面这样来阅读映射:
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
读作: “posts 是一个存储 Post 的 ArrayList 集合”
在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写。所以很多时候你可以简略成:
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
集合的嵌套结果映射
现在你可能已经猜到了集合的嵌套结果映射是怎样工作的——除了新增的 “ofType” 属性,它和关联的完全相同。
首先, 让我们看看对应的 SQL 语句:
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
我们再次连接了博客表和文章表,并且为每一列都赋予了一个有意义的别名,以便映射保持简单。 要映射博客里面的文章集合,就这么简单:
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
再提醒一次,要记得上面 id 元素的重要性,如果你不记得了,请阅读关联部分的相关部分。
如果你喜欢更详略的、可重用的结果映射,你可以使用下面的等价形式:
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>
1.构建数据库多对一关联 (老师对学生)
2.设计pojo类
Teacher.class
package com.liu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
//Teacher表达一对多的关系 可以设置一个 List<Student>
private List<Student> students;
}
Student.class
package com.liu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
3.查询所有的老师信息
1.嵌套Select查询
<mapper namespace="com.liu.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select s.id as sid,
s.name as sname,
t.name as tname,
t.id as tid from student s,teacher t where s.id = t.id and t.id=#{tid}
</select>
<resultMap id="TeacherStudent" type="teacher"> <!--映射类型为teacher(别名,使用了package映射)-->
<id property="id" column="tid"></id>
<result property="name" column="tname"></result>
<!--集合使用collection,ofType来获取泛型的类型-->
<collection property="students" ofType="student" javaType="ArrayList" column="sid" select="getStudentList"/>
<!--
1.Teacher的students属性
2.ofType表明 泛型是 Student(别名student)
3.javaType表明 映射类型是 ArrayList
4.column 表明要在子select查询中的参数 sid
5.select 表示子查询语句
-->
</resultMap>
<select id="getStudentList" resultType="student">
select * from student where id=#{sid} <!--使用resultMap中的sid-->
</select>
</mapper>
//结果
Teacher(id=1, name=张老师, students=[Student(id=1, name=张三, tid=1)])
2.嵌套结果映射
<mapper namespace="com.liu.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select s.id as sid,
s.name as sname,
t.name as tname,
t.id as tid from student s,teacher t where s.id = t.id and t.id=#{tid}
</select>
<resultMap id="TeacherStudent" type="teacher">
<id property="id" column="tid"></id>
<result property="name" column="tname"></result>
<!--集合使用collection,ofType来获取泛型的类型-->
<collection property="students" ofType="student" javaType="ArrayList" resultMap="getStudentList"/>
</resultMap>
<!--一个复用了最上方别名的student的resultMap-->
<resultMap id="getStudentList" type="student">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
</resultMap>
</mapper>
简化版
<resultMap id="TeacherStudent" type="teacher">
<id property="id" column="tid"></id>
<result property="name" column="tname"></result>
<!--集合使用collection,ofType来获取泛型的类型-->
<collection property="students" ofType="student" javaType="ArrayList">
<id column="sid" property="id"></id>
<result column="sname" property="name"></result>
</collection>
</resultMap>