在这里引用一下Mybatis官网对resultMap的介绍:
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份resultMap 能够代替实现同等功能的数千行代码。ResultMap的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
既然官网都说它是最重要最强大的元素,那就让我们来看看它重要在哪里,强大在哪里吧!
在此之前,先说一说我们前面几篇博客用到的一个关键字resultType
前面我们写过这样的查询语句:
<select id="selectById" resultType="com.yky.springboot.entities.User">
SELECT * FROM `user` WHERE id = #{id}
select>
对应的实体类是这样的:
package com.yky.springboot.entities;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Long id;
private String name;
private String phone;
private Date birthday;
Getter、Setter方法....
toString方法...
}
数据库中的字段是这样的:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`birthday` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生日',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
可以看到,数据表中的字段名和Java实体类中的字段名完全一致。此时我们便可以直接用resultType来指定将SQL执行结果映射到com.yky.springboot.entities.User类。
当然了,重复的写完整的类名看起来并不是很优雅,此时我们可以使用类型别名来解决,Mybatis为我们提供了typeAlias关键字,typeAlias需要写在Mybatis的全局配置文件mybatis-config.xml中。在resources下创建mybatis-config.xml文件,并写入以下代码:
<configuration>
<typeAliases>
<typeAlias type="com.yky.springboot.entities.User" alias="User">typeAlias>
typeAliases>
configuration>
还需要在yml文件中指定一下Mybatis全局配置文件所在路径:
mybatis:
#配置mybatis映射文件路径
mapper-locations: classpath:mapper/*.xml
#配置mybatis全局配置文件所在路径
config-location: classpath:mybatis-config.xml
此时同样的SQL语句可以这样写:
<select id="selectById" resultType="User">
SELECT * FROM `user` WHERE id = #{id}
select>
1. 数据库字段需要与实体类字段完全一致
这一点不用多说,毕竟字段不一样的话Mybatis是不知道怎么匹配的。
2. 对多表关联不友好甚至根本满足不了多表关联需求(一对一、一对多、多对多)
比如有一个员工类:Employee
有一个部门类:Department
部门类中有一个List employees属性,包含部门所有员工,在这里部门与员工之间属于一对多的关系。
有以下需求:
[1] 只通过一条多表查询语句,直接查询到部门信息,以及当前部门的所有员工
[2] 将上述SQL语句查询到的信息封装到Department类中(employees保存所有员工列表)
显然这样做使用resultType是满足不了的。
鉴于resultType有种种限制,resultMap便应运而生了。
resultMap最简单的应用场景是:解决数据库字段与实体属性名不一致的问题。
接下来直接上代码
有员工类:
public class Employee implements Serializable {
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 工号
*/
private String number;
/**
* 部门编号
*/
private Long departmentId;
/**
* 薪资
*/
private Double salary;
Getter(),Setter().....
toString()....
}
有数据表:
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`employee_name` varchar(255) NOT NULL,
`number` varchar(255) NOT NULL,
`department_id` int(11) NOT NULL,
`salary` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
很显然,数据表中的列名与实体类中的字段名并不一致,这时就需要用到resultMap来指定数据表中的字段与实体属性字段的一一对应关系了。
Mapper接口代码:
@Mapper
public interface EmployeeMapper {
List<Employee> selectAll();
}
写在EmployeeMapper.xml中的resultMap代码:
<resultMap id="empMap" type="com.yky.springboot.entities.Employee">
<id column="id" property="id"/>
<result column="employee_name" property="name"/>
<result column="number" property="number"/>
<result column="department_id" property="departmentId"/>
<result column="salary" property="salary"/>
resultMap>
写在EmployeeMapper.xml中的SQL查询语句代码:
<select id="selectAll" resultMap="empMap">
SELECT * FROM employee
select>
上面我所举的例子是resultMap的最简单的应用了,resultMap的高级应用则体现在了多表联合映射上。
resultMap高级结果映射,即在多表联合查询时,将结果联合映射到目标实体中。这里的目标实体通常包含其他实体对象、集合等元素。常用在一对一,一对多、多对一、多对多的关系表中。
在此之前,先列出resultMap的子元素(摘自官方教程):
在这里,我们不去讨论数据表的一对一、一对多、多对一、多对多关系。我们只试图通过下面两节展示一个稍微复杂一点的例子来演示resultMap的高级用法。
假设我们需要定义一个实体类来表示一个大学生,需要包含以下信息:
社团实体类:
public class Club implements Serializable {
//社团数据表主键
private Long id;
//社团名称
private String name;
//社团描述
private String desc;
Getter、Setter、toString....
}
专业实体类:
public class Major implements Serializable {
//专业数据表主键id
private Long id;
//专业名称
private String name;
//专业创办时间
private Date startTime;
//年级,如2014级
private Integer grade;
Getter、Setter、toString....
}
学生实体类:
public class Student implements Serializable {
//数据表主键id
private Long id;
//姓名
private String name;
//性别:1,男;2女
private Integer sex;
//年龄
private Integer age;
//学号
private String codeNumber;
//所加入的社团
private List<Club> clubs;
//班级
private Major major;
Getter、Setter、toString....
}
在这里我们需要三个数据表,分别是:
创建表的SQL语句
CREATE TABLE `club` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`desc` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
CREATE TABLE `major` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`start_time` timestamp NULL DEFAULT NULL,
`grade` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`sex` tinyint(4) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`code_number` varchar(64) DEFAULT NULL,
`major_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_major` (`major_id`),
CONSTRAINT `fk_major` FOREIGN KEY (`major_id`) REFERENCES `major` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
CREATE TABLE `student_club` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`student_id` bigint(20) NOT NULL,
`club_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_student` (`student_id`),
KEY `fk_club` (`club_id`),
CONSTRAINT `fk_club` FOREIGN KEY (`club_id`) REFERENCES `club` (`id`),
CONSTRAINT `fk_student` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
导入数据的SQL语句
INSERT INTO `club`(`id`, `name`, `desc`) VALUES (1, '电子科技创新协会', '电子爱好者聚集地');
INSERT INTO `club`(`id`, `name`, `desc`) VALUES (2, '计算机协会', '程序员聚集地');
INSERT INTO `club`(`id`, `name`, `desc`) VALUES (3, '轮滑社', '滑样年华');
INSERT INTO `club`(`id`, `name`, `desc`) VALUES (4, '桥牌协会', '桥牌锦标赛等你来挑战');
INSERT INTO `club`(`id`, `name`, `desc`) VALUES (5, '摄影协会', '摄影穷三代,想好了再来');
INSERT INTO `major`(`id`, `name`, `start_time`, `grade`) VALUES (1, '电子信息工程', '1990-06-21 00:00:00', 2014);
INSERT INTO `major`(`id`, `name`, `start_time`, `grade`) VALUES (2, '电子信息工程', '1990-06-21 00:00:00', 2015);
INSERT INTO `major`(`id`, `name`, `start_time`, `grade`) VALUES (3, '计算机科学与技术', '2002-05-10 00:00:00', 2014);
INSERT INTO `major`(`id`, `name`, `start_time`, `grade`) VALUES (4, '通信工程', '2002-05-10 00:00:00', 2016);
INSERT INTO `student`(`id`, `name`, `sex`, `age`, `code_number`, `major_id`) VALUES (3, '张三', 1, 21, '2016100001', 4);
INSERT INTO `student`(`id`, `name`, `sex`, `age`, `code_number`, `major_id`) VALUES (4, '李四', 2, 23, '2015100001', 2);
INSERT INTO `student`(`id`, `name`, `sex`, `age`, `code_number`, `major_id`) VALUES (5, '王五', 1, 24, '2014010012', 3);
INSERT INTO `student`(`id`, `name`, `sex`, `age`, `code_number`, `major_id`) VALUES (6, '李明', 1, 24, '2014101003', 1);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (3, 3, 2);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (4, 3, 5);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (6, 4, 1);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (7, 5, 3);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (8, 5, 4);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (9, 5, 5);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (10, 6, 1);
INSERT INTO `student_club`(`id`, `student_id`, `club_id`) VALUES (11, 6, 2);
数据表已经创建好了,数据也有了,接下来就是写SQL语句根据id把student的信息全部查出来。如果你对数据库的多表查询还不太了解,可以看本人的这篇博客学习。
SELECT
student.id AS id,
student.age AS age,
student.sex AS sex,
student.code_number AS code_number,
student.NAME AS name,
major.id AS major_id,
major.NAME AS major_name,
major.start_time AS major_start_time,
major.grade AS grade,
club.id AS club_id,
club.name AS club_name,
club.desc AS club_desc
FROM
student
JOIN major ON student.major_id = major.id AND student.id = 5
LEFT JOIN student_club ON student.id = student_club.student_id
LEFT JOIN club ON student_club.club_id = club.id
查询结果:
既然SQL查询语句确认了,接下来就开始写数据访问层代码了。
StudentMapper.java接口文件代码:
@Mapper
public interface StudentMapper {
Student findById(@Param("stuId") Long id);
}
StudentMapper.xml代码:
<mapper namespace="com.yky.springboot.mapper.StudentMapper">
<resultMap id="clubMap" type="Club">
<id property="id" column="club_id"/>
<result property="name" column="club_name"/>
<result property="desc" column="club_desc"/>
resultMap>
<resultMap id="stuMap" type="Student">
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="codeNumber" column="code_number"/>
<result property="name" column="name"/>
<association property="major" javaType="Major">
<id property="id" column="major_id"/>
<result property="name" column="major_name"/>
<result property="startTime" column="major_start_time"/>
<result property="grade" column="grade"/>
association>
<collection property="clubs" resultMap="clubMap"/>
resultMap>
<select id="findById" resultMap="stuMap">
SELECT
student.id AS id,
student.age AS age,
student.sex AS sex,
student.code_number AS code_number,
student.NAME AS name,
major.id AS major_id,
major.NAME AS major_name,
major.start_time AS major_start_time,
major.grade AS grade,
club.id AS club_id,
club.name AS club_name,
club.desc AS club_desc
FROM
student
JOIN major ON student.major_id = major.id AND student.id = #{stuId}
LEFT JOIN student_club ON student.id = student_club.student_id
LEFT JOIN club ON student_club.club_id = club.id
select>
mapper>
单元测试代码:
@SpringBootTest
class StudentMapperTest {
@Autowired
StudentMapper studentMapper;
@Test
void findById() {
Student student = studentMapper.findById(5L);
System.out.println(student);
}
}
在第4节中,我们用一条语句实现了多表联合查询操作。其实,并非只有这一条途径可进行多表关联。resultMap的强大、灵活,可以让我们通过组合多个数据访问层的查询方法来实现多表关联操作。
这种方式具有以下特点。
优点:
缺点:
下面来实现一下:
MajorMapper.java接口:
@Mapper
public interface MajorMapper {
Major findById(@Param("id") Long id);
}
MajorMapper.xml:
<mapper namespace="com.yky.springboot.mapper.MajorMapper">
<select id="findById" resultType="Major">
SELECT * FROM major WHERE id = #{id}
select>
mapper>
ClubMapper.java接口:
@Mapper
public interface ClubMapper {
Club findByStudentId(@Param("sid") Long sid);
}
ClubMapper.xml:
<mapper namespace="com.yky.springboot.mapper.ClubMapper">
<select id="findByStudentId" resultType="Club">
SELECT
club.id,
club.name,
club.desc
FROM
student_club,
club
WHERE
student_club.club_id = club.id
AND student_club.student_id = #{sid}
select>
mapper>
StudentMapper.java接口新增加的方法:
Student lazyFindById(@Param("stuId") Long id);
StudentMapper.xml新增加的映射:
<resultMap id="lazyStuMap" type="Student">
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="codeNumber" column="code_number"/>
<result property="name" column="name"/>
<association select="com.yky.springboot.mapper.MajorMapper.findById" property="major" column="major_id"/>
<collection select="com.yky.springboot.mapper.ClubMapper.findByStudentId" property="clubs" column="id"/>
resultMap>
<select id="lazyFindById" resultMap="lazyStuMap">
SELECT student.* FROM student WHERE id = #{stuId}
select>
单元测试代码:
@Test
void lazyFindId() {
Student student = studentMapper.lazyFindById(5L);
System.out.println("--------------------------分割线-----------------------");
System.out.println(student);
}
输出结果:
可以看到,当前程序运行结果与第三节的运行结果一致,不同的是这一次查询是通过执行三条SQL语句完成的。
在简单的场景下,MyBatis 可以为你自动映射查询结果(resultType)。但如果遇到复杂的场景,你需要构建一个结果映射(resultMap)。Mybatis支持两种方式混合使用。
有三种自动映射等级:
Mybatis默认使用PARTIAL等级,即如果程序员针对某些属性指定了映射关系,则按照指定的来。没有指定的属性,才会自动映射。当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。
因此,第5节的resultMap可以写成下面这样的:
<resultMap id="lazyStuMap" type="Student">
<id property="id" column="id"/>
<result property="codeNumber" column="code_number"/>
<association select="com.yky.springboot.mapper.MajorMapper.findById" property="major" column="major_id"/>
<collection select="com.yky.springboot.mapper.ClubMapper.findByStudentId" property="clubs" column="id"/>
resultMap>
age、sex、name字段完全可以让Mybatis为我们自动映射。
我们还可以看到codeNumber与code_number这样的。通常数据库列使用小写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。
mybatis-config.xml代码如下:
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<typeAliases>
<typeAlias type="com.yky.springboot.entities.User" alias="User">typeAlias>
<typeAlias type="com.yky.springboot.entities.Student" alias="Student">typeAlias>
<typeAlias type="com.yky.springboot.entities.Major" alias="Major">typeAlias>
<typeAlias type="com.yky.springboot.entities.Club" alias="Club">typeAlias>
typeAliases>
configuration>
开启了驼峰命名后,上面写的resultMap可以再精简:
<resultMap id="lazyStuMap" type="Student">
<id property="id" column="id"/>
<association select="com.yky.springboot.mapper.MajorMapper.findById" property="major" column="major_id"/>
<collection select="com.yky.springboot.mapper.ClubMapper.findByStudentId" property="clubs" column="id"/>
resultMap>
无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用/禁用自动映射。
关于结果映射就讲到这里,这已经涵盖了绝大多数的应用场景。至于本文没讲到的,可以去官网查看相关说明文档。欢迎大家在评论区讨论,也欢迎大家指出本文章的错误。如有疑问,可直接在评论区留言,我看到后就会回复。