7.8 KiB
7.8 KiB
修复学生多班级问题总结
📋 问题描述
一个学生在student_class表中可能有多条status=1(活跃)的班级记录,导致:
- 人员列表显示班级不一致
- 课程分配界面同一学生出现在多个班级中
- 编号+班级筛选时显示错误班级
🔍 根本原因
1. UI分配班级逻辑问题
assignStudentToClass方法先查询学生所有班级,再逐条移除- 如果并发操作或异常中断,可能导致移除不完全
2. 导入数据逻辑问题
importStudentsWithProgress方法在更新用户班级时,逐条更新status- 没有事务保护,可能并发导致多条status=1记录
3. 查询逻辑限制
- 之前修改
selectClassIdsByStudentId只返回1条,导致其他旧班级无法被移除
✅ 修复方案
1. 添加批量禁用SQL(原子操作)
文件: StudyStudentClassMapper.xml
<!-- 批量禁用学生的所有活跃班级(确保一个学生只能有一个活跃班级) -->
<update id="deactivateAllActiveClassesByStudentId" parameterType="Long">
update student_class
set status = 0
where student_id = #{studentId} and status = 1
</update>
优势:
- ✅ 一条SQL语句原子性更新
- ✅ 不需要先查询再更新
- ✅ 避免并发问题
2. 添加Mapper接口方法
文件: StudyStudentClassMapper.java
/**
* 批量禁用学生的所有活跃班级(确保一个学生只能有一个活跃班级)
*
* @param studentId 学员ID
* @return 影响的行数
*/
public int deactivateAllActiveClassesByStudentId(Long studentId);
3. 修改UI分配班级逻辑
文件: StudyClassUserServiceImpl.java -> assignStudentToClass方法
修改前:
// 查询学员是否已有班级
List<Long> existingClassIds = studentClassMapper.selectClassIdsByStudentId(studentId);
// 遍历移除旧班级
for (Long existingClassId : existingClassIds) {
// 逐条更新status=0
}
修改后:
// ✅ 先禁用学员的所有活跃班级(使用批量SQL,确保原子性)
int deactivatedCount = studentClassMapper.deactivateAllActiveClassesByStudentId(studentId);
logger.info("已禁用学员 {} 的所有活跃班级,共 {} 条", studentId, deactivatedCount);
4. 修改导入逻辑
文件: StudyClassUserServiceImpl.java -> importStudentsWithProgress方法
修改前:
// 查询现有班级关联
List<StudyStudentClass> oldClassList = studentClassMapper.selectStudentClassList(query);
// 如果不在该班级,将其他班级设为已移除
if (!classUpdated) {
for (StudyStudentClass old : oldClassList) {
if (old.getStatus() == 1) {
old.setStatus(0);
studentClassMapper.updateStudentClass(old);
}
}
}
修改后:
// ✅ 先禁用学员的所有活跃班级(使用批量SQL,确保原子性)
studentClassMapper.deactivateAllActiveClassesByStudentId(u.getUserId());
// ✅ 检查是否已经存在该班级关联(可能是之前移除的)
StudyStudentClass query = new StudyStudentClass();
query.setStudentId(u.getUserId());
query.setClassId(classId);
List<StudyStudentClass> existingList = studentClassMapper.selectStudentClassList(query);
if (existingList != null && !existingList.isEmpty()) {
// 已存在该关联,更新状态为活跃
StudyStudentClass existing = existingList.get(0);
existing.setStatus(1);
existing.setJoinTime(new Date());
studentClassMapper.updateStudentClass(existing);
} else {
// 不存在该关联,插入新的班级关联
StudyStudentClass studentClass = new StudyStudentClass();
studentClass.setStudentId(u.getUserId());
studentClass.setClassId(classId);
studentClass.setStatus(1);
studentClass.setJoinTime(new Date());
studentClassMapper.insertStudentClass(studentClass);
}
5. 修改新增用户逻辑
文件: StudyClassUserServiceImpl.java -> 新增用户时的班级分配
添加:
// ✅ 先禁用所有活跃班级(确保一致性,虽然新用户不应该有)
studentClassMapper.deactivateAllActiveClassesByStudentId(user.getUserId());
// 插入学员-班级关联
StudyStudentClass studentClass = new StudyStudentClass();
studentClass.setStudentId(user.getUserId());
studentClass.setClassId(classId);
studentClass.setStatus(1);
studentClass.setJoinTime(new Date());
studentClassMapper.insertStudentClass(studentClass);
🚀 测试步骤
1. 重启后端服务
修改了以下文件,需要重启:
StudyStudentClassMapper.xmlStudyStudentClassMapper.javaStudyClassUserServiceImpl.java
2. 测试UI分配班级
- 进入人员管理
- 选择一个学生,点击"分配班级"
- 分配到新班级
- 查询数据库:
SELECT * FROM student_class WHERE student_id = xxx AND status = 1; - 预期结果:只有1条status=1的记录
3. 测试导入数据
- 创建测试Excel文件(包含已存在的学生,但班级不同)
- 导入数据
- 查询数据库:
SELECT student_id, COUNT(*) as count FROM student_class WHERE status = 1 GROUP BY student_id HAVING count > 1; - 预期结果:返回空结果(没有学生有多个活跃班级)
4. 测试并发导入
- 同时导入多个包含相同学生的Excel文件
- 查询数据库(同上)
- 预期结果:每个学生只有最后一次导入的班级为活跃状态
🔒 防护措施
已实施:
- ✅ 使用批量SQL更新(原子操作)
- ✅ 使用
@Transactional注解确保事务一致性 - ✅ 在所有班级分配场景中统一使用
deactivateAllActiveClassesByStudentId
建议(可选):
-
添加唯一索引(数据库层面防护)
ALTER TABLE student_class ADD UNIQUE INDEX idx_student_status_unique (student_id, status) WHERE status = 1;注意:MySQL 5.7不支持部分索引,可以使用触发器实现
-
添加数据一致性检查任务
-- 定期检查是否有学生有多个活跃班级 SELECT student_id, COUNT(*) as count FROM student_class WHERE status = 1 GROUP BY student_id HAVING count > 1;
📝 修改文件清单
-
Study-Vue-redis/ry-study-system/src/main/resources/mapper/study/StudyStudentClassMapper.xml- 添加
deactivateAllActiveClassesByStudentIdSQL
- 添加
-
Study-Vue-redis/ry-study-system/src/main/java/com/ddnai/system/mapper/study/StudyStudentClassMapper.java- 添加
deactivateAllActiveClassesByStudentId接口方法
- 添加
-
Study-Vue-redis/ry-study-system/src/main/java/com/ddnai/system/service/impl/study/StudyClassUserServiceImpl.java- 修改
assignStudentToClass方法(UI分配班级) - 修改
importStudentsWithProgress方法(导入数据时分配班级) - 修改新增用户时的班级分配逻辑
- 修改
✅ 预期效果
-
防止新增多班级问题
- UI分配班级时:自动禁用旧班级,只保留新班级
- 导入数据时:自动禁用旧班级,只保留导入的班级
-
数据一致性
- 每个学生在
student_class表中只有1条status=1的记录 - 其他旧班级记录保留但
status=0(历史记录)
- 每个学生在
-
业务逻辑正确
- 人员列表显示学生的唯一活跃班级
- 课程分配界面学生只出现在1个班级中
- 编号+班级筛选正常工作
🔧 后续维护
- 监控日志中的
deactivatedCount,确认是否有异常 - 定期执行数据一致性检查SQL
- 如果需要学生历史班级记录,保留
status=0的记录 - 如果需要学生转班功能,使用
assignStudentToClass方法
修复时间: 2025-12-11 修复人员: AI Assistant 测试状态: ⏳ 待测试