MyBatis教程[5]----resultMap高级结果映射、自动映射

目录

    • 1.回顾resultType
      • 1.1 使用回顾
      • 1.2 简化全类名
      • 1.3 局限性
    • 2.resultMap简单使用
    • 3.resultMap高级结果映射
    • 4.一条SQL语句实现多表关联映射
      • 4.1 实体类创建
      • 4.2 数据表创建
      • 4.3 查询语句编写
      • 4.4 数据访问层代码编写
      • 4.5 单元测试
    • 5.多条语句组合实现多表关联映射
      • 5.1 实现Major实体的数据访问层
      • 5.2 实现Club实体的数据访问层
      • 5.3 Student数据访问层调用Major、Club代码
      • 5.4 单元测试
    • 6. 自动映射
    • 7. 写在最后

在实际应用中,我们常常会遇到数据库中的字段和Java实体中的属性名不一致的情况,或在多表查询时需要将多个表的数据与实体属性关联起来的情况,此时就需要使用resultMap进行结果映射了。

在这里引用一下Mybatis官网对resultMap的介绍:

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份resultMap 能够代替实现同等功能的数千行代码。ResultMap的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

既然官网都说它是最重要最强大的元素,那就让我们来看看它重要在哪里,强大在哪里吧!

1.回顾resultType

1.1 使用回顾

在此之前,先说一说我们前面几篇博客用到的一个关键字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类。

1.2 简化全类名

当然了,重复的写完整的类名看起来并不是很优雅,此时我们可以使用类型别名来解决,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.3 局限性

1. 数据库字段需要与实体类字段完全一致

这一点不用多说,毕竟字段不一样的话Mybatis是不知道怎么匹配的。

2. 对多表关联不友好甚至根本满足不了多表关联需求(一对一、一对多、多对多)

比如有一个员工类:Employee
有一个部门类:Department
部门类中有一个List employees属性,包含部门所有员工,在这里部门与员工之间属于一对多的关系。
有以下需求:
[1] 只通过一条多表查询语句,直接查询到部门信息,以及当前部门的所有员工
[2] 将上述SQL语句查询到的信息封装到Department类中(employees保存所有员工列表)

显然这样做使用resultType是满足不了的。

鉴于resultType有种种限制,resultMap便应运而生了。

2.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的高级应用则体现在了多表联合映射上。

3.resultMap高级结果映射

resultMap高级结果映射,即在多表联合查询时,将结果联合映射到目标实体中。这里的目标实体通常包含其他实体对象、集合等元素。常用在一对一,一对多、多对一、多对多的关系表中。

在此之前,先列出resultMap的子元素(摘自官方教程):

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

在这里,我们不去讨论数据表的一对一、一对多、多对一、多对多关系。我们只试图通过下面两节展示一个稍微复杂一点的例子来演示resultMap的高级用法。

4.一条SQL语句实现多表关联映射

4.1 实体类创建

假设我们需要定义一个实体类来表示一个大学生,需要包含以下信息:

  • 姓名、性别、年龄、学号,这些属于基本信息
  • 所加入的社团,这里需要是一个包含社团对象的集合
  • 所属专业(在这里不考虑多学位情况),这里需要是一个表示专业的对象

社团实体类:

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....
}

4.2 数据表创建

在这里我们需要三个数据表,分别是:

  • major表,用于表示专业
  • club表,用于表示社团
  • student表,用于表示学生
  • student_club表,中间表,用于表示学生表与社团表之间的一对多关系

三个表的模型如下:
MyBatis教程[5]----resultMap高级结果映射、自动映射_第1张图片
数据表创建以及导入的sql语句如下:

创建表的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的信息全部查出来。如果你对数据库的多表查询还不太了解,可以看本人的这篇博客学习。

4.3 查询语句编写

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查询语句确认了,接下来就开始写数据访问层代码了。

4.4 数据访问层代码编写

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>

4.5 单元测试

单元测试代码:

@SpringBootTest
class StudentMapperTest {
     

    @Autowired
    StudentMapper studentMapper;

    @Test
    void findById() {
     
        Student student = studentMapper.findById(5L);

        System.out.println(student);

    }
}

输出结果:
在这里插入图片描述

5.多条语句组合实现多表关联映射

在第4节中,我们用一条语句实现了多表联合查询操作。其实,并非只有这一条途径可进行多表关联。resultMap的强大、灵活,可以让我们通过组合多个数据访问层的查询方法来实现多表关联操作。

这种方式具有以下特点。

优点:

  • 不必使用多表连接查询语句去查询多表,只需要查询当前表即可
  • 按需调用所关联表的查询语句,去查询所关联的表
  • 把单条语句进行了分解,不容易出错,且更简洁

缺点:

  • 可能没有单条语句效率高(单次查询通过执行多条SQL语句实现)

下面来实现一下:

5.1 实现Major实体的数据访问层

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>

5.2 实现Club实体的数据访问层

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>

5.3 Student数据访问层调用Major、Club代码

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>

5.4 单元测试

单元测试代码:

    @Test
    void lazyFindId() {
     
        Student student = studentMapper.lazyFindById(5L);
        System.out.println("--------------------------分割线-----------------------");
        System.out.println(student);
    }

输出结果:
在这里插入图片描述
可以看到,当前程序运行结果与第三节的运行结果一致,不同的是这一次查询是通过执行三条SQL语句完成的。

6. 自动映射

在简单的场景下,MyBatis 可以为你自动映射查询结果(resultType)。但如果遇到复杂的场景,你需要构建一个结果映射(resultMap)。Mybatis支持两种方式混合使用。

有三种自动映射等级:

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

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 属性来为指定的结果映射设置启用/禁用自动映射。
MyBatis教程[5]----resultMap高级结果映射、自动映射_第2张图片

7. 写在最后

关于结果映射就讲到这里,这已经涵盖了绝大多数的应用场景。至于本文没讲到的,可以去官网查看相关说明文档。欢迎大家在评论区讨论,也欢迎大家指出本文章的错误。如有疑问,可直接在评论区留言,我看到后就会回复。

你可能感兴趣的:(Mybatis,mysql,mybatis)