实战总结:班级管理、学员管理、学员信息统计
本文是对《11-后端Web实战(自己完成)》这个实战任务的总结,记录从需求分析、接口设计到代码实现的全过程。
项目地址:project/tlias-web-management(SpringBoot + MyBatis + PageHelper + Lombok)
一、任务概述
上一个实战任务已经完成了员工管理的增删改查和员工信息统计。本任务要求在此基础上,为培训机构系统补齐剩余模块:
- 班级管理:班级列表(条件分页)、添加班级、查询回显、修改班级、删除班级
- 学员管理:学员列表(条件分页)、添加学员、查询回显、修改学员、删除学员、违纪处理
- 学员信息统计:班级人数统计、学员学历统计
- 功能完善:删除部门时,若部门下存在员工则禁止删除
总共新增/修改了 16 个接口点,全部严格对照接口文档开发。
二、总体构思
1. 开发前的三个准备
| 步骤 | 内容 | 目的 |
|---|---|---|
| ① 读接口文档 | 讲义/接口文档.md |
明确每个接口的路径、请求方式、参数格式、响应结构,这是前后端联调的契约 |
| ② 分析已有项目结构 | 查看 controller / service / mapper / pojo 各层 | 复用已有的代码风格、工具类和组件,不引入讲义外的语法 |
| ③ 分析数据库表 | clazz、student 表结构 |
字段名、类型、唯一约束(后续动态 SQL 和统计函数都要用到) |
2. 严格遵守已有分层架构
1 | Controller (接收请求/响应 Result) |
复用的现成组件:Result(统一响应)、PageResult<T>(分页结果)、PageHelper(分页插件)、GlobalExceptionHandler(全局异常处理器)、@Slf4j 日志。
3. 按模块逐个开发,每个模块都是”老套路”
每个模块的开发顺序统一为:分页/列表查询 → 新增 → 根据ID查询 → 修改 → 删除,与之前员工管理的开发顺序完全一致,只是业务数据不同。
三、实体类与自定义异常
1. Clazz(班级)
1 |
|
2. Student(学员)
1 |
|
说明:
masterName、status、clazzName这三个字段在数据库表中不存在,是联查或计算出来的扩展字段,用于响应前端。
3. StudentCountData(班级人数统计结果)
仿照已有的 EmpJobData(职位统计用 jobList + dataList),班级人数统计也封装成一个对象:
1 |
|
4. BusinessException(自定义业务异常)
1 | package com.zhang.exception; |
四、班级管理模块(/clazzs)
1. 条件分页查询 GET /clazzs
接口要点:
- 参数:
name(模糊)、begin/end(匹配结课时间 end_date)、page、pageSize - 响应每行包含
masterName(班主任姓名)和status(班级状态)
实现思路:status 不是一个数据库字段,需要根据当前时间计算:
1 | 当前时间 > 结课时间 → 已结课 |
Mapper.xml(联查员工表拿班主任姓名 + 动态条件):
1 | <select id="list" resultType="com.zhang.pojo.Clazz"> |
Service(分页 + 在 Java 里算状态):
1 |
|
为什么不直接在 SQL 里用
CASE WHEN算状态?两种都可以,选 Java 计算是因为:状态计算逻辑简单清晰、可读性好,且分页结果已经拿到内存里,遍历赋值即可,无需改动 SQL。
2. 添加班级 POST /clazzs
Controller 用 @RequestBody Clazz 接收 JSON,Service 里补 createTime、updateTime 后插入:
1 | <insert id="insert" useGeneratedKeys="true" keyProperty="id"> |
3. 根据ID查询 GET /clazzs/{id}、修改 PUT /clazzs
- 查询:
select ... from clazz where id = #{id},用resultType直接映射(开启了map-underscore-to-camel-case,下划线字段自动转驼峰)。 - 修改:动态 SQL
<set>+<if>,只更新非空字段,update_time在 Service 里刷新。
4. 删除班级 DELETE /clazzs/{id} —— 删除校验
需求:班级下关联了学员则不允许删除,提示 “对不起, 该班级下有学生, 不能直接删除”。
实现思路(讲义提示:自定义异常 + 全局异常处理器):
1 |
|
StudentMapper 里新增统计方法:
1 | <select id="countByClazzId" resultType="java.lang.Integer"> |
5. 查询所有班级 GET /clazzs/list
给”新增学员”页面下拉框用,直接 select ... from clazz,无分页、无联查,返回 List<Clazz>。
五、学员管理模块(/students)
1. 条件分页查询 GET /students
- 参数:
name(模糊)、degree(学历)、clazzId(班级)、page、pageSize - 响应每行包含
clazzName(班级名称,联查班级表)
1 | <select id="list" resultType="com.zhang.pojo.Student"> |
2. 新增 / 根据ID查询 / 修改
- 新增:
violation_count、violation_score数据库有默认值 0,不需要插入。 - 修改:动态
<set>更新非空字段,clazzId、degree等数字字段用<if test="xxx != null">判断。 - 注意:
no、phone、id_card在表上有唯一约束,重复插入/修改会抛DuplicateKeyException,已被已有的全局异常处理器捕获并返回 “xxx已存在”。
3. 批量删除 DELETE /students/{ids} —— 路径参数接收数组
与员工删除(查询参数)的区别:学员删除的 id 是放在路径里的,格式 /students/1,2,3。
1 |
|
知识点:Spring 底层会把逗号分隔的路径参数
"1,2,3"通过转换服务自动拆分成Integer[],和查询参数ids=1,2,3接收成数组是同一个机制。
Mapper 里用 <foreach> 拼 id in (...):
1 | <delete id="deleteByIds"> |
4. 违纪处理 PUT /students/violation/{id}/{score}
需求:违纪处理一次,违纪次数 +1,违纪扣分 + 前端传入的分数。
直接在 SQL 里基于原值自增,一条 update 搞定:
1 | void violation( Integer id, Integer score); |
1 | <update id="violation"> |
注意:因为方法有多个参数(id、score),Mapper 接口上必须用
@Param指定参数名,否则 MyBatis 无法识别#{id}、#{score}。
六、学员信息统计(/report)
1. 班级人数统计 GET /report/studentCountData
需求:统计每个班级的人数,返回 { clazzList: [班级名...], dataList: [人数...] }(格式同员工职位统计)。
Mapper(联查班级表取班级名,按班级分组):
1 | <select id="countStudentClazz" resultType="java.util.Map"> |
Service(用 Stream 把 List<Map> 拆成两个 List 封装进 StudentCountData):
1 |
|
注意强转:
count(*)在 MyBatis 里返回的是Long,所以取出来要(Long)强转;名字取出来是 Object,用.toString()。
2. 学员学历统计 GET /report/studentDegreeData
需求:按学历分组统计人数,返回 [{name: '初中', value: 5}, ...](格式同员工性别统计)。
用 CASE WHEN 把 degree 数字映射成中文学历(这是之前员工职位统计用过的语法):
1 | <select id="countStudentDegree" resultType="java.util.Map"> |
七、功能完善:删除部门校验
需求:删除部门时,如果部门下有员工,则不允许删除,提示 “对不起,当前部门下有员工,不能直接删除!”
在已有的 DeptServiceImpl.deleteById 中加一层判断(需要注入 EmpMapper):
1 |
|
EmpMapper.xml 新增:
1 | <select id="countByDeptId" resultType="java.lang.Integer"> |
八、关键技术点总结
1. 自定义异常 + 全局异常处理器的完整链路
1 | Service 抛 BusinessException("对不起, 该班级下有学生, 不能直接删除") |
在已有的 GlobalExceptionHandler 中新增一个处理方法(Spring 会自动选择最匹配的异常处理器):
1 | //处理自定义业务异常 |
为什么比 try…catch 好?业务代码里不用到处写 try…catch,异常统一收敛到一处,代码干净优雅。
2. 分页:PageHelper 插件
1 | PageHelper.startPage(page, pageSize); // ① 开启分页(下一行查询自动拼接 limit) |
3. resultType vs resultMap
- 字段能直接对应(下划线自动转驼峰)→ 用
resultType(本任务大部分查询都够用)。 - 字段对应不上 / 需要手动嵌套封装(如员工带工作经历)→ 用
resultMap。 - 本任务班级列表、学员列表都是简单联查取别名,用 resultType + 列别名即可,不需要 resultMap。
4. 动态 SQL 的四个标签
| 标签 | 用途 | 本任务应用 |
|---|---|---|
<where> |
自动处理首个条件的 and | 班级/学员分页查询 |
<set> |
动态更新,自动去掉末尾逗号 | 修改班级/学员 |
<if> |
条件判断(字符串判空、数字判 null) | 所有动态 SQL |
<foreach> |
拼接 in 集合 | 批量删除学员 |
5. 统计函数的两种写法
| 函数 | 语法 | 用途 |
|---|---|---|
CASE WHEN |
case expr when val1 then res1 else res end |
学历数字 → 中文映射 |
IF() |
if(条件, 真值, 假值) |
员工性别 1/2 → 男/女 |
6. 两个细节坑
- 多参数 Mapper 方法必须加
@Param:如违纪处理violation(@Param("id") ..., @Param("score") ...),否则报Parameter 'id' not found。 count(*)返回类型是 Long:从 Map 中取值做算术或封装时要注意强转。
九、接口清单总览
| # | 模块 | 路径 | 方式 | 说明 |
|---|---|---|---|---|
| 1 | 班级管理 | /clazzs |
GET | 班级列表(条件分页,含 masterName/status) |
| 2 | 班级管理 | /clazzs |
POST | 添加班级 |
| 3 | 班级管理 | /clazzs/{id} |
GET | 根据ID查询班级 |
| 4 | 班级管理 | /clazzs |
PUT | 修改班级 |
| 5 | 班级管理 | /clazzs/{id} |
DELETE | 删除班级(班级下有学生禁止删) |
| 6 | 班级管理 | /clazzs/list |
GET | 查询所有班级(学员下拉框) |
| 7 | 员工管理 | /emps/list |
GET | 查询全部员工(班主任下拉框) |
| 8 | 学员管理 | /students |
GET | 学员列表(条件分页,含 clazzName) |
| 9 | 学员管理 | /students |
POST | 添加学员 |
| 10 | 学员管理 | /students/{id} |
GET | 根据ID查询学员 |
| 11 | 学员管理 | /students |
PUT | 修改学员 |
| 12 | 学员管理 | /students/{ids} |
DELETE | 批量删除学员(路径逗号数组) |
| 13 | 学员管理 | /students/violation/{id}/{score} |
PUT | 违纪处理 |
| 14 | 数据统计 | /report/studentCountData |
GET | 班级人数统计 |
| 15 | 数据统计 | /report/studentDegreeData |
GET | 学员学历统计 |
| 16 | 部门管理 | /depts |
DELETE | 删除部门(部门下有员工禁止删) |