MyBatis 入门教程之四

SQL 映射文件

select 标签

本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-7

属性说明

select 标签用于定义查询操作,拥有以下常用属性:

  • id:唯一标识符,用来引用这条语句,需要与接口的方法名一致。
  • parameterType:参数类型,可以不传递,MyBatis 会根据 TypeHandler 自动推断参数类型。
  • resultType:返回值类型,可以是类型别名或者全限定类名。如果返回结果是集合,则其值是集合中元素的类型。resultType 不能和 resultMap 同时使用。

提示

select 标签还拥有很多其他属性,详细说明请点击这里查看。

使用案例

返回 List 类型
1
2
3
4
5
public interface EmployeeMapper {

public List<Employee> getAllEmp();

}
1
2
3
<select id="getAllEmp" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email from t_employee
</select>
1
查询结果:[1_Tom_1_tom@gmail.com, 2_Jim_1_jim@gmail.com]
返回 Map 类型(第一种场景)
1
2
3
4
5
6

public interface EmployeeMapper {

public Map<String, Object> getEmpById(Long id);

}
1
2
3
4
5
<select id="getEmpById" parameterType="Long" resultType="Map">
select id, last_name as lastName, gender, email
from t_employee
where id = #{id}
</select>
1
查询结果:{lastName=Tom, gender=1, id=1, email=tom@gmail.com}
返回 Map 类型(第二种场景)
1
2
3
4
5
6
7
8
9
10
11
12
public interface EmployeeMapper {

/**
* 使用注解告诉 MyBatis,返回的 Map 使用哪个 JavaBean 属性作为 key
*
* @param lastName
* @return
*/
@MapKey("id")
public Map<String, Employee> getEmpByLastName(String lastName);

}
1
2
3
4
5
<select id="getEmpByLastName" parameterType="String" resultType="Map">
select id, last_name as lastName, gender, email
from t_employee
where last_name like #{lastName}
</select>
1
查询结果:{1={lastName=Tom, gender=1, id=1, email=tom@gmail.com}, 2={lastName=Jim, gender=1, id=2, email=jim@gmail.com}}

结果映射

全局 setting 设置

配置说明

autoMappingBehavior 可以开启自动映射的功能,默认值是 PARTIAL,唯一的要求是字段名和 JavaBean 属性名需要一致。
– 如果将 autoMappingBehavior 设置为 null,则 MyBatis 会取消自动映射。
– 另外还可以使用数据库字段命名规范,即 JavaBean 的属性名符合驼峰命名法,如 last_name 映射为 lastName,简单设置 mapUnderscoreToCamelCase = true 就可以开启自动驼峰命名规则映射功能。

使用案例
1
2
3
4
5
6
<configuration>
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>

resultMap 的使用

高级结果映射
  • 复杂的查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
  • 复杂的结果映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int" />
</constructor>
<result property="title" column="blog_title" />
<association property="author" javaType="Author">
<id property="id" column="author_id" />
<result property="username" column="author_username" />
<result property="password" column="author_password" />
<result property="email" column="author_email" />
<result property="bio" column="author_bio" />
<result property="favouriteSection" column="author_favourite_section" />
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id" />
<result property="subject" column="post_subject" />
<association property="author" javaType="Author" />
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id" />
</collection>
<collection property="tags" ofType="Tag">
<id property="id" column="tag_id" />
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost" />
</discriminator>
</collection>
</resultMap>
属性说明

通过自定义的 resultMap,可以实现高级结果映射功能。resultMap 标签的属性列表如下:

属性描述
id 当前命名空间中的一个唯一标识,用于标识一个结果映射。
type 类的完全限定名,或者一个类型别名。
autoMapping 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior,默认值是 未设置(unset)

resultMap 标签的子标签说明如下:

  • constructor - 类在实例化时,用来注入结果到构造方法中
    idArg - ID 参数;标记结果作为 ID 可以帮助提高整体效能
    arg - 注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association - 一个复杂类型的关联,许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection - 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator - 使用结果值来决定使用哪个 resultMap
    • case - 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

resultMap 标签的 idresult 子标签都可以将一个列的值映射到一个简单数据类型(String、int、double、Date 等)的属性或字段。这两者之间的唯一区别是,id 子标签对应的属性会被标记为对象的标识符,在比较对象实例时使用。这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是级联映射)的时候。这两个子标签都有一些属性:

属性描述
property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的全限定名,或一个类型别名。如果你映射到一个 JavaBean,那么 MyBatis 通常可以自动推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的” 支持的 JDBC 类型”。只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
typeHandler使用这个属性,你可以覆盖默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

MyBatis 通过内置的 jdbcType 枚举类型支持以下的 JDBC 类型:

自动映射

在简单的场景下,MyBatis 可以自动映射查询结果,但如果遇到复杂的场景,则需要构建一个结果映射,而且这两种策略支持混合使用。当自动映射查询结果时,MyBatis 会获取结果中返回的列名,并在 Java 类中查找相同名字的属性(忽略大小写)。这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔,而 Java 属性一般遵循驼峰命名法约定;为了在这两种命名方式之间启用自动映射,需要在全局的配置文件中将 mapUnderscoreToCamelCase 设置为 true。MyBatis 甚至在提供了结果映射后,自动映射也能工作。在这种情况下,对于每一个结果映射,在 ResultSet 出现的列,如果没有设置手动映射,将被自动映射;在自动映射处理完毕后,再处理手动映射。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-8

自动映射等级

MyBatis 提供了三种自动映射等级:

  • FULL - 自动映射所有属性
  • NONE - 禁用自动映射,仅对手动映射的属性进行映射
  • PARTIAL - 对除在内部定义了嵌套结果映射(也就是关联的属性)以外的属性进行映射

默认的自动映射等级是 PARTIAL,这是有原因的。当对级联查询的结果使用 FULL 时,级联查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。无论设置的自动映射等级是哪种,MyBatis 都支持在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用或禁用自动映射,如下所示:

1
2
3
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
使用案例之一
1
2
3
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
1
2
3
4
5
6
7
8
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>

在上面的例子中,iduserName 列将被自动映射,hashed_password 列将根据配置进行映射。

使用案例之二
1
2
3
4
5
6

public interface EmployeeMapper {

public Employee getById(Long id);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
</resultMap>

<select id="getById" resultMap="employeeResultMap">
select id, last_name, gender, email
from t_employee
where id = #{id}
</select>

</mapper>
级联映射

本节所有的案例代码,都基于以下的表结构和 JavaBean 类编写。

  • 数据库表结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `t_department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `t_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` varchar(255) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`dept_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • JavaBean 类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Department {

private Long id;
private String name;
private List<Employee> employees;

......

@Override
public String toString() {
return "Department [id=" + id + ", name=" + name + ", employees=" + employees + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Employee {

private Long id;
private String lastName;
private String gender;
private String email;
private Department department;

......

@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", gender=" + gender + ", email=" + email + ", department=" + department + "]";
}
}
方式一(级联属性封装)

本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-9

  • DAO 接口
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpAndDept(Long id);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
<!-- 定义级联属性的封装规则 -->
<result column="dept_id" property="department.id" />
<result column="dept_name" property="department.name" />
</resultMap>

<select id="getEmpAndDept" resultMap="employeeResultMap">
select emp.id, emp.last_name, emp.gender, emp.email, dept.id as dept_id, dept.name as dept_name
from t_employee emp
left join t_department dept
on emp.dept_id = dept.id
where emp.id = #{id}
</select>

</mapper>

提示

上述 resultMap 的写法,当调用 getEmpAndDept() 方法时,可以实现级联属性的封装(即级联映射 / 级联查询)。

  • 查询结果
1
打印 getEmpAndDept() 方法的调用结果:Employee [id=6, lastName=Tom, gender=1, email=tom@gmail.com, department=Department [id=1, name=开发部门]]
方式二(嵌套结果映射)

级联映射除了上面的使用方式之外,还可以使用 resultMap 的子标签 association 实现嵌套结果映射。嵌套结果映射的定义,是使用嵌套的结果映射来处理连接结果的重复子集。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-10

  • 属性说明
属性描述
resultMap结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。为了将结果集正确地映射到嵌套的对象树中,MyBatis 允许 “串联” 结果映射,以便解决嵌套结果集的问题。
columnPrefix当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。你可以在这个属性上指定非空的列来改变默认行为,指定后 Mybatis 将只在这些列中任意一列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值是 未设置(unset)
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior。特别注意,本属性对外部的结果映射无效,所以不能搭配 selectresultMap 元素使用。默认值是 未设置(unset)
  • DAO 接口
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpAndDept(Long id);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
<!-- 定义单个级联对象的封装规则,property:指定哪个JavaBean属性是联合的对象,javaType:指定当前这个属性的对象类型(必填) -->
<association property="department" javaType="com.clay.mybatis.bean.Department">
<id column="dept_id" property="id" />
<result column="dept_name" property="name" />
</association>
</resultMap>

<select id="getEmpAndDept" resultMap="employeeResultMap">
select emp.id, emp.last_name, emp.gender, emp.email, dept.id as dept_id, dept.name as dept_name
from t_employee emp
left join t_department dept
on emp.dept_id = dept.id
where emp.id = #{id}
</select>

</mapper>

若希望 Department 的结果映射可以被重用,那么还可以使用以下的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
<!-- 定义单个级联对象的封装规则 -->
<association property="department" javaType="com.clay.mybatis.bean.Department" resultMap="departmentResultMap" />
</resultMap>

<resultMap type="com.clay.mybatis.bean.Department" id="departmentResultMap">
<id column="dept_id" property="id" />
<result column="dept_name" property="name" />
</resultMap>

<select id="getEmpAndDept" resultMap="employeeResultMap">
select emp.id, emp.last_name, emp.gender, emp.email, dept.id as dept_id, dept.name as dept_name
from t_employee emp
left join t_department dept
on emp.dept_id = dept.id
where emp.id = #{id}
</select>

</mapper>
  • 查询结果
1
打印 getEmpAndDept() 方法的调用结果:Employee [id=8, lastName=Tom, gender=1, email=tom@gmail.com, department=Department [id=1, name=开发部门]]
方式三(嵌套 Select 查询)

级联映射除了上面的使用方式之外,还可以使用 resultMap 的子标签 association 实现嵌套 Select 查询(也叫分段查询)。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-11

  • 属性说明
属性描述
property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType一个 Java 类的全限定名,或一个类型别名。如果你映射到一个 JavaBean,那么 MyBatis 通常可以自动推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之后的” 支持的 JDBC 类型”。只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值的列指定这个类型。
typeHandler使用这个属性,你可以覆盖默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。特别注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 select 语句的参数。
select用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。特别注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 select 查询语句的列名。这会使得 prop1prop2 作为参数对象,被设置为对应嵌套 select 语句的参数。
fetchType可选的,有效值为 lazyeager。指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled 属性的值。
  • DAO 接口
1
2
3
4
5
public interface DepartmentMapper {

public Department getById(Long id);

}
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpAndDept(Long id);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.DepartmentMapper">

<select id="getById" parameterType="Long" resultType="com.clay.mybatis.bean.Department">
select id, name
from t_department
where id = #{id}
</select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
<!-- 定义单个级联对象的封装规则,select:表明当前 JavaBean 属性是调用 select 指定的方法来查出结果,column:指定哪一列的值作为参数传递给这个 select 方法 -->
<association
property="department"
javaType="com.clay.mybatis.bean.Department"
select="com.clay.mybatis.dao.DepartmentMapper.getById"
column="dept_id">
</association>
</resultMap>

<select id="getEmpAndDept" resultMap="employeeResultMap">
select id, last_name, gender, email, dept_id
from t_employee
where id = #{id}
</select>

</mapper>
  • 查询结果
1
打印 getEmpAndDept() 方法的调用结果:Employee [id=16, lastName=Tom, gender=1, email=tom@gmail.com, department=Department [id=1, name=开发部门]]
方式四(集合的嵌套结果映射)

级联映射除了上面的使用方式之外,还可以使用 resultMap 的子标签 association 实现集合的嵌套结果映射(适用于集合类型)。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-12

  • DAO 接口
1
2
3
4
5
public interface DepartmentMapper {

public Department getDeptById(Long id);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<mapper namespace="com.clay.mybatis.dao.DepartmentMapper">

<resultMap id="deptResultMap" type="com.clay.mybatis.bean.Department">
<id column="dept_id" property="id" />
<result column="dept_name" property="name" />
<!-- collection 定义关联集合类型的属性的封装规则,ofType:指定集合里面元素的类型 -->
<collection property="employees" ofType="com.clay.mybatis.bean.Employee">
<id column="emp_id" property="id" />
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
</collection>
</resultMap>

<select id="getDeptById" parameterType="Long" resultMap="deptResultMap">
select dept.id as dept_id, dept.name as dept_name, emp.id as emp_id, emp.last_name, emp.gender, emp.email from t_department dept
left join t_employee emp
on dept.id = emp.dept_id
where dept.id = #{id}
</select>

</mapper>
  • 查询结果
1
打印 getDeptById() 方法的调用结果:Department [id=1, name=开发部门, employees=[Employee [id=1, lastName=Jim, gender=1, email=jim@gmail.com, department=null], Employee [id=2, lastName=Peter, gender=1, email=peter@gmail.com, department=null]]]
方式五(集合的嵌套 Select 查询)

级联映射除了上面的使用方式之外,还可以使用 resultMap 的子标签 association 实现集合的嵌套 Select 查询(也叫集合的分段查询,适用于集合类型)。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-13

  • DAO 接口
1
2
3
4
5
public interface DepartmentMapper {

public Department getDeptById(Long id);

}
1
2
3
4
5
public interface EmployeeMapper {

public List<Employee> getByDept(Long deptId);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getByDept" resultType="com.clay.mybatis.bean.Employee">
select id, last_name, gender, email, dept_id
from t_employee
where dept_id = #{deptId}
</select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<mapper namespace="com.clay.mybatis.dao.DepartmentMapper">

<resultMap id="deptResultMap" type="com.clay.mybatis.bean.Department">
<id column="id" property="id" />
<result column="name" property="name" />
<!-- collection 定义关联集合类型的属性的封装规则,ofType:指定集合里面元素的类型,select:表明当前 JavaBean 属性是调用 select 指定的方法来查出结果,column:指定哪一列的值作为参数传递给这个 select 方法 -->
<collection
property="employees"
ofType="com.clay.mybatis.bean.Employee"
select="com.clay.mybatis.dao.EmployeeMapper.getByDept"
column="id" />
</resultMap>

<select id="getDeptById" parameterType="Long" resultMap="deptResultMap">
select id, name
from t_department
where id = #{id}
</select>

</mapper>
  • 查询结果
1
打印 getDeptById() 方法的调用结果:Department [id=1, name=开发部门, employees=[Employee [id=1, lastName=null, gender=1, email=jim@gmail.com, department=null], Employee [id=2, lastName=null, gender=1, email=peter@gmail.com, department=null]]]
嵌套 Select 查询 & 延迟加载

嵌套 Select 查询这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳,这个问题被称为 N+1 查询问题。概括地讲,N+1 查询问题是这样子的:

  • 执行了一个单独的 SQL 语句来获取结果的一个列表(就是 +1)。
  • 对列表返回的每条记录,执行一个 select 查询语句来为每条记录加载详细信息(就是 N)。

这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果,则可以全局配置 MyBatis 启用延迟加载。当启用延迟加载后,只有级联属性被使用到的时候,MyBatis 才会发出查询 SQL,以此提高级联加载的性能。

1
2
3
4
5
6
<settings>
<!-- 延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 属性按需加载 -->
<setting name="aggressiveLazyLoading" value="false" />
</settings>
  • lazyLoadingEnabled:延迟加载,默认值为 false
  • aggressiveLazyLoading:控制具有延迟加载特性的对象的属性的加载情况,true 表示如果对具有延迟加载特性的对象的任意调用会导致这个对象的完整加载,false 表示每种属性按照需要加载。在 MyBatis 3.4.1(包含) 版本之前,默认值为 true,之后默认值为 false

值得一提的是,若全局配置不适合特定的需求,MyBatis 还为此提供了局部延时加载功能。在 collectionassociation 标签上加入属性值 fetchType 就可以了,其取值分别是 eagerlazy

discriminator 鉴别器的使用

有时候,一个数据库查询可能希望根据不同的条件返回多个不同的结果集(但总体上还是有一定的联系的)。鉴别器(discriminator)标签就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解,它很像 Java 语言中的 switch 语句。一个鉴别器的定义需要指定 columnjavaType 属性。column 指定了 MyBatis 查询被比较值的地方,而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。完整的使用说明可查看 官方文档,本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-14

  • DAO 接口
1
2
3
4
5
public interface DepartmentMapper {

public Department getById(Long id);

}
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpAndDept(Long id);

}
  • SQL 映射文件
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.DepartmentMapper">

<select id="getById" parameterType="Long" resultType="com.clay.mybatis.bean.Department">
select id, name
from t_department
where id = #{id}
</select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<resultMap type="com.clay.mybatis.bean.Employee" id="employeeResultMap">
<!-- id:定义主键列的封装规则; column:指定列名; property:指定对应的JavaBean属性; -->
<id column="id" property="id" />
<!-- 定义普通列的封装规则,即使不指定普通列 MyBatis 也会按照默认的规则自动封装,但一般都全部写上 -->
<result column="last_name" property="lastName" />
<result column="gender" property="gender" />
<result column="email" property="email" />
<!-- 使用鉴别器判断某列的值,然后根据某列的值改变封装行为 -->
<discriminator javaType="String" column="gender">
<case value="1" resultType="com.clay.mybatis.bean.Employee">
<!-- 定义单个级联对象的封装规则,select:表明当前 JavaBean 属性是调用 select 指定的方法来查出结果,column:指定哪一列的值作为参数传递给这个select方法 -->
<association
property="department"
javaType="com.clay.mybatis.bean.Department"
select="com.clay.mybatis.dao.DepartmentMapper.getById"
column="dept_id">
</association>
</case>
</discriminator>
</resultMap>

<select id="getEmpAndDept" resultMap="employeeResultMap">
select id, last_name, gender, email, dept_id
from t_employee
where id = #{id}
</select>

</mapper>

上述的 discriminator 标签作用是,当 Employee(员工)gender(性别) 属性为 1 时,才级联查询 Department(部门) 的信息,否则不级联查询。