🤖 Tlias 智能学习辅助系统_2
书接上回 ,在上篇已经完成了
部门管理模块的代码编写,并且学习了日志技术接着从
员工管理这个模块续下去 ... ...
七、员工列表查询
那接下来,我们要来完成的是员工列表的查询功能实现。 具体的需求如下:

在查询员工列表数据时,既需要查询 员工的基本信息,还需要查询员工所属的部门名称,
所以这里呢,会涉及到 多表查询 的操作。
而且,在查询员工列表数据时,既要考虑搜索栏中的查询条件,还要考虑对查询的结果进行分页处理。
那么接下来,我们在实现这个功能时,将会分为三个部分来逐一实现:
- 准备工作
- 分页查询
- 条件分页查询
7.1 准备工作
需求:查询所有员工信息,并查询出部门名称。(涉及到的表:emp、dept)

7.1.1 基础代码准备
- 1). 创建员工管理相关表结构
-- 员工表
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表对应的实体类
Emp、EmpExpr
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; //部门名称
}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员工管理的基础结构,包括Controller、Service、Mapper
EmpMapper:
package com.itheima.mapper;
import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface EmpMapper {
}EmpService:
package com.itheima.service;
public interface EmpService {
}EmpServiceImpl:
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:
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 语句
-- 查询所有的员工信息,如果员工关联了部门,也要查询出部门名称
select e.*, d.name as dept_name from emp e left join dept d on e.dept_id = d.id;- 2). Mapper 接口方法定义
那接下来,我们就定义一个员工管理的 Mapper 接口 EmpMapper 并在其中完成员工信息的查询。
具体代码如下:
@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 提示,且刚刚准备好了 Emp & EmpExpr 两张表的情况下
写完这个 list() 的代码,会发现 emp 爆红,记得刷新一下 IDEA 的数据库连接,
刚更新的两张表还没有被识别到 ...
📌 注意:
注意,上述SQL语句中,给 部门名称起了别名 deptName ,是因为在接口文档中,
要求部门名称给前端返回的数据中,就必须叫 deptName。 而这里我们需要将查询返回的每一条记录
都封装到 Emp 对象中,那么就必须保证查询返回的字段名与属性名是一一对应的。
此时,我们就需要在 Emp 中定义一个属性 deptName 用来封装部门名称。
刚刚建实体类的时候已经携带 , 具体如下高亮:
@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; //部门名称
}代码编写完毕后,我们可以编写一个单元测试,对上述的程序进行测试:
@SpringBootTest
class TliasWebManagementApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testList(){
List<Emp> empList = empMapper.list();
empList.forEach(System.out::println);
}
}运行单元测试后,我们看到控制台输出的数据:

可以看到,员工的信息,员工关联的部门名称都查询出来了。
7.2 分页查询
7.2.1 分析
上述我们在
Mapper接口中定义了接口方法,完成了查询所有员工及其部门名称的功能,是将数据库中所有的数据查询出来了。 试想如果数据库中的数据有很多(假设有几千几万条)的时候,
将数据全部展示出来肯定不现实,那如何解决这个问题呢?
使用分页解决这个问题。每次只展示一页的数据,
比如:一页展示 10 条数据,如果还想看其他的数据,可以通过点击页码进行查询。
而在员工管理的需求中,就要求我们进行分页查询,展示出对应的数据。
具体的页面原型如下:

要想从数据库中进行分页查询,我们要使用 LIMIT 关键字,
格式为:limit 开始索引 每页显示的条数
- 1). 查询第 1 页数据的SQL语句是:
select * from emp limit 0,10;- 2). 查询第 2 页数据的SQL语句是:
select * from emp limit 10,10;- 3). 查询第 3 页的数据的SQL语句是:
select * from emp limit 20,10;观察以上 SQL 语句,发现: 开始索引一直在改变 , 每页显示条数是固定的
❗ 开始索引的计算公式: 开始索引 = (当前页码 - 1) * 每页显示条数
我们继续基于页面原型,继续分析,得出以下结论:
- 前端在请求服务端时,传递的参数
当前页码
page每页显示条数
pageSize
后端需要响应什么数据给前端
所查询到的数据列表(存储到
List集合中)总记录数

后台给前端返回的数据包含:List 集合(数据列表)、total (总记录数)
而这两部分我们通常封装到 PageResult 对象中,并将该对象转换为 json 格式的数据响应回给浏览器。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
private Long total; //总记录数
private List<T> rows; //当前页数据列表
}7.2.2 接口描述
- 1). 基本信息
请求路径:
/emps请求方式:
GET接口描述:该接口用于员工列表数据的条件分页查询
- 2). 请求参数
| 参数名称 | 是否必须 | 示例 | 备注 |
|---|---|---|---|
name | 否 | 张 | 姓名 |
gender | 否 | 1 | 性别 , 1 男 , 2 女 |
begin | 否 | 2010/1/1 | 范围匹配的开始时间(入职日期) |
end | 否 | 2020/1/1 | 范围匹配的结束时间(入职日期) |
page | 是 | 1 | 分页查询的页码,如果未指定,默认为1 |
pageSize | 是 | 10 | 分页查询的每页记录数,如果未指定,默认为10 |
请求数据样例:
/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10- 3). 响应数据
参数格式:application/json
参数说明:
| 名称 | 类型 | 是否必须 | 备注 |
|---|---|---|---|
| code | number | 必须 | 响应码, 1 成功, 0 失败 |
| msg | string | 非必须 | 提示信息 |
| data | object | 必须 | 返回的数据 |
| total | number | 必须 | 总记录数 |
| rows | object[] | 必须 | 数据列表 |
| id | number | 非必须 | id |
| username | string | 非必须 | 用户名 |
| name | string | 非必须 | 姓名 |
| password | string | 非必须 | 密码 |
| gender | number | 非必须 | 性别, 1 男; 2 女 |
| image | string | 非必须 | 图像 |
| job | number | 非必须 | 职位, 说明: 1 班主任, 2 讲师, 3 学工主管, 4 教研主管, 5 咨询师 |
| salary | number | 非必须 | 薪资 |
| entryDate | string | 非必须 | 入职日期 |
| deptId | number | 非必须 | 部门id |
| deptName | string | 非必须 | 部门名称 |
| createTime | string | 非必须 | 创建时间 |
| updateTime | string | 非必须 | 更新时间 |
响应数据样例:
{
"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
@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
public interface EmpService {
/**
* 分页查询
* @param page 页码
* @param pageSize 每页记录数
*/
PageResult<Emp> page(Integer page, Integer pageSize);
}- 3). EmpServiceImpl
@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
@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 请求:

7.2.3.3 前后端联调
打开浏览器,测试后端功能接口:

点击下面的页码,可以正常的查询出对应的数据 。
7.2.4 PageHelper 分页插件
7.2.4.1 介绍
前面我们已经完了基础的分页查询,大家会发现:分页查询功能编写起来比较繁琐。
而分页查询的功能是非常常见的,我们查询员工信息需要分页查询,将来在做其他项目时,
查询用户信息、订单信息、商品信息等等都是需要进行分页查询的。
而分页查询的思路、步骤是比较固定的。
在
Mapper接口中定义两个方法执行两条不同的 SQL 语句:- 查询总记录数
- 指定页码的数据列表
在
Service当中,调用Mapper接口的两个方法,分别获取:总记录数、查询结果列表,然后在将获取的数据结果封装到
PageBean对象中。
大家思考下:在未来开发其他项目,只要涉及到分页查询功能 (例:订单、用户、支付、商品),
都必须按照以上操作完成功能开发,是不是有个工具会有利于我们的开发效率 ...
- 结论:原始方式的分页查询,存在着 "步骤固定"、"代码频繁" 的问题
- 解决方案:可以使用一些现成的分页插件完成。对于
Mybatis来讲现在最主流的就是PageHelper。
PageHelper 是第三方提供的 Mybatis 框架中的一款功能强大、
方便易用的分页插件,支持任何形式的单标、多表的分页查询。
官网:https://pagehelper.github.io/

那接下来,我们可以对比一下,使用 PageHelper 分页插件进行分页
与 原始方式进行分页代码实现的上的差别 :

Mapper接口层:原始的分页查询功能中,我们需要在
Mapper接口中定义两条SQL语句。PageHelper实现分页查询之后,只需要编写一条SQL语句,而且不需要考虑分页操作,就是一条正常的查询语句。
Service层:- 需要根据页码、每页展示记录数,手动的设置分页参数,调用
PageHelper的startPage()的方法。 - 无需手动计算起始索引,直接告诉
PageHelper需要查询那一页的数据,每页展示多少条记录即可。
- 需要根据页码、每页展示记录数,手动的设置分页参数,调用
7.2.4.2 代码实现
当使用了 PageHelper 分页插件进行分页,就无需再 Mapper 中进行手动分页了。
在 Mapper 中我们只需要进行正常的列表查询即可。
在 Service 层中,调用 Mapper 的方法之前设置分页参数,
在调用 Mapper 方法执行查询之后,解析分页结果,并将结果封装到 PageResult 对象中返回。
- 1). 在
pom.xml引入依赖
<!--分页插件PageHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>记得刷新
maven...
- 2).
EmpMapper:
/**
* 查询所有的员工及其对应的部门名称
*/
@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:
@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

我们可以看到数据可以正常查询返回,是可以正常实现分页查询的。
7.2.4.4 实现机制
我们打开 Idea 的控制台,可以看到在进行分页查询时,输出的 SQL 语句。

我们看到执行了两条 SQL 语句,而这两条 SQL 语句,其实是从我们在 Mapper 接口中定义的 SQL 演变而来的。
- 第一条 SQL 语句,用来查询总记录数。

- 第二条 SQL 语句,用来进行分页查询,查询指定页码对应 的数据列表。

其实就是将我们编写的 SQL 语句进行的改造增强,在 SQL 语句之后拼接上了 limit 进行分页查询,
而由于测试时查询的是第一页,起始索引是 0 ,所以简写为 limit ? 。
而 PageHelper 在进行分页查询时,会执行上述两条 SQL 语句,并将查询到的总记录数,
与数据列表封装到了 Page 对象中,我们再获取查询结果时,只需要调用 Page 对象的方法就可以获取。
❗ 注意:
- PageHelper 实现分页查询时,SQL 语句的结尾一定一定一定不要加分号 (否则不能拼接了) !!!
- PageHelper 只会对紧跟在其后的第一条 SQL 语句进行分页处理。
7.3 条件查询分页
做完了分页查询后,下面我们需要在分页查询的基础上,添加条件。
7.3.1 需求

通过员工管理的页面原型我们可以看到,员工列表页面的查询,不仅仅需要考虑分页,还需要考虑查询条件。
分页查询我们已经实现了,接下来,我们需要考虑在分页查询的基础上,再加上查询条件。
我们看到页面原型及需求中描述,搜索栏的搜索条件有三个,分别是:
- 姓名:
模糊匹配 - 性别:
精确匹配 - 入职日期:
范围匹配
7.3.2 接口描述
参照接口文档中的 员工管理 -> 员工列表查询
7.3.3 思路分析

7.3.4 功能开发
通过查看接口文档:员工列表查询
请求路径:/emps
请求方式:GET
请求参数:
| 参数名称 | 是否必须 | 示例 | 备注 |
|---|---|---|---|
| name | 否 | 张 | 姓名 |
| gender | 否 | 1 | 性别,1 男,2 女 |
| begin | 否 | 2010/1/1 | 范围匹配的开始时间(入职日期) |
| end | 否 | 2020/1/1 | 范围匹配的结束时间(入职日期) |
| page | 是 | 1 | 分页查询的页码,如果未指定,默认为1 |
| pageSize | 是 | 10 | 分页查询的每页记录数,如果未指定,默认为10 |
- 在原有分页查询的代码基础上进行改造:
- 1). 在 EmpController 方法中通过多个方法形参,依次接收这几个参数
@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:
javapublic 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 接口方法
@Mapper
public interface EmpMapper {
/**
* 查询所有的员工及其对应的部门名称
*/
public List<Emp> list(String name, Integer gender, LocalDate begin, LocalDate end);
}📌 由于 SQL 语句比较复杂,建议将 SQL 语句配置在 XML 映射文件中。
- 4). 新增 Mapper 映射文件
EmpMapper.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

测试时,需要注意传递的查询条件,有些查询条件查不到数据的,因为数据库没有符合条件的记录。
7.3.5 程序优化1
在上述分页条件查询中,请求参数比较多,有 6 个,如下所示:
- 请求参数:
/emps?page=1&pageSize=5&name=阮&gender=1&begin=2010-01-01&end=2020-01-01
那我们在 controller 层方法中,接收请求参数的时候,
直接在 controller 方法中声明这样6个参数即可,这样做,功能可以实现,但是不方便维护和管理。

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

优化思路:定义一个实体类,来封装这几个请求参数。
【需要保证,前端传递的请求参数和实体类的属性名是一样的】
- 1). 定义实体类:
EmpQueryParam
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接收请求参数
@GetMapping
public Result page(EmpQueryParam empQueryParam) {
log.info("查询请求参数: {}", empQueryParam);
PageResult pageResult = empService.page(empQueryParam);
return Result.success(pageResult);
}- 3). 修改
EmpService接口方法
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方法
@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接口方法
@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中的配置无需修改。
代码优化完毕之后,重新启动运行测试,依然正常运行:

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

那其实在 PageHelper 中,我们可以通过合理化参数配置,来解决这个问题。
直接在 application.yml 中,引入如下配置即可:
pagehelper:
reasonable: true
helper-dialect: mysql说明:
reasonable:分页合理化参数,默认值为false。当该参数设置为
true时,pageNum<=0时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。默认
false时,直接根据参数进行查询。
测试如下:

7.3.6 程序优化2
当前,我们在查询的时候,Mapper 映射配置文件中的 SQL 语句中,查询条件是写死的。
而我们在员工管理中,根据条件查询员工信息时,查询条件是可选的,可以输入也可以不输入 。

- 如果只输入 姓名 这个查询条件,则 SQL 语句中只根据
name字段查询,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 如下:
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 语句中要根据
name、gender两个字段查询,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 语句。
具体的代码实现如下:
<!--定义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 关键字,并会自动去除条件前面多余的 and 或 or 。
之后就可以不使用 where 关键字而使用 where 标签,
在开始标签和结束标签之间将所有的查询条件包裹起来
之后框架会自动根据里面的查询条件来判断是否需要生成 where 关键字,
如果里面的所有条件都不成立,就不会生成 where 关键字
以及如果最终生成的 SQL 语句前面多了一个 and 或者 or,
还可以帮助我们自动去除掉
代码优化完毕后,重新启动服务,测试如下:

- 没有输入任何查询条件时,运行日志如下:

- 当我们输入姓名和性别时

运行日志如下:
.BTZDFezM.png)
我们可以看到,当我们输入不同的搜索条件时,会动态的根据查询条件,动态拼接 SQL 语句。
八、新增员工
完成了员工管理的列表查询功能之后,接下来呢,我们再来完成新增员工的功能。 具体的需求如下:
.DQ0ZjoMY.png)
那么在新增员工的时候,涉及到以下的部分 :
- 新增员工
- 事务管理
- 文件上传
8.1 需求

在新增员工的时候,在表单中,我们既要录入员工的基本信息,又要录入员工的工作经历信息。
员工基本信息,对应的表结构是 emp 表,员工工作经历信息,对应的表结构是 emp_expr 表,
所以这里我们要操作两张表,往两张表中保存数据。
8.2 接口描述
参照提供的接口文档中 员工管理 -> 添加员工 接口的描述。
8.3 思路分析
新增员工的具体的流程:

- 接口文档规定:
- 请求路径:
/emps- 请求方式:
POST- 请求参数:Json 格式数据
- 响应数据:Json 格式数据
- 问题1:如何限定请求方式是 POST ?
@PostMapping- 问题2:怎么在
controller中接收json格式的请求参数?@RequestBody
8.3 功能开发
8.3.1 准备工作
准备 EmpExprMapper 接口及映射配置文件 EmpExprMapper.xml ,
并准备 实体类 接收前端传递的 json 格式的请求参数。
- 1).
EmpExprMapper接口
@Mapper
public interface EmpExprMapper {
}- 2).
EmpExprMapper.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来封装工作经历数据。 最终完整代码如下:
@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 方法。
/**
* 添加员工
*/
@PostMapping
public Result save(@RequestBody Emp emp){
log.info("请求参数emp: {}", emp);
empService.save(emp);
return Result.success();
}- 2).
EmpService & EmpServiceImpl
在 EmpService 中增加 save 方法
/**
* 添加员工
* @param emp
*/
void save(Emp emp);在 EmpServiceImpl 中增加 save 方法 , 实现接口中的 save 方法
@Override
public void save(Emp emp) {
//1.补全基础属性
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
//2.保存员工基本信息
empMapper.insert(emp);
//3. 保存员工的工作经历信息 - 批量 (稍后完成)
}3). EmpMapper
在 EmpMapper 中增加 insert 方法,新增员工的基本信息。
/**
* 新增员工数据
*/
@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 分析
一个员工,是可以有多段工作经历的,所以在页面上将来用户录入员工信息时,
可以自己根据需要添加多段工作经历。页面原型展示如下:

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

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

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

所以,这里最终我们需要执行的是批量插入数据的
insert语句。
8.3.3.2 实现
- 1).
EmpServiceImpl
完善 save 方法中保存员工信息的逻辑。完整逻辑如下:
@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
@Mapper
public interface EmpExprMapper {
/**
* 批量插入员工工作经历信息
*/
public void insertBatch(List<EmpExpr> exprList);
}- 3).
EmpExprMapper.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 这个文件来导入接口的信息
从而提高我们的接口测试的效率 ...
通过网盘分享的文件:接口文档
链接: https://pan.baidu.com/s/1l0F6CE-8q0cgR7YoK0D6MA?pwd=musv 提取码: musv
- 含有这个项目的全部 30 个接口

- 导入成功

目前可以看见,地址栏只有资源路径,没有协议、IP地址 和 端口号那些 ...
所以我们要先配置好环境:


- 这样就可以使用导入的接口来测试了
8.5 功能测试
代码开发完成后,重启服务器,打开 Apifox
增加接口文档提供的测试数据:
{
"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 请求:

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

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

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