MyBatis

Mybatis上手教程–select语句的resultMap相关配置

一为什么要用resultMap

场景1:当我们的数据库表中的字段名为last_name,email,而对应的Java bean实体类中的属性名为lastName,mail时,此时,我们的查询SQL若是直接写成类似select * from tbl_employee时,最终Java代码执行的结果,看到类似于:lastName=null, gender=’男’, mail=’null’的情况。当然,解决该类问题的方式,我们可以通过在Mybatis主配置文件中,通过settings标签来配置驼峰命名转换规则来处理last_name和lastName的映射关系,比如:

    <settings>
       <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>

或者,我们也可以在SQL mapper文件的SQL语句中,给对应的列取别名:

select last_name as lastName,email as mail from tbl_employee

这2种思路都可以解决响应的问题。

但是,我们也可以通过在具体的SQL mapper文件中,通过定义resultMap标签来完成。

场景2:如果我们的Javabean实体类Employee里有一个属性,指向另外一个实体类Department。用于表示该员工所属的部门信息的话。那么,此时,如果我们想要查询的时候,不但要查询员工的信息,还想要连带查询部门名称的话。我们就得依赖resultMap标签了。当然,这种情况下,我们的SQL语句,直接写一个关联的join查询就行,比较简单。

当然,我们得知道在select语句中resultMap和resultType二者是互斥的。即只能选用其中一个,二者不可兼用。

二用resultMap来解决别名映射的问题

1 EmployeeMapper接口文件,EmployeeMapper.java
Employee getEmployeeByIdViaResultMap(Integer id);
2 SQL mapper文件,EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
       <!DOCTYPE mapper
               PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
               "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.knockatdatabase.dao.EmployeeMapper">
   <resultMap id="myEmployee" type="com.knockatdatabase.Employee">
       <id column="id" property="id"></id>
       <result column="email" property="mail"></result>
       <result column="gender" property="gender"></result>
       <result column="last_name" property="lastName"/>
  </resultMap>
   <select id="getEmployeeByIdViaResultMap" resultMap="myEmployee">
       select * from tbl_employee where id=#{id}
  </select>
</mapper>

注意:此时的SQL mapper文件中,多了一个标签resultMap,关于它的解释和说明如下:

  • id=”myEmployee”,是关于该resultMap的标识符,将来引用的时候,就是用的这个名称;
  • type=”com.knockatdatabase.Employee”,表示的是要对这个具体的Javabean的实体类做结果字段的映射;
  • <id column=”id” property=”id”></id>,表示的是要映射数据库表里的主键和javabean实体类的属性映射;
  • 剩下的其它字段则用result标签来映射;
  • 其中的配置,column表示数据库表中的字段,property则对应着javabean实体类的属性;

然后,在我们的SQL中,选用的是上面配置的那个resultMap,而不再使用resultType了。

3 测试类Java代码,EmployeeTest.java
    @Test
   public void testGetEmployeeByIdViaResultMap() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           Employee employee = mapper.getEmployeeByIdViaResultMap(9);
           System.out.println(employee);
      } catch (IOException e) {
           e.printStackTrace();
      }finally {
           sqlSession.close();
      }
  }
//结果:
Employee{id=9, lastName='Huang', gender='男', mail='hensi163@163.com'}

三 用resultMap来解决实体类引用另一个实体类的问题

实体类员工表里有一个属性,指向另外一个实体类Department,而且数据库里员工表上有个外键约束指向主表部门表的主键字段。

现在,想要实现查询员工信息的时候,连带员工所在部门名称一起查询出来。

1添加tbl_dept表,并给tbl_employee添加外键约束,添加测试数据
[mysql@ppasdev ~]$ mysql  -umybatis_project -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2634
Server version: 5.6.32-log MySQL Community Server (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mybatis_project    |
| test               |
+--------------------+
3 rows in set (0.16 sec)
mysql> use mybatis_project;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------------------+
| Tables_in_mybatis_project |
+---------------------------+
| tbl_employee              |
+---------------------------+
1 row in set (0.00 sec)
mysql> create table tbl_dept(id int primary key auto_increment,dept_name varchar(20));
Query OK, 0 rows affected (1.88 sec)

mysql> desc tbl_dept;
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| dept_name | varchar(20) | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> alter table tbl_employee add column did int, add constraint fk_emp_dept foreign key(did) references tbl_dept(id) on delete cascade;
Query OK, 8 rows affected (0.89 sec)
Records: 8 Duplicates: 0  Warnings: 0

mysql> desc tbl_employee;
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| last_name | varchar(50) | YES  |     | NULL    |                |
| gender    | char(1)     | YES  |     | NULL    |                |
| email     | varchar(30) | YES  |     | NULL    |                |
| did       | int(11)     | YES  | MUL | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

mysql> insert into tbl_dept values(1,'市场部');
Query OK, 1 row affected (0.04 sec)

mysql> insert into tbl_dept values(2,'财务部');
Query OK, 1 row affected (0.04 sec)

mysql> update tbl_employee set did=1 where id=1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update tbl_employee set did=2 where id>1;
Query OK, 7 rows affected (0.09 sec)
Rows matched: 7  Changed: 7  Warnings: 0

mysql> select * from tbl_employee;
+----+-----------+--------+------------------------------+------+
| id | last_name | gender | email                        | did  |
+----+-----------+--------+------------------------------+------+
|  1 | 黄伟      | 男     | huangwei@knockatdatabase.com |    1 |
|  2 | Huang     | ?      | hensi163@163.com             |    2 |
|  4 | Huang     | 男     | hensi163@163.com             |    2 |
|  5 | Huang     | 男     | hensi163@163.com             |    2 |
|  6 | Huang     | 男     | hensi163@163.com             |    2 |
|  7 | Huang     | 男     | hensi163@163.com             |    2 |
|  8 | Huang     | 男     | hensi163@163.com             |    2 |
|  9 | Huang     | 男     | hensi163@163.com             |    2 |
+----+-----------+--------+------------------------------+------+
8 rows in set (0.03 sec)

mysql> select * from tbl_dept;
+----+-----------+
| id | dept_name |
+----+-----------+
|  1 | 市场部    |
|  2 | 财务部    |
+----+-----------+
2 rows in set (0.00 sec)

mysql>
2 构建Department实体类,并修改Employee实体类
Department实体类:
package com.knockatdatabase;

/**
* @Author:asher
* @Date:2021/10/10 11:16
* @Description:com.knockatdatabase
* @Version:1.0
*/
public class Department {
   private int id;
   private String deptName;

   public Department() {
  }

   public Department(int id, String deptName) {
       this.id = id;
       this.deptName = deptName;
  }

   @Override
   public String toString() {
       return "Department{" +
               "id=" + id +
               ", deptName='" + deptName + '\'' +
               '}';
  }
}
employee实体类:
package com.knockatdatabase;

/**
* @Author:asher
* @Date:2021/9/28 12:21
* @Description:com.knockatdatabase
* @Version:1.0
*/
public class Employee {
   private Integer id;
   private String lastName;
   private String gender;
   private String mail;
   private Department department;

   public Employee() {
  }

   public Employee(Integer id, String lastName, String gender, String mail, Department department) {
       this.id = id;
       this.lastName = lastName;
       this.gender = gender;
       this.mail = mail;
       this.department = department;
  }

   public Integer getId() {
       return id;
  }

   public void setId(Integer id) {
       this.id = id;
  }

   public String getLastName() {
       return lastName;
  }

   public void setLastName(String lastName) {
       this.lastName = lastName;
  }

   public String getGender() {
       return gender;
  }

   public void setGender(String gender) {
       this.gender = gender;
  }

   public String getMail() {
       return mail;
  }

   public void setMail(String mail) {
       this.mail = mail;
  }

   public Department getDepartment() {
       return department;
  }

   public void setDepartment(Department department) {
       this.department = department;
  }

   @Override
   public String toString() {
       return "Employee{" +
               "id=" + id +
               ", lastName='" + lastName + '\'' +
               ", gender='" + gender + '\'' +
               ", mail='" + mail + '\'' +
               ", department=" + department +
               '}';
  }
}

在之前的Employee实体类的基础上,添加一个私有成员变量,其类型是Department类型的,然后添加该字段的getter/setter,以及toString(),构造方法。

3 EmployeeMapper.java接口添加获取员工和部门信息的新方法
Employee getEmployeeAndDepartmentById(Integer id);
4 SQL mapper文件,编写新的resultMap
    <resultMap id="myEmpAndDept" type="com.knockatdatabase.Employee">
       <id column="id" property="id"></id>
       <result column="last_name" property="lastName"></result>
       <result column="gender" property="gender"></result>
       <result column="email" property="mail"></result>
       <result column="dept_name" property="department.deptName"></result>
  </resultMap>


<select id="getEmployeeAndDepartmentById" resultMap="myEmpAndDept">
      select emp.id,emp.gender,emp.email mail,emp.last_name,dept.dept_name
      from tbl_employee emp,tbl_dept dept
      where emp.did=dept.id and emp.id=#{id}
</select>

这里要务必注意,前面的resultMap是一个新构建出来的,其中 <result column=”dept_name” property=”department.deptName”></result> 表示关联查询的SQL结果中的dept_name字段,对应Employee实体类的Department类型的成员变量department的deptName。是一种吉联的属性关联方法。

5 测试类代码
    @Test
   public void testGetEmployeeAndDepartmentById() {
       String resource = "mybatis-config.xml";
       SqlSession sqlSession = null;
       try{
           InputStream inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
           sqlSession = sqlSessionFactory.openSession();
           EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
           Employee employeeAndDepartmentById = mapper.getEmployeeAndDepartmentById(1);
           System.out.println("employee info: " + employeeAndDepartmentById);
           Department department = employeeAndDepartmentById.getDepartment();
           System.out.println("department info:" + department);
      } catch (IOException e) {
           e.printStackTrace();
      } finally {
          sqlSession.close();
      }
  }
//结果
employee info: Employee{id=1, lastName='黄伟', gender='男', mail='null', department=Department{id=0, deptName='市场部'}}
department info:Department{id=0, deptName='市场部'}

注意:该案例中,为什么输出的结果中mail=’null’???深入分析,会发现,SQL语句中,使用了列别名的形式,”select emp.id,emp.gender,emp.email mail,emp.last_name,dept.dept_name”,已经将email取别名为mail了,而在resultMap中,我们又再次的将<result column=”email” property=”mail”></result>,也就意味着要把SQL语句中的email转为mail,可是此时SQL语句中,已经没有了email列了,于是Mybatis就将NULL再次赋值给mail列了。所以,最后输出的结果成了mail=’null’。也就说说,我们多此一举的画蛇添足了。

四小结

  • resultMap和resultType只能二选一,不能同时使用;
  • resultMap可以解决字段别名映射的问题,即库表字段名和实体类属性名不匹配的情形;
  • resultMap可以解决实体类中的属性引用另外一个实体类的情形,如员工类里有一个字段指向部门表的实体类,适用场景是查询员工信息时,连带其部门信息一起查询;

留言