选课表必须用(student_id, course_id)联合主键并设唯一约束,配外键级联删除;查学生及课程需三表LEFT JOIN+collection映射;增删操作应基于Selection实体,避免双向集合引用。

学生、课程、选课三张表怎么建才不翻车
外键约束和联合主键是核心。学生表 student 和课程表 course 各自用 id 当主键;选课表 selection 必须用 (student_id, course_id) 作联合主键,否则同一学生重复选同一门课就拦不住。
常见错误是把 selection.id 单独设成自增主键——这会让数据冗余且业务语义丢失。另外,student_id 和 course_id 都要加外键,并配 ON DELETE CASCADE(比如学生退学,他所有选课记录自动清除)。
字段设计上,selection 表建议加 selected_at TIMESTAMP 记录时间,方便后续查“最早选哪门课”或做选课时段统计。
MyBatis里怎么写一对多查学生+所选课程
不能只靠 resultMap 嵌套查询就完事。学生表和选课表是多对多关系,中间隔着 selection,所以一次查出学生及其课程,得用三表联查 + 手动组装,或者分两步:先查学生,再用 student_id 批量查 selection 和 course。
立即学习“Java免费学习笔记(深入)”;
推荐用联查,SQL 示例:
SELECT s.id, s.name, c.id AS cid, c.title
FROM student s
LEFT JOIN selection sel ON s.id = sel.student_id
LEFT JOIN course c ON sel.course_id = c.id
WHERE s.id = #{id}
注意点:
-
LEFT JOIN保证没选课的学生也能查出来(c.id和c.title为NULL) - MyBatis 的
resultMap要用collection映射课程列表,且ofType指向课程实体类 - 别忘了给
c.id起别名(如cid),否则和学生id冲突,MyBatis 会覆盖
插入选课记录时怎么防重复和超限
数据库层必须加唯一约束:UNIQUE (student_id, course_id),这是最后一道防线。应用层还要做两件事:检查课程是否已满、检查学生是否已选过。
典型流程:
- 查
selection表是否存在(student_id, course_id)记录 → 防重复 - 查该课程在
selection中的当前人数,对比课程表course.max_capacity字段 → 防超限 - 两个检查必须在同一个事务里完成,否则并发时可能两人同时通过检查,然后都插入成功
别依赖前端校验,也别只靠 Java 层 if 判断——高并发下必然出错。
Java实体类怎么映射多对多关系才不绕晕
学生类 Student 不该直接持有 List<Course>,而应持有 List<Selection>,再通过 Selection 关联到 Course。这样更贴近数据库结构,也避免循环引用和 JSON 序列化炸掉。
示例结构:
class Student { private Long id; private String name; private List<Selection> selections; }
class Selection { private Long studentId; private Long courseId; private LocalDateTime selectedAt; }
class Course { private Long id; private String title; private Integer maxCapacity; }
好处是:
- 增删选课只需操作
Selection实体,不用改Student或Course - 查某学生第几轮选的课、选课时间等扩展字段,天然有落脚点
- 避免
Student和Course双向持有对方集合,导致 Hibernate 的LazyInitializationException或 Jackson 的StackOverflowError
真正麻烦的是查询场景的灵活组合——比如“查所有未满员的课程,且排除该学生已选的”。这种逻辑写 SQL 更稳,别硬套 ORM 的链式调用。










