从0开始学习Java+AI知识点总结-23.web实战案例(班级和学生增删改查、信息统计)
一、项目概述
基于部门管理完成与部门管理类似
1.1 业务场景
在教育培训、学校管理等场景中,需要对班级信息(如开课时间、班主任、教室)和学生信息(如学历、违纪记录、所属班级)进行全生命周期管理,同时需要数据统计功能辅助决策。
1.2 核心功能模块
系统包含两大核心模块,功能清单严格遵循接口文档规范:
- 班级管理:条件分页查询、新增班级、根据 ID 查询班级、修改班级、删除班级、查询所有班级(用于学生关联)
- 学生管理:条件分页查询、新增学生、根据 ID 查询学生、修改学生、删除学生、违纪处理(累计违纪次数和扣分)
二、数据库设计
合理的数据库设计是系统稳定运行的基础,需满足第三范式(减少冗余)并建立必要关联。
2.1 班级表(clazz)
存储班级基本信息,包含唯一标识、时间范围、关联关系等核心字段。
字段名 | 类型 | 约束 | 说明 |
id | int unsigned | 主键、自增 | 班级唯一 ID |
name | varchar(30) | 非空、唯一 | 班级名称(如 “202班”) |
room | varchar(20) | 可选 | 教室位置(如 “212 教室”) |
begin_date | date | 非空 | 开课时间(用于计算班级状态) |
end_date | date | 非空 | 结课时间 |
master_id | int unsigned | 可选、关联员工表 | 班主任 ID(外键关联员工表) |
subject | tinyint unsigned | 非空 | 学科(1:Java,2: 前端,3: 大数据,4:Python,5:Go,6: 嵌入式) |
create_time | datetime | 可选 | 记录创建时间(审计字段) |
update_time | datetime | 可选 | 记录最后修改时间(审计字段) |
创建 SQL:
create table clazz( id int unsigned primary key auto_increment comment 'ID,主键', name varchar(30) not null unique comment '班级名称', room varchar(20) comment '班级教室', begin_date date not null comment '开课时间', end_date date not null comment '结课时间', master_id int unsigned null comment '班主任ID,关联员工表ID', subject tinyint unsigned not null comment '学科,1:java,2:前端,3:大数据,4:Python,5:Go,6:嵌入式', create_time datetime comment '创建时间', update_time datetime comment '修改时间' )comment '班级表'; -- 初始化测试数据 INSERT INTO clazz VALUES (1,'202班','212','2024-04-30','2024-06-29',10,1,'2024-06-01 17:08:23','2024-06-01 17:39:58'), (2,'203班','210','2024-07-10','2024-01-20',3,2,'2024-06-01 17:45:12','2024-06-01 17:45:12'), (3,'204班','108','2024-06-15','2024-12-25',6,1,'2024-06-01 17:45:40','2024-06-01 17:45:40'); |
2.2 学生表(student)
存储学生信息,通过clazz_id与班级表关联,包含个人属性和行为记录。
字段名 | 类型 | 约束 | 说明 |
id | int unsigned | 主键、自增 | 学生唯一 ID |
name | varchar(10) | 非空 | 学生姓名 |
no | char(10) | 非空、唯一 | 学号(如 “2022000001”) |
gender | tinyint unsigned | 非空 | 性别(1: 男,2: 女) |
phone | varchar(11) | 非空、唯一 | 手机号(用于联系) |
id_card | char(18) | 非空、唯一 | 身份证号(唯一标识) |
is_college | tinyint unsigned | 非空 | 是否来自院校(1: 是,0: 否) |
address | varchar(100) | 可选 | 联系地址 |
degree | tinyint unsigned | 可选 | 最高学历(1: 初中,2: 高中,3: 大专,4: 本科,5: 硕士,6: 博士) |
graduation_date | date | 可选 | 毕业时间 |
clazz_id | int unsigned | 非空、外键 | 所属班级 ID(关联 clazz 表 id) |
violation_count | tinyint unsigned | 非空、默认 0 | 违纪次数(初始为 0) |
violation_score | tinyint unsigned | 非空、默认 0 | 违纪扣分(初始为 0) |
create_time | datetime | 可选 | 创建时间 |
update_time | datetime | 可选 | 最后修改时间 |
创建 SQL:
create table student( id int unsigned primary key auto_increment comment 'ID,主键', name varchar(10) not null comment '姓名', no char(10) not null unique comment '学号', gender tinyint unsigned not null comment '性别,1:男,2:女', phone varchar(11) not null unique comment '手机号', id_card char(18) not null unique comment '身份证号', is_college tinyint unsigned not null comment '是否来自于院校,1:是,0:否', address varchar(100) comment '联系地址', degree tinyint unsigned comment '最高学历,1:初中,2:高中,3:大专,4:本科,5:硕士,6:博士', graduation_date date comment '毕业时间', clazz_id int unsigned not null comment '班级ID,关联班级表ID', violation_count tinyint unsigned default '0' not null comment '违纪次数', violation_score tinyint unsigned default '0' not null comment '违纪扣分', create_time datetime comment '创建时间', update_time datetime comment '修改时间', constraint fk_student_clazz foreign key (clazz_id) references clazz(id) ) comment '学员表'; -- 初始化测试数据 INSERT INTO student VALUES (1,'段誉','2022000001',1,'18800000001','110120000300200001',1,'北京市昌平区建材城西路1号',1,'2021-07-01',2,0,0,'2024-11-14 21:22:19','2024-11-15 16:20:59'), (2,'萧峰','2022000002',1,'18800210003','110120000300200002',1,'北京市昌平区建材城西路2号',2,'2022-07-01',1,0,0,'2024-11-14 21:22:19','2024-11-14 21:22:19'); |
三、实体类设计
实体类是 Java 代码与数据库表的映射,需包含表字段及业务扩展字段,推荐使用 Lombok 简化代码。
3.1 班级实体(Clazz)
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; @Data // 自动生成get/set/toString @NoArgsConstructor // 无参构造 @AllArgsConstructor // 全参构造 public class Clazz { private Integer id; // 班级ID private String name; // 班级名称 private String room; // 教室 private LocalDate beginDate; // 开课时间(LocalDate处理日期) private LocalDate endDate; // 结课时间 private Integer masterId; // 班主任ID private Integer subject; // 学科(1-6) private LocalDateTime createTime; // 创建时间 private LocalDateTime updateTime; // 修改时间 // 扩展字段(非数据库字段,用于前端展示) private String masterName; // 班主任姓名(关联查询) private String status; // 班级状态(未开班/在读/已结课) } |
3.2 学生实体(Student)
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor public class Student { private Integer id; // 学生ID private String name; // 姓名 private String no; // 学号 private Integer gender; // 性别(1男2女) private String phone; // 手机号 private String idCard; // 身份证号 private Integer isCollege; // 是否来自院校(1是0否) private String address; // 联系地址 private Integer degree; // 最高学历(1-6) private LocalDate graduationDate; // 毕业时间 private Integer clazzId; // 所属班级ID private Short violationCount; // 违纪次数 private Short violationScore; // 违纪扣分 private LocalDateTime createTime; // 创建时间 private LocalDateTime updateTime; // 修改时间 // 扩展字段 private String clazzName; // 班级名称(关联查询) } |
四、核心接口实现
4.1 班级管理模块
4.1.1 班级条件分页查询
接口文档规范:
- 请求路径:/clazzs
- 请求方式:GET
- 参数:name(班级名称模糊查询)、begin(结课时间开始)、end(结课时间结束)、page(页码)、pageSize(每页条数)
- 响应:分页数据(总条数 + 班级列表,含状态)
实现步骤:
- Controller 层:接收参数并调用 Service
@RestController @RequestMapping("/clazzs") public class ClazzController { @Autowired private ClazzService clazzService; // 条件分页查询班级 @GetMapping public Result<PageInfo<Clazz>> getClazzList( @RequestParam(required = false) String name, @RequestParam(required = false) String begin, @RequestParam(required = false) String end, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize) { PageInfo<Clazz> pageInfo = clazzService.queryClazzByPage(name, begin, end, page, pageSize); return Result.success(pageInfo); } } |
- Service 层:分页查询 + 状态计算
@Service public class ClazzServiceImpl implements ClazzService { @Autowired private ClazzMapper clazzMapper; @Override public PageInfo<Clazz> queryClazzByPage(String name, String begin, String end, Integer page, Integer pageSize) { // 开启分页(基于PageHelper插件) PageHelper.startPage(page, pageSize); // 查询数据 List<Clazz> clazzList = clazzMapper.selectByCondition(name, begin, end); // 计算班级状态(核心业务逻辑) LocalDate now = LocalDate.now(); for (Clazz clazz : clazzList) { if (now.isBefore(clazz.getBeginDate())) { clazz.setStatus("未开班"); } else if (now.isAfter(clazz.getEndDate())) { clazz.setStatus("已结课"); } else { clazz.setStatus("在读"); } } return new PageInfo<>(clazzList); } } |
- Mapper 层:动态 SQL 条件查询
<mapper namespace="com.example.mapper.ClazzMapper"> <!-- 条件查询班级 --> <select id="selectByCondition" resultType="Clazz"> select * from clazz <where> <if test="name != null and name != ''">and name like concat('%', #{name}, '%')</if> <if test="begin != null and begin != ''">and end_date >= #{begin}</if> <if test="end != null and end != ''">and end_date <= #{end}</if> </where> order by update_time desc </select> </mapper> |
关键技巧:
- 分页插件PageHelper需在 Spring Boot 中配置依赖,自动拦截分页参数
- 状态计算在 Service 层处理,避免数据库压力过大
- 时间字段建议用LocalDate/LocalDateTime(Java 8+),避免Date的线程安全问题
4.1.2 新增班级
接口文档规范:
- 请求路径:/clazzs
- 请求方式:POST
- 请求体:name(班级名称)、room(教室)、beginDate(开课时间)、endDate(结课时间)、masterId(班主任 ID)、subject(学科)
- 响应:成功 / 失败提示
实现步骤:
- Controller 层:接收班级对象
@PostMapping public Result addClazz(@RequestBody Clazz clazz) { clazzService.addClazz(clazz); return Result.success(); } |
- Service 层:校验唯一性 + 设置时间
@Override public void addClazz(Clazz clazz) { // 校验班级名称唯一性 Clazz existing = clazzMapper.selectByName(clazz.getName()); if (existing != null) { throw new BusinessException("班级名称已存在"); } // 设置创建/修改时间 LocalDateTime now = LocalDateTime.now(); clazz.setCreateTime(now); clazz.setUpdateTime(now); // 插入数据库 clazzMapper.insert(clazz); } |
关键校验:
- 非空校验:name、beginDate、endDate、subject为必填项
- 唯一性校验:班级名称不可重复(数据库唯一索引 + 代码校验双重保障)
- 时间逻辑:beginDate需早于endDate(可在 Service 层添加校验)
4.1.3 删除班级
接口文档规范:
- 请求路径:/clazzs/{id}
- 请求方式:DELETE
- 参数:路径参数id(班级 ID)
- 约束:若班级下有学生,不允许删除,返回提示 “该班级下有学生,不能直接删除”
实现步骤:
- Controller 层:接收班级 ID
@DeleteMapping("/{id}") public Result deleteClazz(@PathVariable Integer id) { clazzService.deleteClazz(id); return Result.success(); } |
- Service 层:关联校验 + 删除
@Autowired private StudentMapper studentMapper; @Override public void deleteClazz(Integer id) { // 校验班级是否有关联学生 int count = studentMapper.countByClazzId(id); if (count > 0) { throw new BusinessException("对不起,该班级下有学生,不能直接删除"); } // 执行删除 clazzMapper.deleteById(id); } |
优化建议:
- 实际项目中可采用 “逻辑删除”(添加is_deleted字段),保留历史数据
- 删除操作建议添加事务注解@Transactional,确保数据一致性
4.1.4 其他班级接口
- 根据 ID 查询班级:请求路径/clazzs/{id}(GET),直接查询单条数据返回
- 修改班级:请求路径/clazzs(PUT),接收完整班级对象,更新非空字段 + 修改时间
- 查询所有班级:请求路径/clazzs/list(GET),返回全量班级列表(用于学生新增时选择班级)
4.2 学生管理模块
4.2.1 学生条件分页查询
接口文档规范:
- 请求路径:/students
- 请求方式:GET
- 参数:name(学生姓名)、degree(学历)、clazzId(班级 ID)、page、pageSize
- 响应:分页数据(总条数 + 学生列表,含班级名称)
核心代码:
Service 层关联查询班级名称:
@Override public PageInfo<Student> queryStudentByPage(String name, Integer degree, Integer clazzId, Integer page, Integer pageSize) { PageHelper.startPage(page, pageSize); // 联表查询:学生表+班级表(获取班级名称) List<Student> studentList = studentMapper.selectByCondition(name, degree, clazzId); return new PageInfo<>(studentList); } |
Mapper 层 SQL:
<select id="selectByCondition" resultType="Student"> select s.*, c.name as clazz_name from student s left join clazz c on s.clazz_id = c.id <where> <if test="name != null and name != ''">and s.name like concat('%', #{name}, '%')</if> <if test="degree != null">and s.degree = #{degree}</if> <if test="clazzId != null">and s.clazz_id = #{clazzId}</if> </where> order by s.update_time desc </select> |
4.2.2 违纪处理接口
接口文档规范:
- 请求路径:/students/violation/{id}/{score}
- 请求方式:PUT
- 参数:路径参数id(学生 ID)、score(扣分数)
- 逻辑:违纪次数 + 1,违纪扣分 + score
实现步骤:
- Controller 层:接收参数
@PutMapping("/violation/{id}/{score}") public Result handleViolation(@PathVariable Integer id, @PathVariable Short score) { studentService.handleViolation(id, score); return Result.success(); } |
- Service 层:原子性更新
@Override @Transactional // 确保更新操作原子性 public void handleViolation(Integer id, Short score) { // 校验参数 if (score <= 0) { throw new BusinessException("违纪扣分必须为正数"); } // 查询学生 Student student = studentMapper.selectById(id); if (student == null) { throw new BusinessException("学生不存在"); } // 更新违纪信息 student.setViolationCount((short) (student.getViolationCount() + 1)); student.setViolationScore((short) (student.getViolationScore() + score)); student.setUpdateTime(LocalDateTime.now()); studentMapper.updateById(student); } |
关键注意:
- 扣分数需校验为正数,避免恶意数据
- 使用@Transactional确保 “次数 + 1” 和 “分数累加” 同时成功或失败
4.2.3 其他学生接口
- 新增学生:请求路径/students(POST),校验学号、手机号、身份证号唯一性
- 根据 ID 查询学生:请求路径/students/{id}(GET),返回单条学生详情
- 修改学生:请求路径/students(PUT),更新学生信息 + 修改时间
- 删除学生:请求路径/students/{ids}(DELETE),支持批量删除(参数为 ID 数组)
五、数据统计功能
5.1 班级人数统计
接口文档规范:
- 请求路径:/report/studentCountData
- 请求方式:GET
- 响应:班级名称列表 + 对应人数列表
实现 SQL:
select c.name as clazz_name, count(s.id) as student_count from clazz c left join student s on c.id = s.clazz_id group by c.id, c.name order by student_count desc; |
Service 层代码:
@Override public Map<String, Object> countStudentByClazz() { List<ClazzCountVO> countList = studentMapper.countByClazz(); // 提取班级名称和人数到两个列表 List<String> clazzList = countList.stream().map(ClazzCountVO::getClazzName).collect(Collectors.toList()); List<Integer> dataList = countList.stream().map(ClazzCountVO::getCount).collect(Collectors.toList()); // 封装响应 Map<String, Object> result = new HashMap<>(); result.put("clazzList", clazzList); result.put("dataList", dataList); return result; } |
5.2 学生学历统计
接口文档规范:
- 请求路径:/report/studentDegreeData
- 请求方式:GET
- 响应:学历名称(初中 / 高中 / 大专等)+ 对应人数
实现 SQL:
select case degree when 1 then '初中' when 2 then '高中' when 3 then '大专' when 4 then '本科' when 5 then '硕士' when 6 then '博士' else '其他' end as degree_name, count(id) as student_count from student group by degree order by degree; |
六、实战注意事项
6.1 接口开发规范
- 严格遵循接口文档的请求路径、参数名、响应格式,避免前后端联调纠纷
- 统一响应格式:{code: 1/0, msg: "提示信息", data: 数据}(1 成功,0 失败)
- 分页参数统一用page(页码)和pageSize(每页条数),默认值分别为 1 和 10
6.2 数据校验与异常处理
- 数据校验:使用@Valid+JSR303 注解(如@NotBlank、@Pattern)校验入参
- 全局异常处理:通过@RestControllerAdvice统一捕获异常,返回标准化错误响应
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.error(e.getMessage()); } } |
6.3 性能优化建议
- 分页查询添加索引:如clazz.end_date、student.clazz_id等频繁查询字段
- 关联查询避免SELECT *,只查询需要的字段
- 高频访问接口(如班级列表)可添加 Redis 缓存,减少数据库压力
七、总结与扩展
本文基于真实业务需求,完整实现了班级与学生管理系统的核心功能,涵盖了:
- 数据库设计(表结构 + 关联关系)
- 实体类定义(含扩展字段)
- 核心接口开发(CRUD + 分页 + 条件查询)
- 业务逻辑处理(状态计算、关联校验、违纪累计)
- 数据统计(班级人数、学历分布)
通过本项目实战,你可以掌握 基本的增删改查。如果觉得本文有帮助,欢迎收藏关注,后续将分享更多实战案例!