MyBatis resultMap

resultMap 用于描述 SQL 返回结果与 Java 实体类属性之间的对应关系。

一、简单示例

1
2
3
4
5
6
7
<resultMap id="唯一标识" type="实体类">
···
</resultMap>

···

<select id="方法名" resultMap="唯一标识">

其中:

  • id 用于标识 resultMap
  • type 用于指定返回结果要构建成哪一个实体类

二、什么是 resultMap ?

1. 作用

resultMap 用于设置查询返回结果中 字段 与实体类中 属性 的对照关系。

2. 子标签

  • id、result:将结果包装成基本类型
  • constructor:使数据通过构造方法传入对象
  • association:将结果包装成对象
  • collection:将结果包装成集合

三、标签 - id、result

1. 示例

1
2
3
<id property="属性名" column="字段名"/>

<result property="属性名" column="字段名"/>

2. id 与 result

id 标签和 result 标签均可用于将实体类的属性与数据库的字段做一一对应。

id 标签的特殊之处在于,它会将属性标记为对象的标识符,在比较对象实例时使用,从而提高整体的性能。

3. 属性

属性 描述
property 类属性名
column 数据库字段名
javaType Java 类型
jdbcType JDBC 类型
typeHandler 类型处理器

三、标签 - constructor

1. 说明

默认情况下,Mybatis 在取出结果时,会首先调用无参构造方法构造对象,然后通过 setter() 方法将对应数据放入对象之中。

但在实际开发中,还有许多不可变类。

不可变类:一旦对象被创建出来,其成员变量就不能被改变

对于不可变类,因为其对象的成员变量在初始化之后无法被改变,因此需要通过构造方法来进行赋值。

MyBatis 为此提供了 constructor 标签,使数据通过构造方法(而不是通过 setter)传入对象。

2. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class User {

public User(Integer id, String username, int age) {
...
}

}

···

<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="int"/>
</constructor>

3. idArg 与 arg

idArg 和 arg 都用于配置构造方法的参数。

idArg 的特殊之处在于,用它配置的构造参数将会被标记为对象的标识符

4. 属性

属性 描述
column 数据库字段名
javaType Java 类型
jdbcType JDBC 类型
typeHandler 类型处理器
select 用于嵌套查询
resultMap 结果集映射
name 构造方法形参名

四、标签 - association

1. 案例

(1) 数据库

student 表:

teacher 表:

student 表中有外键 tid,与 teacher 表中的主键 id 相对应。

每个学生对应一个老师,每个老师对应多个学生。

(2) 实体类

Student:

1
2
3
4
5
6
7
8
9
10
11
public class Student {
private int id;
private String name;
private Teacher teacher;

// 构造方法

// getter and setter

// toString()
}

Teacher:

1
2
3
4
5
6
7
8
9
10
public class Teacher {
private int id;
private String name;

// 构造方法

// getter and setter

// toString()
}

为了表达学生与老师之间的对应关系,在 Student 类中添加 Teacher 对象,作为成员变量。

(3) 需求

希望查询的返回 sutdent 对象,其中包含 id、name 以及 teacher 对象

(4) 解决方案

可以通过 association 标签实现,并且共有两种解决方案:

  • 嵌套查询
  • 嵌套映射

2. association

(1) 说明

  • association 标签可以看作“对象”
  • association 标签用于将返回结果包装成对象
  • 当返回结果中嵌套着对象时,使用 association 标签来配置
  • 通过嵌套查询和嵌套映射来解决嵌套对象属性和返回数据之间的对应关系

(2) 属性

通用属性:

属性 描述
property 类属性名
javaType Java 类型
jdbcType JDBC 类型
typeHandler 类型处理器

嵌套查询专用属性:

属性 描述
select 用于设置嵌套查询,填入查询方法名
column 字段名,指定字段的值将会传递给嵌套查询方法(可以传递多个字段: column="{prop1=col1,prop2=col2}"
fetchType 延迟加载,可选(将覆盖全局配置参数 lazyLoadingEnabled

嵌套映射专用属性:

属性 描述
resultMap 填入 resultMap 的唯一标识
columnPrefix 指定列前缀
notNullColumn 指定非空列
autoMapping 开启或关闭自动映射

3. 嵌套查询

(1) 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="getStudentList" resultMap="getStudentListMap">
select * from student
</select>

<!--resultMap-->
<resultMap id="getStudentListMap" type="pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" select="getTeacher"/>
</resultMap>

<!--嵌套查询方法-->
<select id="getTeacher" resultType="pojo.Teacher">
select * from teacher where id = #{id}
</select>
  • 返回结果中嵌套了对象,故使用 association 来配置
    • property 属性指定类属性名为 teacher
    • column 属性指定要传入嵌套查询方法的字段为 tid
    • select 指定嵌套查询方法
  • 嵌套查询方法中,按正常方式配置 SQL 语句,它将会接收传入的数据,向数据库查询,并将返回结果赋给 association 中 property 属性指定的类属性

(2) 延迟加载

这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。

  • 1:执行了一个单独的 SQL 语句来获取结果的一个列表
  • N:对列表返回的每条记录都再执行一次嵌套查询

这将会导致成百上千的 SQL 语句被执行

MyBatis 能通过配置 fetchType 进行延迟加载(对于 ”N“,仅当访问对应数据时才加载),从而避免大量语句同时查询。

4. 嵌套映射

(1) 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<select id="getStudentList2" resultMap="getStudentList2Map">
SELECT
student.id,
student.name,
teacher.id teacher_id,
teacher.name teacher_name
FROM
student,
teacher
WHERE
student.tid = teacher.id
</select>

<!--resultMap-->
<resultMap id="getStudentList2Map" type="pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher">
<result property="id" column="teacher_id"/>
<result property="name" column="teacher_name"/>
</association>
</resultMap>
  • property 属性指定类属性名为 teacher
  • 在子标签中配置对象中属性与查询结果中字段的对应关系

(2) 列前缀

columnPrefix 该属性用于指定列前缀,配置之后,子标签中的 column 会先添加前缀再建立映射关系。

将示例改写为使用 columnPrefix:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<select id="getStudentList2" resultMap="getStudentList2Map">
SELECT
student.id,
student.name,
teacher.id teacher_id,
teacher.name teacher_name
FROM
student,
teacher
WHERE
student.tid = teacher.id
</select>

<!--resultMap-->
<resultMap id="getStudentList2Map" type="pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" columnPrefix="teacher_">
<result property="id" column="id"/>
<result property="name" column="name"/>
</association>
</resultMap>

五、标签 - collection

1. 案例

(1) 数据库

student 表:

teacher 表:

student 表中有外键 tid,与 teacher 表中的主键 id 相对应。

每个学生对应一个老师,每个老师对应多个学生。

(2) 实体类

Student:

1
2
3
4
5
6
7
8
9
10
11
public class Student {
private int id;
private String name;
private int tid;

// 构造方法

// getter and setter

// toString()
}

Teacher:

1
2
3
4
5
6
7
8
9
10
11
public class Teacher {
private int id;
private String name;
private List<Student> students;

// 构造方法

// getter and setter

// toString()
}

为了表达学生与老师之间的对应关系,在 Teacher 类中添加 Student 对象数组,作为成员变量。

(3) 需求

希望查询的返回 Teacher 对象,其中包含 id、name 以及 Student 对象数组

(4) 解决方案

可以通过 collection 标签实现,并且和 assciation 一样有两种解决方案:嵌套查询、嵌套映射。

(5) 嵌套查询

1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="getTeacher" resultMap="getTeacherMap">
select * from teacher where id = #{id}
</select>

<resultMap id="getTeacherMap" type="pojo.Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" column="id" select="getStudents"/>
</resultMap>

<select id="getStudents" resultType="pojo.Student">
select * from student where tid = #{tid}
</select>

(6) 嵌套映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<select id="getTeacher2" resultMap="getTeacher2Map">
SELECT
student.id sid,
student.`name` sname,
teacher.id tid,
teacher.`name` tname
FROM
student,
teacher
WHERE
teacher.id = #{id}
AND student.tid = teacher.id
</select>

<resultMap id="getTeacher2Map" type="pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>

2. collection

(1) 说明

  • collection 标签可以看作“集合”
  • collection 标签用于将返回结果包装成集合
  • 当返回结果中嵌套着集合时,使用 collection 标签来配置
  • 通过嵌套查询和嵌套映射来解决嵌套集合和返回数据之间的对应关系

(2) ofType 属性

id、result、association 和 collection 均有 javaType 属性,用于表示该标签对应的实体类的属性的 Java 类型。

  • 在 id 和 result 中,javaType 为基本类型,可以省略
  • 在 asscociation 中,javaType 为实体类,可以省略
  • 在 collection 中,javaType 为集合类型,可以省略

但是对于 collection 而言,缺少了一个用于描述集合中元素类型的属性,因此 MyBatis 引入了 ofType 属性,该属性用于表述集合元素的类型。

六、标签 - discriminator

1. 说明

discriminator 标签类似于 switch 语句

根据指定列的值,选择对应的映射关系

2. 属性

属性 描述
javaType 用于指定用于筛选的数据的类型(以确保正确地比较并选择)
column 指定用于筛选的数据库字段名
extends 引入其它 resultMap

3. 子标签 - case

1
<case value="值" resultMap="唯一标识"/>

discriminator 拥有 case 这一子标签,在其中填入 value 和 resultMap。

4. 匹配规则

  • 当对应字段的值与 case 的值是相等时,应用 case 的 resultMap

  • case 与 discriminator 之外是互斥的

    • 当 case 被匹配时,discriminator 之外的对照关系将失效
    • 当任何一个 case 都未被匹配时,discriminator 之外的对照关系将被应用

    可以为 discriminator 添加 extends 属性,并设为外层 resultMap 的值,

    如此,当 case 被匹配时,discriminator 之外的对照关系也将生效

  • case 之间是互斥的,当匹配到一个 case,其它 case 的 resultMap 将不会被应用

5. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="bicycleResult"/>
<case value="3" resultMap="tricycleResult"/>
</discriminator>
</resultMap>

<resultMap id="vehicleResult" type="Car">
<id property="carid" column="id" />
</resultMap>

<resultMap id="vehicleResult" type="Bicycle">
<id property="bicycleid" column="id" />
</resultMap>

<resultMap id="vehicleResult" type="Tricycle">
<id property="tricycleid" column="id" />
</resultMap>
  • 当对应字段的值等于 1 时,resultMap 为 carResult,对应关系为:

    1
    2
    3
    <resultMap id="vehicleResult" type="Car">
    <id property="carid" column="id" />
    </resultMap>

    当对应字段的值等于 2 时,resultMap 为 bicycleResult,对应关系为:

    ···

  • 当对应字段的值不与任何 case 对应时,discriminator 之外的对照关系被应用,对照关系为:

    1
    2
    3
    <resultMap id="vehicleResult" type="Vehicle">
    <id property="id" column="id" />
    </resultMap>

七、自动映射

即使没有配置 resultMap,只要实体类的属性和数据库的字段一一对应,结果也能够顺利被取出。

这是因为 MyBatis 自行创建了 resultMap,进行了自动映射工作。

MyBatis 默认会将所有未配置的属性/字段以实体类属性名/数据库字段名做自动映射。

参考