Skip to content

🤖 Tlias 智能学习辅助系统_2


书接上回 ,在上篇已经完成了 部门管理 模块的代码编写,并且学习了日志技术

接着从 员工管理 这个模块续下去 ... ...


七、员工列表查询


那接下来,我们要来完成的是员工列表的查询功能实现。 具体的需求如下:

3bdf7784-a579-4ddd-8d57-6e9cb06c9898

在查询员工列表数据时,既需要查询 员工的基本信息,还需要查询员工所属的部门名称,

所以这里呢,会涉及到 多表查询 的操作。


而且,在查询员工列表数据时,既要考虑搜索栏中的查询条件,还要考虑对查询的结果进行分页处理。

那么接下来,我们在实现这个功能时,将会分为三个部分来逐一实现:

  • 准备工作
  • 分页查询
  • 条件分页查询

7.1 准备工作


需求:查询所有员工信息,并查询出部门名称。(涉及到的表:empdept

49a1ca70-4f4a-47b0-b165-9f3e4a6c254b

7.1.1 基础代码准备


  • 1). 创建员工管理相关表结构
sql
-- 员工表
create table emp(
    id int unsigned primary key auto_increment comment 'ID,主键',
    username varchar(20) not null unique comment '用户名',
    password varchar(50) default '123456' comment '密码',
    name varchar(10) not null comment '姓名',
    gender tinyint unsigned not null comment '性别, 1:男, 2:女',
    phone char(11) not null unique comment '手机号',
    job tinyint unsigned comment '职位, 1 班主任, 2 讲师 , 3 学工主管, 4 教研主管, 5 咨询师',
    salary int unsigned comment '薪资',
    image varchar(300) comment '头像',
    entry_date date comment '入职日期',
    dept_id int unsigned comment '部门ID',
    create_time datetime comment '创建时间',
    update_time datetime comment '修改时间'
) comment '员工表';


INSERT INTO emp VALUES 
    (1,'shinaian','123456','施耐庵',1,'13309090001',4,15000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2000-01-01',2,'2023-10-20 16:35:33','2023-11-16 16:11:26'),
    (2,'songjiang','123456','宋江',1,'13309090002',2,8600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:37'),
    (3,'lujunyi','123456','卢俊义',1,'13309090003',2,8900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:39'),
    (4,'wuyong','123456','吴用',1,'13309090004',2,9200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:41'),
    (5,'gongsunsheng','123456','公孙胜',1,'13309090005',2,9500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-12-05',2,'2023-10-20 16:35:33','2023-10-20 16:35:43'),
    (6,'huosanniang','123456','扈三娘',2,'13309090006',3,6500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:45'),
    (7,'chaijin','123456','柴进',1,'13309090007',1,4700,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2005-08-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:47'),
    (8,'likui','123456','李逵',1,'13309090008',1,4800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2014-11-09',1,'2023-10-20 16:35:33','2023-10-20 16:35:49'),
    (9,'wusong','123456','武松',1,'13309090009',1,4900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-03-11',1,'2023-10-20 16:35:33','2023-10-20 16:35:51'),
    (10,'linchong','123456','林冲',1,'13309090010',1,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:53'),
    (11,'huyanzhuo','123456','呼延灼',1,'13309090011',2,9700,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-02-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:55'),
    (12,'xiaoliguang','123456','小李广',1,'13309090012',2,10000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-08-18',2,'2023-10-20 16:35:33','2023-10-20 16:35:57'),
    (13,'yangzhi','123456','杨志',1,'13309090013',1,5300,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-11-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:59'),
    (14,'shijin','123456','史进',1,'13309090014',2,10600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2002-08-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:01'),
    (15,'sunerniang','123456','孙二娘',2,'13309090015',2,10900,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:03'),
    (16,'luzhishen','123456','鲁智深',1,'13309090016',2,9600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2010-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:05'),
    (17,'liying','12345678','李应',1,'13309090017',1,5800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-03-21',1,'2023-10-20 16:35:33','2023-10-20 16:36:07'),
    (18,'shiqian','123456','时迁',1,'13309090018',2,10200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:09'),
    (19,'gudasao','123456','顾大嫂',2,'13309090019',2,10500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2008-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:11'),
    (20,'ruanxiaoer','123456','阮小二',1,'13309090020',2,10800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2018-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:13'),
    (21,'ruanxiaowu','123456','阮小五',1,'13309090021',5,5200,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2015-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:15'),
    (22,'ruanxiaoqi','123456','阮小七',1,'13309090022',5,5500,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2016-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:17'),
    (23,'ruanji','123456','阮籍',1,'13309090023',5,5800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2012-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:19'),
    (24,'tongwei','123456','童威',1,'13309090024',5,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2006-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:21'),
    (25,'tongmeng','123456','童猛',1,'13309090025',5,4800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2002-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:23'),
    (26,'yanshun','123456','燕顺',1,'13309090026',5,5400,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2011-01-01',3,'2023-10-20 16:35:33','2023-11-08 22:12:46'),
    (27,'lijun','123456','李俊',1,'13309090027',2,6600,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2004-01-01',2,'2023-10-20 16:35:33','2023-11-16 17:56:59'),
    (28,'lizhong','123456','李忠',1,'13309090028',5,5000,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2007-01-01',3,'2023-10-20 16:35:33','2023-11-17 16:34:22'),
    (30,'liyun','123456','李云',1,'13309090030',NULL,NULL,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2020-03-01',NULL,'2023-10-20 16:35:33','2023-10-20 16:36:31'),
    (36,'linghuchong','123456','令狐冲',1,'18809091212',2,6800,'https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/1.jpg','2023-10-19',2,'2023-10-20 20:44:54','2023-11-09 09:41:04');
    

-- 员工工作经历信息
create table emp_expr(
    id int unsigned primary key auto_increment comment 'ID, 主键',
    emp_id int unsigned comment '员工ID',
    begin date comment '开始时间',
    end  date comment '结束时间',
    company varchar(50) comment '公司名称',
    job varchar(50) comment '职位'
)comment '工作经历';

  • 2). 准备emp表对应的实体类 EmpEmpExpr
java
package com.itheima.pojo;

import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Data
public class Emp {
    private Integer id; //ID,主键
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer gender; //性别, 1:男, 2:女
    private String phone; //手机号
    private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
    private Integer salary; //薪资
    private String image; //头像
    private LocalDate entryDate; //入职日期
    private Integer deptId; //关联的部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间

    //封装部门名称数
    private String deptName; //部门名称
}

java
package com.itheima.pojo;

import lombok.Data;

import java.time.LocalDate;

/**
 * 工作经历
 */
@Data
public class EmpExpr {
    private Integer id; //ID
    private Integer empId; //员工ID
    private LocalDate begin; //开始时间
    private LocalDate end; //结束时间
    private String company; //公司名称
    private String job; //职位
}

  • 3). 准备 Emp 员工管理的基础结构,包括 ControllerServiceMapper

  • EmpMapper
java
package com.itheima.mapper;

import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface EmpMapper {

}

  • EmpService :
java
package com.itheima.service;

public interface EmpService {
}

  • EmpServiceImpl :
java
package com.itheima.service.impl;

import com.itheima.mapper.EmpMapper;
import com.itheima.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 员工管理
 */
@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;

}

  • EmpController :
java
package com.itheima.controller;

import com.itheima.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

/**
 * 员工管理
 */
@Slf4j
@RestController
public class EmpController {

    @Autowired
    private EmpService empService;

}

7.1.2 SQL&Mapper接口


那接下来,我们就先考虑一下要查询所有的员工数据,及其关联的部门名称,这个 SQL 语句该如何实现 ?

这里,要查询所有的员工,也就意味着,即使员工没有部门,也需要将该员工查询出来 。

所以,这里需要用 左外连接 实现,具体 SQL 如下:

  • 1). 基于 AI 生成对应的 SQL 语句
sql
-- 查询所有的员工信息,如果员工关联了部门,也要查询出部门名称
select e.*, d.name as dept_name from emp e left join dept d on e.dept_id = d.id;

  • 2). Mapper 接口方法定义

那接下来,我们就定义一个员工管理的 Mapper 接口 EmpMapper 并在其中完成员工信息的查询。

具体代码如下:

java
@Mapper
public interface EmpMapper {

    /**
     * 查询所有的员工及其对应的部门名称
     */
    @Select("select e.*, d.name as deptName from emp e left join dept d on e.dept_id = d.id")
    public List<Emp> list();

}

📌 注意:

在配置了 IDEA 注解上面的 SQL 提示,且刚刚准备好了 EmpEmpExpr 两张表的情况下

写完这个 list() 的代码,会发现 emp 爆红,记得刷新一下 IDEA 的数据库连接,

刚更新的两张表还没有被识别到 ...


📌 注意:

注意,上述SQL语句中,给 部门名称起了别名 deptName ,是因为在接口文档中,

要求部门名称给前端返回的数据中,就必须叫 deptName。 而这里我们需要将查询返回的每一条记录

都封装到 Emp 对象中,那么就必须保证查询返回的字段名与属性名是一一对应的。


此时,我们就需要在 Emp 中定义一个属性 deptName 用来封装部门名称。

刚刚建实体类的时候已经携带 , 具体如下高亮:

java
@Data
public class Emp {
    private Integer id; //ID,主键
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer gender; //性别, 1:男, 2:女
    private String phone; //手机号
    private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
    private Integer salary; //薪资
    private String image; //头像
    private LocalDate entryDate; //入职日期
    private Integer deptId; //关联的部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间

    //封装部门名称数
    private String deptName; //部门名称
}

代码编写完毕后,我们可以编写一个单元测试,对上述的程序进行测试:

java
@SpringBootTest
class TliasWebManagementApplicationTests {

    @Autowired
    private EmpMapper empMapper;

    @Test
    public void testList(){
        List<Emp> empList = empMapper.list();
        empList.forEach(System.out::println);
    }
}

运行单元测试后,我们看到控制台输出的数据:

4a329ec1-cc63-4230-aa02-feb858148cc4

可以看到,员工的信息,员工关联的部门名称都查询出来了。


7.2 分页查询


7.2.1 分析

  • 上述我们在 Mapper 接口中定义了接口方法,完成了查询所有员工及其部门名称的功能,

    是将数据库中所有的数据查询出来了。 试想如果数据库中的数据有很多(假设有几千几万条)的时候,

    将数据全部展示出来肯定不现实,那如何解决这个问题呢?

  • 使用分页解决这个问题。每次只展示一页的数据,

    比如:一页展示 10 条数据,如果还想看其他的数据,可以通过点击页码进行查询。

  • 而在员工管理的需求中,就要求我们进行分页查询,展示出对应的数据。

    具体的页面原型如下:

21fa812d-8fa7-4544-8887-b0d9791eb760

要想从数据库中进行分页查询,我们要使用 LIMIT 关键字,

格式为:limit 开始索引 每页显示的条数

  • 1). 查询第 1 页数据的SQL语句是:
sql
select * from emp  limit 0,10;
  • 2). 查询第 2 页数据的SQL语句是:
sql
select * from emp  limit 10,10;
  • 3). 查询第 3 页的数据的SQL语句是:
sql
select * from emp  limit 20,10;

观察以上 SQL 语句,发现: 开始索引一直在改变 , 每页显示条数是固定的

❗ 开始索引的计算公式: 开始索引 = (当前页码 - 1) * 每页显示条数


我们继续基于页面原型,继续分析,得出以下结论:

  • 前端在请求服务端时,传递的参数
  1. 当前页码 page

  2. 每页显示条数 pageSize

  • 后端需要响应什么数据给前端

    1. 所查询到的数据列表(存储到 List 集合中)

    2. 总记录数


5cfa934d-ba5d-4780-9d01-6ac72e1b6b4c

后台给前端返回的数据包含:List 集合(数据列表)、total (总记录数)

而这两部分我们通常封装到 PageResult 对象中,并将该对象转换为 json 格式的数据响应回给浏览器。

java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
    private Long total; //总记录数
    private List<T> rows; //当前页数据列表
}

7.2.2 接口描述


  • 1). 基本信息

请求路径:/emps

请求方式:GET

接口描述:该接口用于员工列表数据的条件分页查询


  • 2). 请求参数
参数名称是否必须示例备注
name姓名
gender1性别 , 1 男 , 2 女
begin2010/1/1范围匹配的开始时间(入职日期)
end2020/1/1范围匹配的结束时间(入职日期)
page1分页查询的页码,如果未指定,默认为1
pageSize10分页查询的每页记录数,如果未指定,默认为10

请求数据样例:

java
/emps?name=&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10

  • 3). 响应数据

参数格式:application/json

参数说明:

名称类型是否必须备注
codenumber必须响应码, 1 成功, 0 失败
msgstring非必须提示信息
dataobject必须返回的数据
totalnumber必须总记录数
rowsobject[]必须数据列表
idnumber非必须id
usernamestring非必须用户名
namestring非必须姓名
passwordstring非必须密码
gendernumber非必须性别, 1 男; 2 女
imagestring非必须图像
jobnumber非必须职位, 说明: 1 班主任, 2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
salarynumber非必须薪资
entryDatestring非必须入职日期
deptIdnumber非必须部门id
deptNamestring非必须部门名称
createTimestring非必须创建时间
updateTimestring非必须更新时间

响应数据样例:

json
{
  "code": 1,
  "msg": "success",
  "data": {
    "total": 2,
    "rows": [
       {
        "id": 1,
        "username": "jinyong",
        "password": "123456",
        "name": "金庸",
        "gender": 1,
        "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
        "job": 2,
        "salary": 8000,
        "entryDate": "2015-01-01",
        "deptId": 2,
        "deptName": "教研部",
        "createTime": "2022-09-01T23:06:30",
        "updateTime": "2022-09-02T00:29:04"
      },
      {
        "id": 2,
        "username": "zhangwuji",
        "password": "123456",
        "name": "张无忌",
        "gender": 1,
        "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
        "job": 2,
        "salary": 6000,
        "entryDate": "2015-01-01",
        "deptId": 2,
        "deptName": "教研部",
        "createTime": "2022-09-01T23:06:30",
        "updateTime": "2022-09-02T00:29:04"
      }
    ]
  }
}

目前我们只考虑分页查询,先不考虑查询条件,而上述的接口文档中,

与分页查询相关的参数就两个,一个是 page,一个是 pageSize


7.2.3 原始方式


7.2.3.1 代码实现

通过查看接口文档:员工列表查询

请求路径:/emps

请求方式:GET

请求参数:跟随在请求路径后的参数字符串。 例:/emps?page=1&pageSize=10

响应数据:json 格式


  • 1). EmpMapper
java
@Mapper
public interface EmpMapper {

    /**
     * 查询总记录数
     */
    @Select("select count(*) from emp e left join dept d on e.dept_id = d.id ")
    public Long count();
    
    /**
     * 查询所有的员工及其对应的部门名称
     */
    @Select("select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id limit #{start}, #{pageSize}")
    public List<Emp> list(Integer start , Integer pageSize);

}

  • 2). EmpService
java
public interface EmpService {
    /**
     * 分页查询
     * @param page 页码
     * @param pageSize 每页记录数
     */
    PageResult<Emp> page(Integer page, Integer pageSize);
}

  • 3). EmpServiceImpl
java
@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;

    @Override
    public PageResult<Emp> page(Integer page, Integer pageSize) {
        //1. 获取总记录数
        Long total = empMapper.count();

        //2. 获取结果列表
        Integer start = (page - 1) * pageSize;
        List<Emp> empList = empMapper.list(start, pageSize);

        //3. 封装结果
        return new PageResult<Emp>(total, empList);
    }
}

  • 4). EmpController
java
@Slf4j
@RequestMapping("/emps")
@RestController
public class EmpController {
        
    @Autowired
    private EmpService empService;
        
    @GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page ,
                       @RequestParam(defaultValue = "10") Integer pageSize){
        log.info("查询员工信息, page={}, pageSize={}", page, pageSize);
        PageResult<Emp> pageResult = empService.page(page, pageSize);
        return Result.success(pageResult);
    }
        
}

📌 @RequestParam(defaultValue="默认值") //设置请求参数默认值


7.2.3.2 功能测试

功能开发完成后,重新启动项目,使用Apifox,发起 GET 请求:

99b545bc-9d91-44c1-b5b8-2057cb9cab36
7.2.3.3 前后端联调

打开浏览器,测试后端功能接口:

d64b27ca-f486-4293-95c0-8c64916a03c3

点击下面的页码,可以正常的查询出对应的数据 。


7.2.4 PageHelper 分页插件


7.2.4.1 介绍

前面我们已经完了基础的分页查询,大家会发现:分页查询功能编写起来比较繁琐。

而分页查询的功能是非常常见的,我们查询员工信息需要分页查询,将来在做其他项目时,

查询用户信息、订单信息、商品信息等等都是需要进行分页查询的。


而分页查询的思路、步骤是比较固定的。

  • Mapper 接口中定义两个方法执行两条不同的 SQL 语句:

    1. 查询总记录数
    2. 指定页码的数据列表
  • Service 当中,调用 Mapper 接口的两个方法,分别获取:总记录数、查询结果列表,

    然后在将获取的数据结果封装到 PageBean 对象中。


大家思考下:在未来开发其他项目,只要涉及到分页查询功能 (例:订单、用户、支付、商品),

都必须按照以上操作完成功能开发,是不是有个工具会有利于我们的开发效率 ...

  • 结论:原始方式的分页查询,存在着 "步骤固定"、"代码频繁" 的问题
  • 解决方案:可以使用一些现成的分页插件完成。对于 Mybatis 来讲现在最主流的就是 PageHelper

PageHelper 是第三方提供的 Mybatis 框架中的一款功能强大、

方便易用的分页插件,支持任何形式的单标、多表的分页查询。

官网:https://pagehelper.github.io/

微信截图_20250731092456

那接下来,我们可以对比一下,使用 PageHelper 分页插件进行分页

与 原始方式进行分页代码实现的上的差别 :

5ab09886-b610-4b01-bdb0-41dfa8657aaa
  • Mapper 接口层:

    • 原始的分页查询功能中,我们需要在 Mapper 接口中定义两条SQL语句。

    • PageHelper 实现分页查询之后,只需要编写一条SQL语句,

      而且不需要考虑分页操作,就是一条正常的查询语句。

  • Service 层:

    • 需要根据页码、每页展示记录数,手动的设置分页参数,调用 PageHelperstartPage() 的方法。
    • 无需手动计算起始索引,直接告诉 PageHelper 需要查询那一页的数据,每页展示多少条记录即可。

7.2.4.2 代码实现

当使用了 PageHelper 分页插件进行分页,就无需再 Mapper 中进行手动分页了。

Mapper 中我们只需要进行正常的列表查询即可。

Service 层中,调用 Mapper 的方法之前设置分页参数,

在调用 Mapper 方法执行查询之后,解析分页结果,并将结果封装到 PageResult 对象中返回。


  • 1). 在 pom.xml 引入依赖
xml
<!--分页插件PageHelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

记得刷新 maven ...


  • 2). EmpMapper :
java
/**
 * 查询所有的员工及其对应的部门名称
 */
@Select("select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id")
public List<Emp> list();

不需要写 limit 了 , 也不需要接收什么参数 ...


  • 3). EmpServiceImpl :
java
@Override
public PageResult<Emp> page(Integer page, Integer pageSize) {
    //1. 设置分页参数
    PageHelper.startPage(page,pageSize);

    //2. 执行查询
    List<Emp> empList = empMapper.list();
    Page<Emp> p = (Page<Emp>) empList;

    //3. 封装结果
    return new PageResult<Emp>(p.getTotal(), p.getResult());
}

7.2.4.3 功能测试

功能开发完成后,我们重启项目工程,打开 Apifox ,发起 GET 请求,

访问 :http://localhost:8080/emps?page=1&pageSize=5

fa7c089e-590b-46df-966b-d370a4f1c4f4

我们可以看到数据可以正常查询返回,是可以正常实现分页查询的。


7.2.4.4 实现机制

我们打开 Idea 的控制台,可以看到在进行分页查询时,输出的 SQL 语句。

f98bc14f-6f3b-4b78-9d56-809267c7be12

我们看到执行了两条 SQL 语句,而这两条 SQL 语句,其实是从我们在 Mapper 接口中定义的 SQL 演变而来的。


  • 第一条 SQL 语句,用来查询总记录数。
8f338e71-3a2b-44c7-8823-f29d7c2a574e
  • 第二条 SQL 语句,用来进行分页查询,查询指定页码对应 的数据列表。
9caf4838-8ba1-4d4e-982d-711df6420831

其实就是将我们编写的 SQL 语句进行的改造增强,在 SQL 语句之后拼接上了 limit 进行分页查询,

而由于测试时查询的是第一页,起始索引是 0 ,所以简写为 limit ?


PageHelper 在进行分页查询时,会执行上述两条 SQL 语句,并将查询到的总记录数,

与数据列表封装到了 Page 对象中,我们再获取查询结果时,只需要调用 Page 对象的方法就可以获取。


❗ 注意:

  • PageHelper 实现分页查询时,SQL 语句的结尾一定一定一定不要加分号 (否则不能拼接了) !!!
  • PageHelper 只会对紧跟在其后的第一条 SQL 语句进行分页处理。

7.3 条件查询分页


做完了分页查询后,下面我们需要在分页查询的基础上,添加条件。


7.3.1 需求


abd78270-6c4c-4636-8d6c-798d5a1049a1

通过员工管理的页面原型我们可以看到,员工列表页面的查询,不仅仅需要考虑分页,还需要考虑查询条件。

分页查询我们已经实现了,接下来,我们需要考虑在分页查询的基础上,再加上查询条件。

我们看到页面原型及需求中描述,搜索栏的搜索条件有三个,分别是:

  • 姓名:模糊匹配
  • 性别:精确匹配
  • 入职日期:范围匹配

7.3.2 接口描述

参照接口文档中的 员工管理 -> 员工列表查询


7.3.3 思路分析


微信截图_20250731101932

7.3.4 功能开发


通过查看接口文档:员工列表查询

请求路径:/emps

请求方式:GET

请求参数:

参数名称是否必须示例备注
name姓名
gender1性别,1 男,2 女
begin2010/1/1范围匹配的开始时间(入职日期)
end2020/1/1范围匹配的结束时间(入职日期)
page1分页查询的页码,如果未指定,默认为1
pageSize10分页查询的每页记录数,如果未指定,默认为10

  • 在原有分页查询的代码基础上进行改造:

  • 1). 在 EmpController 方法中通过多个方法形参,依次接收这几个参数
java
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {

    @Autowired
    private EmpService empService;

    @GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Integer gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
        log.info("查询请求参数: {}, {}, {}, {}, {}, {}", page, pageSize, name, gender, begin, end);
        PageResult<Emp> pageResult = empService.page(page, pageSize);
        return Result.success(pageResult);
    }
}

  • 2). 修改 EmpService 及 EmpServiceImpl 中的代码逻辑

    • EmpService
    java
    public interface EmpService {
        /**
         * 分页查询
         */
        PageResult<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end);
    }

    • EmpServiceImpl :
    java
    /**
     * 员工管理
     */
    @Service
    public class EmpServiceImpl implements EmpService {
    
        @Autowired
        private EmpMapper empMapper;
    
        @Override
        public PageResult<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
            //1. 设置PageHelper分页参数
            PageHelper.startPage(page, pageSize);
            //2. 执行查询
            List<Emp> empList = empMapper.list(name, gender, begin, end);
            //3. 封装分页结果
            Page<Emp> p = (Page<Emp>) empList;
            return new PageResult<Emp>(p.getTotal(), p.getResult());
        }
    }

  • 3). 调整 EmpMapper 接口方法

java
@Mapper
public interface EmpMapper {
    
    /**
     * 查询所有的员工及其对应的部门名称
     */
    public List<Emp> list(String name, Integer gender, LocalDate begin, LocalDate end);
    
}

📌 由于 SQL 语句比较复杂,建议将 SQL 语句配置在 XML 映射文件中。


  • 4). 新增 Mapper 映射文件 EmpMapper.xml
xml
<!--定义Mapper映射文件的约束和基本结构-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
    <select id="list" resultType="com.itheima.pojo.Emp">
        select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id
        where e.name like concat('%',#{name},'%')
          and e.gender = #{gender}
          and e.entry_date between #{begin} and #{end}
    </select>
</mapper>

❗ 注意:

上面 like 模糊匹配名字的位置 不要写成 '%#{name}%' ,因为 SQL 语句会变成 '%?%'

当问号出现在引号内,就不再是占位符了,而是一个普通字符串中的问号字符


功能测试:

/emps?page=1&pageSize=5&name=阮&gender=1&begin=2010-01-01&end=2020-01-01

9684fdff-ab70-4299-a706-a96e6e262f0d

测试时,需要注意传递的查询条件,有些查询条件查不到数据的,因为数据库没有符合条件的记录。


7.3.5 程序优化1


在上述分页条件查询中,请求参数比较多,有 6 个,如下所示:

  • 请求参数:/emps?page=1&pageSize=5&name=阮&gender=1&begin=2010-01-01&end=2020-01-01

那我们在 controller 层方法中,接收请求参数的时候,

直接在 controller 方法中声明这样6个参数即可,这样做,功能可以实现,但是不方便维护和管理。

7c7172b8-e1d5-4551-afd9-1b5b842154c3

那接下来呢,我们就可以通过 通义零码 这类 AI 辅助工具,对这一块儿的代码进行优化。 具体操作如下:

0ecd874b-3a3d-41b3-9499-b62004f06bf1
  • 优化思路:定义一个实体类,来封装这几个请求参数。

    【需要保证,前端传递的请求参数和实体类的属性名是一样的】


  • 1). 定义实体类:EmpQueryParam
java
package com.itheima.pojo;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;

@Data
public class EmpQueryParam {
    
    private Integer page = 1; //页码
    private Integer pageSize = 10; //每页展示记录数
    private String name; //姓名
    private Integer gender; //性别
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate begin; //入职开始时间
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate end; //入职结束时间
    
}

  • 2). EmpController 接收请求参数
java
@GetMapping
public Result page(EmpQueryParam empQueryParam) {
    log.info("查询请求参数: {}", empQueryParam);
    PageResult pageResult = empService.page(empQueryParam);
    return Result.success(pageResult);
}

  • 3). 修改 EmpService 接口方法
java
public interface EmpService {
    /**
     * 分页查询
     */
    //PageResult<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end);
    PageResult<Emp> page(EmpQueryParam empQueryParam);
}

  • 4). 修改 EmpServiceImpl 中的 page 方法
java
@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;

    /*@Override
    public PageResult<Emp> page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
        //1. 设置PageHelper分页参数
        PageHelper.startPage(page, pageSize);
        //2. 执行查询
        List<Emp> empList = empMapper.list(name, gender, begin, end);
        //3. 封装分页结果
        Page<Emp> p = (Page<Emp>) empList;
        return new PageResult<Emp>(p.getTotal(), p.getResult());
    }*/

    public PageResult<Emp> page(EmpQueryParam empQueryParam) {
        //1. 设置PageHelper分页参数
        PageHelper.startPage(empQueryParam.getPage(), empQueryParam.getPageSize());
        //2. 执行查询
        List<Emp> empList = empMapper.list(empQueryParam);
        //3. 封装分页结果
        Page<Emp> p = (Page<Emp>)empList;
        return new PageResult<Emp>(p.getTotal(), p.getResult());
    }
}

  • 5). 修改 EmpMapper 接口方法
java
@Mapper
public interface EmpMapper {

    /**
     * 查询所有的员工及其对应的部门名称
     */
//    @Select("select e.*, d.name as deptName from emp e left join dept d on e.dept_id = d.id")
//    public List<Emp> list(String name, Integer gender, LocalDate begin, LocalDate end);
    
    /**
     * 根据查询条件查询员工
     */
    List<Emp> list(EmpQueryParam empQueryParam);
}

  • EmpMapper.xml 中的配置无需修改。

代码优化完毕之后,重新启动运行测试,依然正常运行:

e574bb30-de9b-4d88-9b11-369ef7b26fec

但当我们在测试的时候,页码输入负数,查询是有问题的,查不到对应的数据了。

c0f5abff-73d5-401e-abd3-6826432d7446

那其实在 PageHelper 中,我们可以通过合理化参数配置,来解决这个问题。

直接在 application.yml 中,引入如下配置即可:

yaml
pagehelper:
  reasonable: true
  helper-dialect: mysql
  • 说明:

    • reasonable:分页合理化参数,默认值为 false

      当该参数设置为 true 时,pageNum<=0 时会查询第一页,pageNum>pages(超过总数时),

      会查询最后一页。默认 false 时,直接根据参数进行查询。

测试如下:

b00db398-5c32-47c5-9e5f-5abf3154d08d

7.3.6 程序优化2


当前,我们在查询的时候,Mapper 映射配置文件中的 SQL 语句中,查询条件是写死的。

而我们在员工管理中,根据条件查询员工信息时,查询条件是可选的,可以输入也可以不输入

89800903-dc71-4992-8c29-b623dce5f423
  • 如果只输入 姓名 这个查询条件,则 SQL 语句中只根据 name 字段查询,SQL 如下:
sql
select e.*, d.name deptName 
from emp as e left join dept as d 
on e.dept_id = d.id 
where e.name like concat('%',#{name},'%');

  • 如果只输入 性别 这个查询条件,则 SQL 语句中只根据 gender 字段查询,SQL 如下:
sql
select e.*, d.name deptName 
from emp as e left join dept as d 
on e.dept_id = d.id 
where e.gender = #{gender};

  • 如果输入 姓名性别 这两个查询条件,则 SQL 语句中要根据 namegender 两个字段查询,SQL如下:
sql
select e.*, d.name deptName 
from emp as e left join dept as d 
on e.dept_id = d.id
where 
e.name like concat('%',#{name},'%') 
and e.gender = #{gender};

我们看到,这个 SQL 语句不应该是写死的,而应该根据用户输入的条件的变化而变化。

那这里呢,就要通过 Mybatis 中的 来实现。


  • 所谓动态 SQL,指的就是随着用户的输入或外部的条件的变化而变化的 SQL 语句。

具体的代码实现如下:

xml
<!--定义Mapper映射文件的约束和基本结构-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
    <select id="list" resultType="com.itheima.pojo.Emp">
        select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id
        
        <where>
            <if test="name != null and name != ''">
                e.name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and e.gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and e.entry_date between #{begin} and #{end}
            </if>
        </where>
        
    </select>
</mapper>

在这里呢,我们用到了两个动态 SQL 的标签:<if> <where>

📌 这两个标签的具体作用如下:

<if> :判断条件是否成立,如果条件为 true ,则拼接 SQL 。

<where> :根据查询条件,来生成 where 关键字,并会自动去除条件前面多余的 andor

  • 之后就可以不使用 where 关键字而使用 where 标签,

    在开始标签和结束标签之间将所有的查询条件包裹起来

  • 之后框架会自动根据里面的查询条件来判断是否需要生成 where 关键字,

    如果里面的所有条件都不成立,就不会生成 where 关键字

  • 以及如果最终生成的 SQL 语句前面多了一个 and 或者 or,

    还可以帮助我们自动去除掉


代码优化完毕后,重新启动服务,测试如下:

44212832-d0e2-48f4-9bb4-d896db817f87
  • 没有输入任何查询条件时,运行日志如下:
f73cd862-6264-4aa1-8405-e5bdeb752de0
  • 当我们输入姓名和性别时
7fba5fe0-8cea-458b-ba20-ccf6f0ae4a38

运行日志如下:

425b97d9-951d-4712-89fa-c64f458a4639 (1)

我们可以看到,当我们输入不同的搜索条件时,会动态的根据查询条件,动态拼接 SQL 语句。


八、新增员工


完成了员工管理的列表查询功能之后,接下来呢,我们再来完成新增员工的功能。 具体的需求如下:

1280X1280 (1)

那么在新增员工的时候,涉及到以下的部分 :

  • 新增员工
  • 事务管理
  • 文件上传

8.1 需求


1280X1280

在新增员工的时候,在表单中,我们既要录入员工的基本信息,又要录入员工的工作经历信息。

员工基本信息,对应的表结构是 emp 表,员工工作经历信息,对应的表结构是 emp_expr 表,

所以这里我们要操作两张表,往两张表中保存数据。


8.2 接口描述

参照提供的接口文档中 员工管理 -> 添加员工 接口的描述。


8.3 思路分析


新增员工的具体的流程:

微信截图_20250803091318
  • 接口文档规定:
    • 请求路径:/emps
    • 请求方式:POST
    • 请求参数:Json 格式数据
    • 响应数据:Json 格式数据
  • 问题1:如何限定请求方式是 POST ? @PostMapping
  • 问题2:怎么在 controller 中接收 json 格式的请求参数?@RequestBody

8.3 功能开发


8.3.1 准备工作


准备 EmpExprMapper 接口及映射配置文件 EmpExprMapper.xml

并准备 实体类 接收前端传递的 json 格式的请求参数。


  • 1). EmpExprMapper 接口
java
@Mapper
public interface EmpExprMapper {

}

  • 2). EmpExprMapper.xml 配置文件
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.itheima.mapper.EmpExprMapper">

</mapper>

  • 3). 需要在 Emp 员工实体类中增加属性 exprList 来封装工作经历数据。 最终完整代码如下:
java
@Data
public class Emp {
    private Integer id; //ID,主键
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Integer gender; //性别, 1:男, 2:女
    private String phone; //手机号
    private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师
    private Integer salary; //薪资
    private String image; //头像
    private LocalDate entryDate; //入职日期
    private Integer deptId; //关联的部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间

    //封装部门名称数
    private String deptName; //部门名称

    //封装员工工作经历信息
    private List<EmpExpr> exprList; 
}

8.3.2 保存员工基本信息


  • 1). EmpController

EmpController 中增加 save 方法。

java
/**
 * 添加员工
 */
@PostMapping
public Result save(@RequestBody Emp emp){
    log.info("请求参数emp: {}", emp);
    empService.save(emp);
    return Result.success();
}

  • 2). EmpService & EmpServiceImpl

EmpService 中增加 save 方法

java
/**
* 添加员工
* @param emp
*/
void save(Emp emp);

EmpServiceImpl 中增加 save 方法 , 实现接口中的 save 方法

java
@Override
public void save(Emp emp) {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    
    //2.保存员工基本信息
    empMapper.insert(emp);

    //3. 保存员工的工作经历信息 - 批量 (稍后完成)
    
}

3). EmpMapper

EmpMapper 中增加 insert 方法,新增员工的基本信息。

java
/**
* 新增员工数据
*/
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into emp(username, name, gender, phone, job, salary, image, entry_date, dept_id, create_time, update_time) " +
        "values (#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime})")
void insert(Emp emp);

📌 主键返回:@Options(useGeneratedKeys = true, keyProperty = "id")

由于稍后,我们在保存工作经历信息的时候,需要记录是哪位员工的工作经历。

所以,保存完员工信息之后,是需要获取到员工的 ID 的,

那这里就需要通过 Mybatis 中提供的主键返回功能来获取。


8.3.3 批量保存工作经历


8.3.3.1 分析

一个员工,是可以有多段工作经历的,所以在页面上将来用户录入员工信息时,

可以自己根据需要添加多段工作经历。页面原型展示如下:

b9f02086-8938-403f-a4e9-769b2f5fa04e

那如果员工只有一段工作经历,我们就需要往工作经历表中保存一条记录。 执行的 SQL 如下:

ffa70d92-4868-4537-95a5-d2bfa16df0ff

如果员工有两段工作经历,我们就需要往工作经历表中保存两条记录。执行的 SQL 如下:

a71eb1c0-868b-4e07-833a-e2bfccb779bb

如果员工有三段工作经历,我们就需要往工作经历表中保存三条记录。执行的 SQL 如下:

12972ec2-ce00-4e1f-af49-243cc2aa086f

所以,这里最终我们需要执行的是批量插入数据的 insert 语句。


8.3.3.2 实现

  • 1). EmpServiceImpl

完善 save 方法中保存员工信息的逻辑。完整逻辑如下:

java
@Override
public void save(Emp emp) {
    //1.补全基础属性
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());
    //2.保存员工基本信息
    empMapper.insert(emp);

    //3. 保存员工的工作经历信息 - 批量
    
    //@Options 注解就是为了这句代码可以获取到数据库生成的id,返回给emp对象的id属性
    Integer empId = emp.getId();
    
    List<EmpExpr> exprList = emp.getExprList();
    if(!CollectionUtils.isEmpty(exprList)){
        exprList.forEach(empExpr -> empExpr.setEmpId(empId));
        empExprMapper.insertBatch(exprList);
    }
}

  • 2). EmpExprMapper
java
@Mapper
public interface EmpExprMapper {

    /**
     * 批量插入员工工作经历信息
     */
    public void insertBatch(List<EmpExpr> exprList);
}

  • 3). EmpExprMapper.xml
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.itheima.mapper.EmpExprMapper">

    <!--批量插入员工工作经历信息-->
    <insert id="insertBatch">
        insert into emp_expr (emp_id, begin, end, company, job) values
        <foreach collection="exprList" item="expr" separator=",">
            (#{expr.empId}, #{expr.begin}, #{expr.end}, #{expr.company}, #{expr.job})
        </foreach>
    </insert>

</mapper>

📌 这里用到 Mybatis 中的动态 SQL 里提供的 foreach 标签

改标签的作用,是用来遍历循环,常见的属性说明:

  • collection:集合名称

  • item:集合遍历出来的元素/项

  • separator:每一次遍历使用的分隔符

  • open:遍历开始前拼接的片段

  • close:遍历结束后拼接的片段

上述的属性,是可选的,并不是所有的都是必须的。 可以自己根据实际需求,来指定对应的属性。


8.4 ApiFox 导入接口


在前面我们使用 ApiFox 一直都是使用快捷请求的方式,目的是为了熟悉使用这款工具

那么学到这里,应该是比较熟悉了,那么我们就可以导入前面接口文档文件夹中

提供过的 Web开发-AI.openapi.json 这个文件来导入接口的信息

从而提高我们的接口测试的效率 ...

txt
通过网盘分享的文件:接口文档
链接: https://pan.baidu.com/s/1l0F6CE-8q0cgR7YoK0D6MA?pwd=musv 提取码: musv

微信截图_20250805092423
  • 含有这个项目的全部 30 个接口
微信截图_20250805092607
  • 导入成功
微信图片_20250805092724
  • 目前可以看见,地址栏只有资源路径,没有协议、IP地址 和 端口号那些 ...

    所以我们要先配置好环境:

微信截图_20250805092936
微信截图_20250805093217
  • 这样就可以使用导入的接口来测试了

8.5 功能测试


代码开发完成后,重启服务器,打开 Apifox

增加接口文档提供的测试数据:

json
{
  "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg",
  "username": "linpingzhi",
  "name": "林平之",
  "gender": 1,
  "job": 1,
  "entryDate": "2022-09-18",
  "deptId": 1,
  "phone": "18809091234",
  "salary": 8000,
  "exprList": [
      {
         "company": "百度科技股份有限公司",
         "job": "java开发",
         "begin": "2012-07-01",
         "end": "2019-03-03"
      },
      {
         "company": "阿里巴巴科技股份有限公司",
         "job": "架构师",
         "begin": "2019-03-15",
         "end": "2023-03-01"
      }
   ]
}

在刚刚导入的接口里的 添加员工 ,发送 POST 请求:

21135b2d-b0c7-46fe-9c7b-f0a23842131e

请求完毕后,可以打开 idea 的控制台看到控制台输出的日志:

60c79cc1-1519-40ad-b05a-452beb97c66f

8.6 前后端联调


功能测试通过后,我们再进行通过打开浏览器,测试后端功能接口:

8f7d6efc-569c-4050-bc55-3e7d32c5f98b

点击保存之后,可以看到列表中已经展示出了这条数据。