管理系统-部门
管理系统-部门
今日目标
- 完成部门通过id查询(回显)
- 完成部门修改
- 完成员工分页查询
- 完成员工分页查询之分页插件
- 完成条件查询(动态sql)
知识储备
- springboot项目参数接受
- mybatis集成
- sql语句的书写
1. 部门模块
1.1 修改部门
前言
对于任何业务的修改功能来说,一般都会分为两步进行:查询回显、修改数据。

查询回显
需求分析
当我们点击 "编辑" 的时候,需要根据ID查询部门数据,然后用于页面回显展示。

明确了根据ID查询部门的需求之后,再来梳理一下实现该功能时,三层架构每一层的职责:

了解了需求之后,我们再看看接口文档中,关于根据ID查询部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

代码操作
路径参数接收
/depts/1
,/depts/2
这种在url中传递的参数,我们称之为路径参数。 那么如何接收这样的路径参数呢 ?
路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用 @PathVariable 获取路径参数。如下所示:

/**
* 根据ID查询部门数据
* @return
*/
@GetMapping("/{id}")
public Result getInfo(@PathVariable Integer id){
System.out.println("根据ID查询部门数据: " + id);
return Result.success();
}
1). Controller层
在 DeptController
中增加 getInfo
方法,具体代码如下:
/**
* 根据ID查询部门数据
*/
@GetMapping("/{id}")
public Result getInfo(@PathVariable Integer id){
System.out.println("根据ID查询部门数据: " + id);
Dept dept = deptService.getInfo(id);
return Result.success(dept);
}
2). Service层
在 DeptServiceImpl
中增加 getInfo
方法,具体代码如下:
@Override
public Dept getInfo(Integer id) {
return deptMapper.getById(id);
}
3). Mapper层
在 DeptMapper
中增加 getById
方法,具体代码如下:
/**
* 根据ID查询部门数据
*/
@Select("select id, name, create_time, update_time from dept where id = #{id}")
Dept getById(Integer id);
代码编写完毕之后,我们就可以启动服务,进行测试了。

思考
在url中是否可以携带多个路径参数呢 ,如:/depts/1/0
。是可以这么传递的,具体的接收方式如下:
@GetMapping("/depts/{id}/{sta}")
public Result getInfo(@PathVariable Integer id, @PathVariable Integer sta){
//...
}
总结
课堂作业
@RequestBody
和@RequestParam
以及@PathVariable
注解区别?🎤🎤
1.2 修改数据
前言
1. 需求分析
查询回显回来之后,就可以对部门的信息进行修改了,修改完毕之后,点击确定,此时,就需要根据ID修改部门的数据。

了解了需求之后,我们再看看接口文档中,关于修改部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

2. 思路分析
参照接口文档,梳理三层架构每一层的职责:

通过接口文档,我们可以看到前端传递的请求参数是json格式的请求参数,在Controller的方法中,我们可以通过 @RequestBody
注解来接收,并将其封装到一个对象中。
代码操作
1). Controller层
在 DeptController
中增加 update
方法,具体代码如下:
/**
* 修改部门数据
*/
@PutMapping
public Result update(@RequestBody Dept dept){
System.out.println("修改部门数据: " + dept);
deptService.update(dept);
return Result.success();
}
2). Service层
在 DeptServiceImpl
中增加 update
方法。 由于是修改操作,每一次修改数据,都需要更新updateTime。所以,具体代码如下:
@Override
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
deptMapper.update(dept);
}
3). Mapper层
在 DeptMapper
中增加 update
方法,具体代码如下:
/**
* 根据ID更新部门数据
*/
@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
代码编写完毕之后,我们就可以启动服务,进行测试了。

修改完成之后,我们可以看到最新的数据,如下:

1.3 功能优化
功能优化
@RequestMapping
到此呢,关于基本的部门的增删改查功能,我们已经实现了。 我们会发现,我们在 DeptController
中所定义的方法,所有的请求路径,都是 /depts
开头的,只要操作的是部门数据,请求路径都是 /depts
开头。
那么这个时候,我们其实是可以把这个公共的路径 /depts
抽取到类上的,那在各个方法上,就可以省略了这个 /depts
路径。 代码如下:

一个完整的请求路径,应该是类上的
@RequestMapping
的value属性 + 方法上的@RequestMapping
的value属性。
1. 问题分析
我们刚才在修改部门数据的时候,是根据ID修改所有的部门数据信息,具体的代码如下:

那我们在更新部门数据时,如何做到只更新其中的某一个或某几个字段呢? 比如:
我只想根据ID更新部门名称,不更新updateTime。 按照我们当前编写的SQL语句,就会将update_time字段置为null。

而正常的SQL应该是不更新 update_time,如下:

也就是说,传递了对应的数据值,我就更新对应的字段。如果没有传递,则不更新对应的字段。也就说,这个SQL语句,不是固定的,是随着条件动态变化的。 那要想实现这样的效果,就需要用到Mybatis中的动态SQL。
2. 代码实现
随着用户的输入或外部条件的变化而变化的SQL语句,我们称为 动态SQL。
我们可以将之前在 DeptMapper
中update
方法上定义的SQL语句,换成动态SQL。
具体操作步骤如下:
1). 在 src/main/resource
目录下创建文件夹 com/itheima/mapper
,并在其中定义配置文件,名为 DeptMapper.xml

2). 在DeptMapper.xml
中基于Mybatis中提供的动态SQL标签 <if>
<set>
将SQL语句改造为动态的。
<?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.DeptMapper">
<!--动态更新部门数据-->
<update id="update">
update dept
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
</mapper>
在上述我们使用了,两个动态SQL的标签,具体含义如下:
<if>
:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。<set>
:动态地在行首插入 SET 关键字,并会删掉额外的逗号。(用在update语句中)
2. 日志技术
日志技术
什么时日志?
日志就好比生活中的日记,可以随时随地记录你生活中的点点滴滴。
程序中的日志,是用来记录应用程序的运行信息、状态信息、错误信息的。
为什么要在程序中记录日志呢?
- 便于追踪应用程序中的数据信息、程序的执行过程。
- 便于对应用程序的性能进行优化。
- 便于应用程序出现问题之后,排查问题,解决问题。
- 便于监控系统的运行状态。
- ... ...
之前我们编写程序时,也可以通过
System.out.println(...)
来输出日志,为什么我们还要学习单独的日志技术呢?这是因为,如果通过
System.out.println(...)
来记录日志,会存在以下几点问题:- 硬编码。所有的记录日志的代码,都是硬编码,没有办法做到灵活控制,要想不输出这个日志了,只能删除掉记录日志的代码。
- 只能输出日志到控制台。
- 不便于程序的扩展、维护。
所以,在现在的项目开发中,我们一般都会使用专业的日志框架,来解决这些问题。
日志框架

JUL: 这是JavaSE平台提供的官方日志框架,也被称为JUL。配置相对简单,但不够灵活,性能较差。
Log4j: 一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。
Logback: 基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j。
Slf4j:(Simple Logging Facade for Java)简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。
日志级别
日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级由低到高):
日志级别 | 说明 | 记录方式 |
---|---|---|
trace | 追踪,记录程序运行轨迹 【使用很少】 | log.trace("...") |
debug | 调试,记录程序调试过程中的信息,实际应用中一般将其视为最低级别 【使用较多】 | log.debug("...") |
info | 记录程序运行的重要信息 ,如:数据库连接、网络连接、io操作 【使用较多】 | log.info("...") |
warn | 警告信息,可能会发生问题 【使用较多】 | log.warn("...") |
error | 错误信息 【使用较多】 | log.error("...") |
代码操作
Slf4j(酸辣粉使用教程)
几个常用的 lombok 注解:
@Data
:注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法@Setter
:注解在属性上;为属性提供 setting 方法@Getter
:注解在属性上;为属性提供 getting 方法@SneakyThrows
:无需在签名处显式抛出异常@Log4j
:注解在类上;为类提供一个 属性名为log 的 log4j 日志对像,用作日志输出的,一般会在项目每个类的开头加入该注解@Slf4j
: 同上@NoArgsConstructor
:注解在类上;为类提供一个无参的构造方法@AllArgsConstructor
:注解在类上;为类提供一个全参的构造方法
为什么使用@Slf4j?
为了能够少写两行代码,不用每次都在类的最前边写上: private static final Logger logger = LoggerFactory.getLogger(this.XXX.class);
使用步骤:
1. 在springboot项目下的pom.xml 导入下列依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
2. 在类名上加上注解 @Slf4j
@SpringBootTest
@Slf4j
public class Slf4jTest {
@Test
public void test(){
log.info("测试日志info");
log.warn("测试日志warn");
log.error("测试日志error");
log.debug("测试日志debug");
log.trace("测试日志trace");
}
}
运行结果:
2024-04-15 21:26:02.557 INFO 21708 --- [ main] cn.yangeit.Slf4jTest : 测试日志info
2024-04-15 21:26:02.557 WARN 21708 --- [ main] cn.yangeit.Slf4jTest : 测试日志warn
2024-04-15 21:26:02.557 ERROR 21708 --- [ main] cn.yangeit.Slf4jTest : 测试日志error
3. 占位符 {}
非常好用
//占位符:日志输出中{}很好用
@Test
public void test2(){
log.info("测试日志info:{}","yangeit");
log.warn("测试日志warn:{},{}",1,6);
}
运行结果:
3. 员工模块
3.1 员工列表查询(单表版本)
员工列表查询
那接下来,我们要来完成的是员工列表的查询功能实现。 具体的需求如下:

在查询员工列表数据时,既要考虑搜索栏中的查询条件,还要考虑对查询的结果进行分页处理。
在查询员工列表数据时,既需要查询 员工的基本信息,还需要查询员工所属的部门名称,所以这里呢,会涉及到多表查询的操作。本章节,只进行单表操作!!
那么接下来,我们在实现这个功能时,将会分为三个部分来逐一实现:
- 基本查询
- 分页查询
- 条件分页查询
代码操作
环境准备
1). 准备数据库表 emp(员工表)
emp_expr(员工工作经历表)
-- 先删除这个2个表
drop table if exists emp;
drop table if exists emp_expr;
-- 员工表
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,'5.png','2000-01-01',2,'2023-10-20 16:35:33','2023-11-16 16:11:26'),
(2,'songjiang','123456','宋江',1,'13309090002',2,8600,'01.png','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:37'),
(3,'lujunyi','123456','卢俊义',1,'13309090003',2,8900,'01.png','2008-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:39'),
(4,'wuyong','123456','吴用',1,'13309090004',2,9200,'01.png','2007-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:41'),
(5,'gongsunsheng','123456','公孙胜',1,'13309090005',2,9500,'01.png','2012-12-05',2,'2023-10-20 16:35:33','2023-10-20 16:35:43'),
(6,'huosanniang','123456','扈三娘',2,'13309090006',3,6500,'01.png','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:45'),
(7,'chaijin','123456','柴进',1,'13309090007',1,4700,'01.png','2005-08-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:47'),
(8,'likui','123456','李逵',1,'13309090008',1,4800,'01.png','2014-11-09',1,'2023-10-20 16:35:33','2023-10-20 16:35:49'),
(9,'wusong','123456','武松',1,'13309090009',1,4900,'01.png','2011-03-11',1,'2023-10-20 16:35:33','2023-10-20 16:35:51'),
(10,'linchong','123456','林冲',1,'13309090010',1,5000,'01.png','2013-09-05',1,'2023-10-20 16:35:33','2023-10-20 16:35:53'),
(11,'huyanzhuo','123456','呼延灼',1,'13309090011',2,9700,'01.png','2007-02-01',2,'2023-10-20 16:35:33','2023-10-20 16:35:55'),
(12,'xiaoliguang','123456','小李广',1,'13309090012',2,10000,'01.png','2008-08-18',2,'2023-10-20 16:35:33','2023-10-20 16:35:57'),
(13,'yangzhi','123456','杨志',1,'13309090013',1,5300,'01.png','2012-11-01',1,'2023-10-20 16:35:33','2023-10-20 16:35:59'),
(14,'shijin','123456','史进',1,'13309090014',2,10600,'01.png','2002-08-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:01'),
(15,'sunerniang','123456','孙二娘',2,'13309090015',2,10900,'01.png','2011-05-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:03'),
(16,'luzhishen','123456','鲁智深',1,'13309090016',2,9600,'01.png','2010-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:05'),
(17,'liying','12345678','李应',1,'13309090017',1,5800,'01.png','2015-03-21',1,'2023-10-20 16:35:33','2023-10-20 16:36:07'),
(18,'shiqian','123456','时迁',1,'13309090018',2,10200,'01.png','2015-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:09'),
(19,'gudasao','123456','顾大嫂',2,'13309090019',2,10500,'01.png','2008-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:11'),
(20,'ruanxiaoer','123456','阮小二',1,'13309090020',2,10800,'01.png','2018-01-01',2,'2023-10-20 16:35:33','2023-10-20 16:36:13'),
(21,'ruanxiaowu','123456','阮小五',1,'13309090021',5,5200,'01.png','2015-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:15'),
(22,'ruanxiaoqi','123456','阮小七',1,'13309090022',5,5500,'01.png','2016-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:17'),
(23,'ruanji','123456','阮籍',1,'13309090023',5,5800,'01.png','2012-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:19'),
(24,'tongwei','123456','童威',1,'13309090024',5,5000,'01.png','2006-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:21'),
(25,'tongmeng','123456','童猛',1,'13309090025',5,4800,'01.png','2002-01-01',3,'2023-10-20 16:35:33','2023-10-20 16:36:23'),
(26,'yanshun','123456','燕顺',1,'13309090026',5,5400,'01.png','2011-01-01',3,'2023-10-20 16:35:33','2023-11-08 22:12:46'),
(27,'lijun','123456','李俊',1,'13309090027',2,6600,'8.png','2004-01-01',2,'2023-10-20 16:35:33','2023-11-16 17:56:59'),
(28,'lizhong','123456','李忠',1,'13309090028',5,5000,'6.png','2007-01-01',3,'2023-10-20 16:35:33','2023-11-17 16:34:22'),
(30,'liyun','123456','李云',1,'13309090030',NULL,NULL,'01.png','2020-03-01',NULL,'2023-10-20 16:35:33','2023-10-20 16:36:31'),
(36,'linghuchong','123456','令狐冲',1,'18809091212',2,6800,'1.png','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). 准备与表结构对应的实体类 (资料中提供了, 直接引入到项目中)
/**
* 员工信息
*/
@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; //部门名称
}
/**
* 工作经历
*/
@Data
public class EmpExpr {
private Integer id; //ID
private Integer empId; //员工ID
private LocalDate begin; //开始时间
private LocalDate end; //结束时间
private String company; //公司名称
private String job; //职位
}

基本查询
那接下来,我们就先考虑一下要查询所有的员工数据, 不关联的部门名称,
那接下来,我们就定义一个员工管理的mapper接口 EmpMapper
并在其中完成员工信息的查询。 具体代码如下:
@Mapper
public interface EmpMapper {
/**
* 查询所有的员工
*/
@Select("select * from emp")
public List<Emp> list();
}
代码编写完毕后,我们可以编写一个单元测试,对上述的程序进行测试:
@SpringBootTest
@Slf4j
public class EmpTest {
@Autowired
private EmpMapper empMapper;
@Test
public void testListEmp(){
List<Emp> empList = empMapper.list();
empList.forEach(emp -> System.out.println(emp));
}
}
运行单元测试后,我们看到控制台输出的数据:

可以看到,员工的信息,查出来了,部门名字没有查出来(暂时不管部门字段 )
3.2 员工分页查询(单表版本)
前言
需求分析
上述我们在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(总记录数)
而这两部分我们通常封装到PageBean对象中,并将该对象转换为json格式的数据响应回给浏览器。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private Long total; //总记录数
private List rows; //当前页数据列表
}
代码操作
接口文旦
员工列表查询
基本信息
请求路径:/emps 请求方式:GET 接口描述:该接口用于员工列表数据的条件分页查询
请求参数
- 参数格式:queryString
- 参数说明:
参数名称 是否必须 示例 备注 name 否 张 姓名 gender 否 1 性别 , 1 男 , 2 女 begin 否 2010-01-01 范围匹配的开始时间(入职日期) end 否 2020-01-01 范围匹配的结束时间(入职日期) page 是 1 分页查询的页码,如果未指定,默认为1 pageSize 是 10 分页查询的每页记录数,如果未指定,默认为10 - 请求数据样例:
/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10
响应数据
参数格式:application/json
参数说明:
名称 类型 是否必须 备注 code number 必须 响应码, 1 成功 , 0 失败 msg string 非必须 提示信息 data object 必须 返回的数据 |- total number 必须 总记录数 |- rows object [] 必须 数据列表 |- id number 非必须 id |- username string 非必须 用户名 |- name string 非必须 姓名 |- gender number 非必须 性别 , 1 男 ; 2 女 |- image string 非必须 图像 |- job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师 |- salary number 非必须 薪资 |- entryDate string 非必须 入职日期 |- deptId number 非必须 部门id |- deptName 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。
思路分析
代码实现:
通过查看接口文档:员工列表查询
请求路径:/emps
请求方式:GET
请求参数:跟随在请求路径后的参数字符串。 例:/emps?page=1&pageSize=10
响应数据:json格式
1). 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);
PageBean pageBean = empService.page(page, pageSize);
return Result.success(pageBean);
}
}
@RequestParam(defaultValue="默认值") //设置请求参数默认值
2). EmpService
public interface EmpService {
/**
* 分页查询
* @param page 页码
* @param pageSize 每页记录数
*/
PageBean page(Integer page, Integer pageSize);
}
3). EmpServiceImpl
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageBean 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 PageBean(total, empList);
}
}
4). EmpMapper
@Mapper
public interface EmpMapper {
/**
* 查询总记录数
*/
@Select("select count(*) from emp ")
public Long count();
/**
* 查询所有的员工及其对应的部门名称
*/
@Select("select * from emp limit #{start}, #{pageSize}")
public List<Emp> list(Integer start , Integer pageSize);
}
功能测试
功能开发完成后,重新启动项目,使用Apifox,发起GET请求:

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

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

- Mapper接口层:
- 原始的分页查询功能中,我们需要在Mapper接口中定义两条SQL语句。
- PageHelper实现分页查询之后,只需要编写一条SQL语句,而且不需要考虑分页操作,就是一条正常的查询语句。
- Service层:
- 需要根据页码、每页展示记录数,手动的计算起始索引。
- 无需手动计算起始索引,直接告诉PageHelper需要查询那一页的数据,每页展示多少条记录即可。
代码操作
当使用了PageHelper分页插件进行分页,就无需再Mapper中进行手动分页了。 在Mapper中我们只需要进行正常的列表查询即可。在Service层中,调用Mapper的方法之前设置分页参数,在调用Mapper方法执行查询之后,解析分页结果,并将结果封装到PageBean对象中返回。
1、在pom.xml引入依赖
<!--分页插件PageHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
2、EmpMapper
/**
* 查询所有的员工及其对应的部门名称
*/
@Select("select *from emp")
public List<Emp> list();
3、EmpServiceImpl
@Override
public PageBean page(Integer page, Integer pageSize) {
//1. 设置分页参数
PageHelper.startPage(page,pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list();
//将list转成分页插件中的Page 因为Page对象中有很多属性(总条目数,当前数据集)
Page<Emp> p = (Page<Emp>) empList;
//3. 封装结果
return new PageBean(p.getTotal(), p.getResult());
}
测试
功能开发完成后,我们重启项目工程,打开Apifox,发起GET请求,访问 :http://localhost:8080/emps?page=1&pageSize=5

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

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

其实就是将我们编写的SQL语句进行的改造增强,将查询返回的字段列表替换成了 count(0)
来统计总记录数。
- 第二条SQL语句,用来进行分页查询,查询指定页码对应 的数据列表。

其实就是将我们编写的SQL语句进行的改造增强,在SQL语句之后拼接上了limit进行分页查询,而由于测试时查询的是第一页,起始索引是0,所以简写为limit ?。
而PageHelper在进行分页查询时,会执行上述两条SQL语句,并将查询到的总记录数,与数据列表封装到了 Page<Emp>
对象中,我们再获取查询结果时,只需要调用Page对象的方法就可以获取。
注意:
1. PageHelper实现分页查询时,SQL语句的结尾一定一定一定不要加分号(😉.。
2. PageHelper只会对紧跟在其后的第一条SQL语句进行分页处理。
3.4 员工分页条件查询(单表版本)
员工分页条件查询
上述代码,完成了分页查询,主要收获:
- 定义了PageBean,封装了前端的需要的结果(总条目数,当前页的数据集 )
- 熟悉了根据接口开发的,开发流程:接口文档---> controller-->service-->mapper-->sql-->apifox测试--->前端页面测试
- 知悉一个Service方法,可以调用多个Mapper方法,且分页查询需要执行2条语句(聚合函数,分页查询)
但是存在如下问题:
- 查询列表中,没有部门数据涉及到多表查询,后期在讲解
- 只能分页,不能条件查询

思路分析: 👇
我们看到页面原型及需求中描述,搜索栏的搜索条件有三个,分别是:
- 姓名:模糊匹配
- 性别:精确匹配
- 入职日期:范围匹配
select e.* from emp e
where
e.name like concat('%','张','%') -- 条件1:根据姓名模糊匹配
and e.gender = 1 -- 条件2:根据性别精确匹配
and e.entry_date = between '2000-01-01' and '2010-01-01' -- 条件3:根据入职日期范围匹配
order by update_time desc;
而且上述的三个条件,都是可以传递,也可以不传递的,也就是动态的。
1). 如果用户仅输入了姓名,则SQL为:
select e.* from emp as e where e.name like ?
2). 如果用户仅选择了性别,则SQL为:
select e.* from emp as e where e.gender = ?
3). 如果用户输入了姓名 和 性别 , 则SQL为:
select e.* from emp as e where e.name like ? and e.gender = ?
我们需要使用前面学习的Mybatis中的动态SQL 。
思路分析:

接口文档
- 通过查看接口文档:员工列表查询
- 请求路径:/emps
- 请求方式:GET
- 请求参数:
参数名称 | 是否必须 | 示例 | 备注 |
---|---|---|---|
name | 否 | 张 | 姓名 |
gender | 否 | 1 | 性别 , 1 男 , 2 女 |
begin | 否 | 2010-01-01 | 范围匹配的开始时间(入职日期) |
end | 否 | 2020-01-01 | 范围匹配的结束时间(入职日期) |
page | 是 | 1 | 分页查询的页码,如果未指定,默认为1 |
pageSize | 是 | 10 | 分页查询的每页记录数,如果未指定,默认为10 |
在原有分页查询的代码基础上进行改造。
代码操作
1. Controller
方式一:在Controller方法中通过多个方法形参,依次接收这几个参数保守者,可以用这个,方便理解
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "2") 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);
PageBean pageBean = null; //empService.page(page, pageSize);
return Result.success(pageBean);
}
}
场景:如果参数个数比较少,建议直接接收即可。 如果参数个数比较多,这种接收方式不便于维护管理。
方式二:在Controller方法中通过实体对象封装多个参数。(实体属性与请求参数名保持一致)
1). 定义实体类
@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). Controller方法中通过实体类,封装多个参数
/**
* 条件分页查询
*/
@GetMapping
public Result page(EmpQueryParam param) {
log.info("请求参数: {}", param);
PageBean pageBean = empService.page(param);
return Result.success(pageBean);
}
场景:请求参数比较多时,可以将多个参数封装到一个对象中。
2 Service
1). 在EmpService接口中增加如下方法:
/**
* 分页条件查询
*/
PageBean page(EmpQueryParam param);
2). 在EmpServiceImpl中实现page方法进行分页条件查询
@Override
public PageBean page(EmpQueryParam param) {
//1. 设置分页参数
PageHelper.startPage(param.getPage(), param.getPageSize());
//2. 执行查询
List<Emp> empList = empMapper.list(param);
//3. 解析封装分页结果
Page<Emp> p = (Page<Emp>) empList;
return new PageBean(p.getTotal(), p.getResult());
}
3. Mapper
1). 在EmpMapper中增加如下接口方法 (前面实现的分页查询的方法可以注释了)
public List<Emp> list(EmpQueryParam param);
2). 创建EmpMapper接口对应的映射配置文件 EmpMapper.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.EmpMapper">
<!-- 动态条件查询 -->
<select id="list" resultType="com.itheima.pojo.Emp">
select e.* deptName from emp e
<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 entry_date between #{begin} and #{end} </if>
</where>
order by e.update_time desc
</select>
</mapper>
<where>
标签的作用:
- 自动根据条件判断是否添加
where
关键字- 可以自动去除掉第一个条件前面多余的
and
或or
1. 功能测试
功能开发完成后,重启项目工程,打开Apifox,发起GET请求:
1). 输入 page、pageSize、name 条件测试

控制台SQL语句:

2). 输入 page、pageSize、name、gender 条件测试

控制台SQL语句:

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