282 lines
7.1 KiB
Markdown
282 lines
7.1 KiB
Markdown
|
|
# 学生数据不一致问题 - 完整解决方案
|
|||
|
|
|
|||
|
|
## 📊 **问题描述**
|
|||
|
|
|
|||
|
|
**现象:**
|
|||
|
|
- 课程分配时选择班级/监区的学生数量 ≠ 用户管理的学生数量
|
|||
|
|
- 例如:班级显示30个学生,但用户管理只有25个学生
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 **根本原因**
|
|||
|
|
|
|||
|
|
### **数据表关系:**
|
|||
|
|
```
|
|||
|
|
sys_user (用户表)
|
|||
|
|
↓ user_id
|
|||
|
|
study_student (学生表)
|
|||
|
|
↓ student_id
|
|||
|
|
study_student_class (学生-班级关联)
|
|||
|
|
study_course_assignment (课程分配)
|
|||
|
|
study_learning_record (学习记录)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **删除用户时的问题:**
|
|||
|
|
|
|||
|
|
**当前逻辑(`SysUserServiceImpl.deleteUserById`):**
|
|||
|
|
```
|
|||
|
|
✅ 删除:sys_user(用户)
|
|||
|
|
✅ 删除:sys_user_role(用户角色)
|
|||
|
|
✅ 删除:sys_user_post(用户岗位)
|
|||
|
|
|
|||
|
|
❌ 未删除:study_student(学生信息)
|
|||
|
|
❌ 未删除:study_student_class(学生班级关联)← 导致数量不一致
|
|||
|
|
❌ 未删除:study_course_assignment(课程分配)
|
|||
|
|
❌ 未删除:study_learning_record(学习记录)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结果:**
|
|||
|
|
- 删除用户后,学生相关数据变成"孤儿数据"
|
|||
|
|
- 课程分配查询`study_student_class`表,包含孤儿数据
|
|||
|
|
- 用户管理查询`sys_user`表,不包含已删除用户
|
|||
|
|
- **数量不一致!**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ **解决方案**
|
|||
|
|
|
|||
|
|
### **步骤1:清理现有孤儿数据(必须先执行)**
|
|||
|
|
|
|||
|
|
**使用SQL脚本:**
|
|||
|
|
```
|
|||
|
|
log/数据清理-删除孤儿数据.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**执行步骤:**
|
|||
|
|
```sql
|
|||
|
|
-- 1. 查询孤儿数据(先确认)
|
|||
|
|
SELECT COUNT(*)
|
|||
|
|
FROM study_student s
|
|||
|
|
LEFT JOIN sys_user u ON s.user_id = u.user_id
|
|||
|
|
WHERE u.user_id IS NULL;
|
|||
|
|
|
|||
|
|
-- 2. 备份数据库(重要!)
|
|||
|
|
|
|||
|
|
-- 3. 执行清理脚本(按顺序删除)
|
|||
|
|
-- 见:数据清理-删除孤儿数据.sql
|
|||
|
|
|
|||
|
|
-- 4. 验证结果
|
|||
|
|
SELECT
|
|||
|
|
'用户表学生' AS type, COUNT(*) AS count
|
|||
|
|
FROM sys_user WHERE user_type IN ('student', '02')
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT
|
|||
|
|
'学生表记录' AS type, COUNT(*) AS count
|
|||
|
|
FROM study_student;
|
|||
|
|
-- 两个数量应该一致
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **步骤2:修复代码(已完成)**
|
|||
|
|
|
|||
|
|
**文件:** `SysUserServiceImpl.java`
|
|||
|
|
|
|||
|
|
**修改:** 添加级联删除逻辑
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
@Transactional
|
|||
|
|
public int deleteUserById(Long userId)
|
|||
|
|
{
|
|||
|
|
// ✅ 新增:级联删除学生相关数据
|
|||
|
|
deleteStudentRelatedData(userId);
|
|||
|
|
|
|||
|
|
// 删除用户与角色关联
|
|||
|
|
userRoleMapper.deleteUserRoleByUserId(userId);
|
|||
|
|
// 删除用户与岗位表
|
|||
|
|
userPostMapper.deleteUserPostByUserId(userId);
|
|||
|
|
return userMapper.deleteUserById(userId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**说明:**
|
|||
|
|
- 目前只添加了框架和注释
|
|||
|
|
- 需要后续注入相关Mapper并实现删除逻辑
|
|||
|
|
- 建议使用存储过程或触发器完成级联删除
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **步骤3:数据库级联删除(推荐)**
|
|||
|
|
|
|||
|
|
**方案A:添加外键约束(最佳)**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 给study_student表添加外键约束
|
|||
|
|
ALTER TABLE study_student
|
|||
|
|
ADD CONSTRAINT fk_student_user
|
|||
|
|
FOREIGN KEY (user_id)
|
|||
|
|
REFERENCES sys_user(user_id)
|
|||
|
|
ON DELETE CASCADE;
|
|||
|
|
|
|||
|
|
-- 给其他关联表也添加
|
|||
|
|
ALTER TABLE study_student_class
|
|||
|
|
ADD CONSTRAINT fk_student_class_student
|
|||
|
|
FOREIGN KEY (student_id)
|
|||
|
|
REFERENCES study_student(student_id)
|
|||
|
|
ON DELETE CASCADE;
|
|||
|
|
|
|||
|
|
ALTER TABLE study_learning_record
|
|||
|
|
ADD CONSTRAINT fk_learning_student
|
|||
|
|
FOREIGN KEY (student_id)
|
|||
|
|
REFERENCES study_student(student_id)
|
|||
|
|
ON DELETE CASCADE;
|
|||
|
|
|
|||
|
|
-- ... 其他表类似
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**方案B:创建存储过程**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
DELIMITER //
|
|||
|
|
CREATE PROCEDURE delete_user_cascade(IN p_user_id BIGINT)
|
|||
|
|
BEGIN
|
|||
|
|
DECLARE v_student_id BIGINT;
|
|||
|
|
|
|||
|
|
-- 获取学生ID
|
|||
|
|
SELECT student_id INTO v_student_id
|
|||
|
|
FROM study_student
|
|||
|
|
WHERE user_id = p_user_id;
|
|||
|
|
|
|||
|
|
IF v_student_id IS NOT NULL THEN
|
|||
|
|
-- 按顺序删除关联数据
|
|||
|
|
DELETE FROM study_learning_detail WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_learning_record WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_course_assignment WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_student_class WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_exam_record WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_student_answer WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_student_change_log WHERE student_id = v_student_id;
|
|||
|
|
DELETE FROM study_student WHERE student_id = v_student_id;
|
|||
|
|
END IF;
|
|||
|
|
|
|||
|
|
-- 删除用户角色和岗位
|
|||
|
|
DELETE FROM sys_user_role WHERE user_id = p_user_id;
|
|||
|
|
DELETE FROM sys_user_post WHERE user_id = p_user_id;
|
|||
|
|
|
|||
|
|
-- 删除用户
|
|||
|
|
DELETE FROM sys_user WHERE user_id = p_user_id;
|
|||
|
|
END//
|
|||
|
|
DELIMITER ;
|
|||
|
|
|
|||
|
|
-- 使用:
|
|||
|
|
CALL delete_user_cascade(123);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 **完整执行计划**
|
|||
|
|
|
|||
|
|
### **立即执行(解决当前问题):**
|
|||
|
|
|
|||
|
|
1. ✅ **备份数据库**
|
|||
|
|
```bash
|
|||
|
|
mysqldump -u用户名 -p数据库名 > backup_$(date +%Y%m%d).sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. ✅ **清理孤儿数据**
|
|||
|
|
```bash
|
|||
|
|
mysql -u用户名 -p数据库名 < log/数据清理-删除孤儿数据.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. ✅ **验证数据一致性**
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
(SELECT COUNT(*) FROM sys_user WHERE user_type IN ('student', '02')) AS user_count,
|
|||
|
|
(SELECT COUNT(*) FROM study_student) AS student_count;
|
|||
|
|
-- 两个数量应该相等
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **后续优化(防止再次发生):**
|
|||
|
|
|
|||
|
|
1. ✅ **方案1(推荐):数据库添加外键约束 + ON DELETE CASCADE**
|
|||
|
|
- 优点:数据库级别保证数据一致性
|
|||
|
|
- 缺点:需要重建外键(测试环境先验证)
|
|||
|
|
|
|||
|
|
2. ✅ **方案2:使用存储过程**
|
|||
|
|
- 优点:集中管理删除逻辑
|
|||
|
|
- 缺点:需要修改Java代码调用存储过程
|
|||
|
|
|
|||
|
|
3. ✅ **方案3:完善Java代码级联删除**
|
|||
|
|
- 优点:灵活可控
|
|||
|
|
- 缺点:需要注入多个Mapper,代码量大
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ **注意事项**
|
|||
|
|
|
|||
|
|
1. **必须先备份数据库!**
|
|||
|
|
2. **先在测试环境验证**
|
|||
|
|
3. **执行清理脚本前确认要删除的数据**
|
|||
|
|
4. **清理后验证数据一致性**
|
|||
|
|
5. **重新编译并部署后端(包含修复)**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 **验证步骤**
|
|||
|
|
|
|||
|
|
### **清理前:**
|
|||
|
|
```sql
|
|||
|
|
-- 查询孤儿学生数
|
|||
|
|
SELECT COUNT(*) FROM study_student s
|
|||
|
|
LEFT JOIN sys_user u ON s.user_id = u.user_id
|
|||
|
|
WHERE u.user_id IS NULL;
|
|||
|
|
-- 可能显示:5个孤儿数据
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **清理后:**
|
|||
|
|
```sql
|
|||
|
|
-- 再次查询(应该为0)
|
|||
|
|
SELECT COUNT(*) FROM study_student s
|
|||
|
|
LEFT JOIN sys_user u ON s.user_id = u.user_id
|
|||
|
|
WHERE u.user_id IS NULL;
|
|||
|
|
-- 应该显示:0
|
|||
|
|
|
|||
|
|
-- 验证数量一致
|
|||
|
|
SELECT
|
|||
|
|
'用户表学生' AS type, COUNT(*) AS count
|
|||
|
|
FROM sys_user WHERE user_type IN ('student', '02')
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT
|
|||
|
|
'学生表' AS type, COUNT(*) AS count
|
|||
|
|
FROM study_student
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT
|
|||
|
|
'班级学生' AS type, COUNT(DISTINCT student_id) AS count
|
|||
|
|
FROM study_student_class;
|
|||
|
|
-- 三个数量应该基本一致
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ **总结**
|
|||
|
|
|
|||
|
|
**问题根源:**
|
|||
|
|
- 删除用户时没有级联删除学生数据
|
|||
|
|
- 导致"孤儿"学生记录存在
|
|||
|
|
|
|||
|
|
**解决方案:**
|
|||
|
|
1. 立即执行SQL清理脚本删除孤儿数据
|
|||
|
|
2. 添加数据库外键约束(推荐)或完善Java代码
|
|||
|
|
3. 测试验证数据一致性
|
|||
|
|
|
|||
|
|
**预防措施:**
|
|||
|
|
- 使用数据库外键约束 + ON DELETE CASCADE
|
|||
|
|
- 或者在删除用户前检查是否有关联数据并提示
|
|||
|
|
|
|||
|
|
**已提供文件:**
|
|||
|
|
- ✅ 数据清理SQL脚本:`log/数据清理-删除孤儿数据.sql`
|
|||
|
|
- ✅ 代码修复:`SysUserServiceImpl.java`(已添加级联删除框架)
|
|||
|
|
- ✅ 说明文档:`log/学生数据不一致问题-解决方案.md`
|