ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JavaWeb学生选课系统毕业设计:三层架构与数据库设计详解

JavaWeb学生选课系统毕业设计:三层架构与数据库设计详解

简介:一份基于Java的学生选课系统设计毕业论文文档,面向计算机相关专业需要完成毕业设计或课程设计的在校学生,尤其适合以教务管理系统为选题方向的读者。文档以河南工业大学毕业设计论文为原型,包含中英文摘要、关键词、目录及正文,结构完整。正文从项目背景与意义切入,围绕学生、教师、管理员三类用户角色展开需求分析,详细论述了课程管理、选课管理、成绩管理、通知公告、权限管理和统计报表等功能模块,并给出了基于JSP、jQuery、Java三层架构和MySQL数据库的技术实现方案。同时梳理了需求调研、概要设计、详细设计、编码实现、测试调试、部署上线的完整开发流程,能够帮助读者快速把握系统类毕业论文的结构与写法。压缩包内共1个doc文件,大小1.6MB。目前已有462人学习,适合作为毕业设计论文模板、系统设计参考或答辩准备素材。

1. 一个学生选课系统,为什么值得当 java 毕业设计来做

最近有同学找我说,毕业设计选了学生选课系统这个题,网上一搜模板确实多,但代码拿到手要么跑不起来,要么跑起来看不懂三层架构怎么分、数据库表为什么要这么建。这份资料刚好能解决这个痛点:它是一套完整的基于 JavaWeb 的学生选课系统,从需求分析、总体设计、数据库设计到每个功能模块的详细设计都有,论文正文和源码逻辑是对应得上的。教师端能维护学生和课程信息、统计学分并筛出学分不足的学生,学生端能看课、选课、退课、查已选课程。对准备 java 毕业设计、毕业论文的人来说,这不是一个「玩具 Demo」,而是一套可以直接复现、可以二次开发的完整骨架。

2. 需求与模块梳理:学生、教师、学分上限,三条主线定功能边界

2.1 角色与权限:登录窗口决定了你能做什么

这个系统最核心的设计思路,是「先定角色,再定功能」。正文的需求分析里写了三种角色:学生、教师、管理员,但从功能划分和详细设计来看,教师和管理员实际是合并实现的——教师登录后拥有最高权限,负责学生信息和课程信息的增删改查。也就是说,真正影响业务逻辑的角色只有两个:学生和教师。

权限边界的意义在于,它在登录那一层就把操作范围定死了。学生登录后只能做选课、退课、查已选课程这三类事,连修改自己密码的能力都没有;教师登录后可以动所有基础数据。这种设计在毕业论文里很好写清楚:一句话概括「不同身份进入不同主界面,所有后续操作都被这个身份约束」。对应到代码上,就是登录成功后 Session 里存的 role 值,后续每个页面和 Servlet 都拿它做权限判断。

这个设定也直接决定了数据库的形态:学生、教师、课程是三张独立的实体表,选课关系单独一张表。教师管的是「基础数据」,学生操作的是「关系数据」,数据流是单向的,边界非常干净。

2.2 功能清单:9 个核心流程怎么落地

正文第 2 章把功能需求写得很散,我按实际落地的角度重新归纳成 9 个能直接对应代码的流程:

  1. 登录:页面提供身份下拉框(学生/教师),输入编号和密码,校验成功后跳到对应主界面,失败则回到登录页并提示。对应模块是登录模块,页面是 login.jsp。
  2. 添加学生信息:教师录入学号、姓名、密码、班级、性别,写入 t_student 表,重复学号要拦截。
  3. 修改/删除学生信息:先按学号查出学生信息,回显到页面,改完更新或删除,操作成功给出提示。
  4. 添加课程信息:教师录入课程编号、课程名、授课教师、学分、教室,写入 t_course 表。
  5. 修改/删除课程信息:课程被学生选修过后,删除课程要处理选课关系表里的关联数据,否则会出现脏数据。
  6. 学生选课:学生查看可选课程列表,提交选课,系统判断是否重复选、是否超过学分上限。
  7. 退课:学生从已选课程列表里删除某条记录,删除后学分额度恢复。
  8. 查询:学生和教师都能按学号、姓名、班级、课程名做模糊查询,教师端查询结果带分页。
  9. 学分汇总与统计:系统按学生汇总已选学分,和上限对比,筛出学分不足或超限的学生。

这里面最容易被忽视的是第 5 条的关联数据问题。正文在「删除已选课程功能」章节只写了学生退课,但教师删除课程时没有明确说明要不要级联删除选课记录。实际开发里这属于必须处理的边界:要么删课程时先删 t_stu_course 里的关联记录,要么干脆禁止删除已有学生选修的课程。我建议后者,毕业论文里写「课程已有选课记录时不允许删除,只能修改状态为停开」,能少写很多坑。

2.3 数据流与模块边界:教师管数据,学生管选择

从数据流图的角度看,这个系统的数据走向是一条清晰的链:

教师录入学生信息、课程信息 → 写入基础表 → 学生登录后读取课程列表 → 提交选课 → 系统校验学分额度 → 写入选课关系表 → 教师按需统计学分。

正文里专门画了系统数据流图,文字描述是「数据从输入到输出的传输和转换过程」。对应到这份资料里,就是数据流从表现层进入,经过业务层校验,最后通过数据层落库。选课业务的校验点集中在业务层,这个分层在毕业论文的「系统实现的大致功能」章节里已经埋下伏笔——登录界面只做身份选择,具体逻辑全部下沉到 Service 层。

性能需求方面,正文提了响应时间、并发处理、安全性和数据完整性。这个体量的选课系统并发量不大,真正影响响应速度的是 SQL 写法和数据库连接释放。我见过很多课程设计翻车都翻在连接没关、SQL 全表扫描上,所以第 3 章的 DAO 实现里,PreparedStatement 和连接关闭是重点,这两个地方做到位,秒级响应基本没问题。

3. 架构与数据库:三层架构怎么分层,四张表怎么设计

3.1 项目目录与三层架构:表现层通过 Servlet 调 Service,Service 调 DAO

正文里写了「以 java 语言和三层架构为实现手段」,这是整个项目的骨架。我拆这套资料时,项目结构是这样的:

src ├── com.course.entity 实体类:Student、Teacher、Course、StuCourse ├── com.course.dao 数据访问层:接口 + JDBC 实现 ├── com.course.service 业务逻辑层:登录验证、选课学分校验、统计 ├── com.course.servlet 控制层:LoginServlet、CourseServlet、StudentServlet ├── com.course.util DBUtil 数据库连接工具、字符串校验工具 webapp ├── login.jsp 登录页 ├── student_index.jsp 学生主界面 ├── teacher_index.jsp 教师主界面 └── js / css jQuery、页面样式

三层架构最核心的约定是依赖方向:表现层只依赖业务层,业务层只依赖数据层,谁都不能越级调用。教师点「添加学生」按钮,请求到 StudentServlet,Servlet 调用 StudentService.addStudent(),Service 里做学号唯一性校验,再调 StudentDao.insert(),最后把结果返回给页面。这中间任何一层出了问题,都可以单独替换而不影响其他两层。

正文在「系统的易用性和易维护性」里强调代码要有可读性、可操作性和可移植性,三层架构就是这个诉求的直接答案。实际写论文时,画一张包结构图 + 一张请求时序图,这部分内容就能占满一节。

3.2 数据库设计:四张表撑起整个选课业务

正文第 4 章明确了四个实体:教师、学生、课程、选课关系,并给出了字段的取值范围表。我按正文的字段定义,把建表 SQL 补全成可直接执行的版本:

CREATE TABLE t_teacher ( tea_no VARCHAR(11) PRIMARY KEY, -- 教师编号,唯一标识教师 tea_name VARCHAR(20) NOT NULL, -- 教师姓名 tea_password VARCHAR(32) NOT NULL -- 教师登录密码 ); CREATE TABLE t_student ( stu_no VARCHAR(11) PRIMARY KEY, -- 学号,唯一标识学生 stu_name VARCHAR(20) NOT NULL, -- 学生姓名 stu_password VARCHAR(32) NOT NULL, -- 学生登录密码 stu_class VARCHAR(20), -- 班级 stu_sex CHAR(2) -- 性别 ); CREATE TABLE t_course ( course_no VARCHAR(11) PRIMARY KEY, -- 课程编号 course_name VARCHAR(50) NOT NULL, -- 课程名称 teacher VARCHAR(20), -- 授课教师 credit FLOAT(5,1), -- 课程学分,范围 0-5 classroom VARCHAR(11) -- 上课教室 ); CREATE TABLE t_stu_course ( id INT AUTO_INCREMENT PRIMARY KEY, stu_no VARCHAR(11) NOT NULL, -- 学号 course_no VARCHAR(11) NOT NULL, -- 课程编号 credit FLOAT(5,1), -- 冗余学分,方便汇总 UNIQUE KEY uk_stu_course (stu_no, course_no), -- 防止重复选课 CONSTRAINT fk_sc_stu FOREIGN KEY (stu_no) REFERENCES t_student(stu_no), CONSTRAINT fk_sc_cou FOREIGN KEY (course_no) REFERENCES t_course(course_no) );

正文第 4.1.3 节的表 4-2 字段说明里有一处很典型的坑:班级字段直接用 class 命名。class 是 Java 关键字,同时也是 SQL 的保留字,实体类里写private String class;直接编译报错,MyBatis 和 JDBC 操作时还要处理关键字转义。我的血泪经验是建表时一律改成 stu_class,从源头避开,别在代码里到处加反引号。

t_stu_course 表里的 credit 字段属于冗余设计。选课关系表本来只需要学号和课程号两个外键,但加一个冗余学分后,统计学生已选学分时就不用 JOIN 课程表,一句SELECT SUM(credit) FROM t_stu_course WHERE stu_no = ?就能出结果。这是典型的空间换时间,在毕业论文的数据表设计章节值得专门写一段。

3.3 学分规则在表结构里的体现

正文反复强调的学分上限规则,在表结构里其实没有专门字段,因为这是一个业务规则而不是实体属性。我看到的大部分同类项目,会在 t_student 表加一个 max_credit 字段,或者在系统参数表里存一行配置。我更推荐后者,因为「选修学分上限」可能随学期调整,写在学生表里改起来太麻烦。

常见做法是建一张 t_config 表:

CREATE TABLE t_config ( config_key VARCHAR(20) PRIMARY KEY, config_value VARCHAR(20) NOT NULL ); INSERT INTO t_config VALUES ('MAX_CREDIT', '20');

选课校验时从配置表取值,系统参数调整只改数据库,不动代码。这个设计在论文「学分控制模块」里能体现你的工程意识,答辩时也是加分项。

3.4 运行环境与启动步骤:在 IDEA 里把它跑起来

正文开头写了开发工具是 ideaIU-2018.3.1,技术组合是 JSP + jQuery + Serlvet(JavaWeb)+ MySQL。我按这个组合把运行步骤捋一遍,这是我把这份资料跑通时的标准流程:

  1. 准备环境:JDK 1.8、Tomcat 8.5、MySQL 5.7 或 8.0,开发工具用 IDEA。
  2. 导入项目:如果是普通 Web 工程(非 Maven),用 IDEA 的 Open 直接打开,然后进入 Project Structure → Artifacts,添加 Web Application Exploded,把项目根目录设为 Web Resource Directory。
  3. 建库:在 MySQL 里执行项目附带的 course.sql,生成四张表和初始数据,然后新建一个数据库连接,库名保持和 DBUtil 里的配置一致。
  4. 修改连接配置:打开 DBUtil.java,把用户名密码改成你自己的本地数据库账号。
  5. 部署运行:给项目配置 Tomcat 运行环境,Deployment 里添加 war exploded 包,Application context 设为 /course,启动后访问http://localhost:8080/course/login.jsp。

DBUtil 里最容易出错的是连接串。我一般写成这样:

private static final String URL = "jdbc:mysql://localhost:3306/course?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";

serverTimezone 是 MySQL 8.0 必须加的参数,不加会报时区错误;useSSL=false 是为了避免本地连接的 SSL 警告。如果遇到连接超时或驱动类找不到,先检查 lib 目录里有没有 mysql-connector-java.jar,这是新手最常漏的一步。

4. 核心模块拆解:登录、选课、学分校验的完整执行链路

4.1 登录模块:身份判断与 Session 处理

登录模块是正文第 5 章第一个详细设计的模块,逻辑一句话就能说清:用户选择身份,输入编号和密码,系统去对应的表里查记录,匹配成功进主界面,失败回登录页。但代码实现有细节:不同身份查不同的表,而且失败后要带上提示信息返回。

// LoginServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String role = req.getParameter("role"); // student / teacher String no = req.getParameter("no"); // 学号或教师编号 String password = req.getParameter("password"); // 密码 UserService userService = new UserService(); Object user = userService.login(role, no, password); if (user == null) { req.setAttribute("msg", "编号或密码错误,请重新输入"); req.getRequestDispatcher("login.jsp").forward(req, resp); return; } HttpSession session = req.getSession(); session.setAttribute("role", role); session.setAttribute("user", user); if ("student".equals(role)) { resp.sendRedirect("student_index.jsp"); } else { resp.sendRedirect("teacher_index.jsp"); } }

逻辑说明:role 参数决定登录时查 t_student 还是 t_teacher,查不到直接返回 null,页面重新登录,并在页面上展示 msg 提示;登录成功把 role 和 user 对象放进 Session,后续每个页面都靠 Session 里的 role 判断该展示什么内容。这里有个细节,失败用 forward 而成功用 sendRedirect,forward 是服务器内部跳转,能保留 request 里的提示信息;sendRedirect 是浏览器重定向,跳转后是个全新请求,不会带旧数据。很多课程项目失败也用重定向,结果页面上的错误提示永远显示不出来,这点值得在论文实现细节里写清楚。

4.2 选课模块:先汇总学分,再判断上限,最后写库

选课是整个系统的业务核心,正文里「当学分超出一个学生学分上限时,发出提示,并且不能继续选课」这句话是整个模块的验收标准。实现顺序必须是:先查课程、再算已选学分、然后做上限判断、最后写选课记录,顺序反了就会出现学分超限还能选上的问题。

// CourseService.java public String selectCourse(String stuNo, String courseNo) throws Exception { // 1. 根据课程编号查出课程信息,拿学分 Course course = courseDao.findByNo(courseNo); if (course == null) { return "课程不存在"; } // 2. 统计该学生已选学分总和 float selectedCredit = courseDao.sumCreditByStu(stuNo); // 3. 读取学分配额上限,默认 20 学分 float maxCredit = configDao.getFloatValue("MAX_CREDIT", 20); // 4. 上限判断,超限直接返回错误 if (selectedCredit + course.getCredit() > maxCredit) { return "选课失败:学分上限为 " + maxCredit + ",当前已选 " + selectedCredit + ",该课程 " + course.getCredit() + " 学分"; } // 5. 重复选课校验 if (courseDao.isSelected(stuNo, courseNo)) { return "该课程已选,不能重复选课"; } // 6. 写入选课关系表 courseDao.insertStuCourse(stuNo, courseNo, course.getCredit()); return "success"; }

参数说明:这个方法接收学号和课程编号两个参数。第 2 步sumCreditByStu对应 SQL 是SELECT IFNULL(SUM(credit), 0) FROM t_stu_course WHERE stu_no = ?,用 IFNULL 处理从没选过课的学生的空值情况,否则返回 null 直接导致第 4 步空指针。第 4 步超限判断返回的提示文案要具体,把上限值、当前已选值、新课程学分都拼进去,这在演示环节能给答辩老师直观反馈。

选课提交按钮的防重复点击也是个隐蔽问题。学生连点两次提交,两个请求几乎同时进来,Service 层的 isSelected 判断可能都返回 false,于是插入两条记录。解决方式不能用代码逻辑兜底,必须在数据库层面加唯一索引——t_stu_course 表里的uk_stu_course (stu_no, course_no)就是干这个的。插入时捕获 DuplicateKeyException,转换成「该课程已选」的提示返回给页面。

退课逻辑是选课的逆向操作,执行DELETE FROM t_stu_course WHERE stu_no = ? AND course_no = ?,删完之后该学生的已选学分同步减少,下一次选课的额度空间自动恢复。很多人在退课后发现学分没变,多半是 COUNT 和 SUM 混用——统计已选课程数用 COUNT(*),统计学分必须用 SUM(credit),这两个东西不是一回事。

4.3 学分不足统计与模糊查询:教师端的高频操作

正文里教师模块的需求是「统计学分,筛选出选修学分不够的同学」。这里核心是一个带 HAVING 的聚合查询:

SELECT stu_no, SUM(credit) AS total_credit FROM t_stu_course GROUP BY stu_no HAVING total_credit < 12;

WHERE 是分组前过滤行,HAVING 是分组后过滤组,这个区分是数据库设计章节里值得写的知识点。如果还要把学号对应到学生姓名和班级,就 JOIN 一下 t_student 表,展示效果更好。

模糊查询是教师端查询学生信息的常用操作。正文写的是「可以根据某个数据项查询,也可以通过查询包含特定字符的相关记录」,落到 SQL 就是 LIKE 拼接:

// StudentDao.java public List<Student> findByKeyword(String keyword, int page, int pageSize) { String sql = "SELECT * FROM t_student WHERE stu_no LIKE ? OR stu_name LIKE ? OR stu_class LIKE ?" + " LIMIT ?, ?"; // 参数绑定:%keyword% 三个位置取同样的模糊值 // limit 偏移量 = (page - 1) * pageSize }

LIMIT 的两个参数是偏移量和条数,第一页是 LIMIT 0, 10,第二页是 LIMIT 10, 10。教师端的分页还要单独查一次SELECT COUNT(*)得到总记录数,才能算总页数。这一步的踩坑点在于 count 查询和列表查询的 WHERE 条件必须完全一致,否则列表能查出来但总页数算错,翻到最后一页直接空白。

5. 避坑与排查:JSP 项目从能跑到能演示的 5 个真实翻车点

5.1 登录成功后刷新页面又跳回登录页

现象:登录成功后进入学生主界面,一按 F5 刷新就回到登录页,还要重新登录。

原因:这类项目的页面基本没有做 Session 校验。刷新触发的是页面重新加载,如果 JSP 里没有判断 Session 里是否存在 user 对象,或者判断逻辑写错了位置,服务器会把未登录状态当成「非法访问」跳回登录页。

解决:在所有业务 JSP 页面顶部加一段统一的 Session 校验,或者在 web.xml 里配置一个登录过滤器。更规范的做法是写一个 LoginFilter 实现javax.servlet.Filter,拦截所有访问,白名单放行 login.jsp 和登录 Servlet,其余请求检查 Session 中的 user 属性,为空就重定向回登录页。这才是 JSP 项目防止未授权访问的标准姿势。

5.2 中文乱码:入库是问号,页面上是乱码

现象:学生姓名、班级等中文数据写入数据库后变成??,页面上显示乱码,而且不同页面有的正常有的乱。

原因:编码不统一。JSP 页面、请求参数、数据库连接、表结构四处编码只要有一处不一致就乱码。最常见的组合是 JSP 页面用了 GBK,数据库连接串没带 characterEncoding 参数,表又是默认的 latin1 字符集,三边全是错的。

解决:统一 UTF-8。四件事全部做一遍:JSP 页面顶部写<%@ page contentType="text/html;charset=UTF-8" %>;在 Filter 或 Servlet 入口执行request.setCharacterEncoding("UTF-8");JDBC 连接串加characterEncoding=UTF-8;建表时指定DEFAULT CHARSET=utf8mb4。我调试这类项目时,遇到乱码第一时间不是改代码,而是先SHOW CREATE TABLE看字符集,再查连接串,这两个对上了代码基本不用动。

5.3 选课可以重复选,Service 层判断失灵

现象:学生对着同一门课连点两次选课,系统居然提示成功,数据库里出现两条相同的选课记录。

原因:Service 层的 isSelected 查询和 insert 是分开执行的,第一次点击的请求还没执行完,第二次请求又进来,两次都查不到已选记录,于是一起通过了判断。后端只做了应用层判断,数据库没有任何约束兜底。

解决:在 t_stu_course 表上建立联合唯一索引UNIQUE KEY uk_stu_course (stu_no, course_no),让数据库成为最后一道防线。当重复插入触发 DuplicateKeyException 时,在 DAO 层捕获这个异常并转换成业务提示「该课程已选」。从那以后我只要看到「先查再插」这种流程,都会自动加上唯一索引,这是数据库设计的习惯问题。

5.4 分页到最后一页空白,模糊查询结果数与列表对不上

现象:总记录数计算正确,但翻到最后一页时列表为空;或者是输入关键词查询后,列表有数据但总页数还是按全表算的。

原因:count 查询和列表查询的 WHERE 条件不一致。有人在 count SQL 里漏了 keyword 过滤条件,导致总数永远按全表统计;有人 LIMIT 的偏移量写死成 0,翻页永远只出第一页;还有人没处理「最后一页可能是空页」的情况,手动把页码改成 10 但第 10 页实际没数据。

解决:把 keyword 同时传给 count 和 list 两个查询方法,保持一致;LIMIT 偏移量用(page - 1) * pageSize动态计算;每次查询前如果当前页大于总页数,自动把 page 重置为总页数。这三件事做完,分页查询才算闭环。

5.5 字段名用了 class,SQL 直接报语法错误

现象:执行SELECT * FROM t_student WHERE class = ?时直接报 SQL 语法错误,但其他表的查询都正常。

原因:正文表 4-2 里班级字段就叫 class,class 是 MySQL 的保留字(也是 Java 关键字)。JDBC 执行时没有做关键字处理,数据库解析 SQL 时直接被 class 卡住,提示 around 'class' 附近的语法错误。

解决:最省事的方案是建表时把字段改名,正文里我用的是 stu_class,语义不变,所有代码跟着调整。如果是老表已经用了这个名,SQL 里写WHERE \class` = ?` 用反引号转义,Java 实体类里也得想办法绕开关键字——这属于给自己添堵。但凡建表之前多看一眼保留字列表,这个坑完全能避开。

6. 二次开发:把课程设计改造成能写进简历的选课系统

改造点现状建议方案
密码存储明文存数据库MD5 加盐或直接上 BCrypt,改造量小,效果明显
页面逻辑JSP 里散落 Java 代码引入 JSTL + EL,把 scriptlet 全部移到 Servlet
SQL 拼接部分位置用字符串拼接全部换 PreparedStatement 参数绑定,防注入
异常处理各 Servlet 各自 try-catch抽一个 BaseServlet 统一处理异常和响应
重复选课Service 层查询判断已经加了唯一索引,再捕获异常转成业务提示

我建议按这个顺序改造:先引入 Maven 管理依赖,让 Jar 包版本可控,这是所有后续改造的基础;然后把密码加密换掉,这是安全性提升最快的一步;接着把 JSP 里肉眼可见的 Java 代码清理掉,页面清爽了代码可读性立刻上一个档次;最后把分页和模糊查询的参数封装成查询对象,让 DAO 接口只接收一个条件对象,而不是四个散落的参数。

改造完要验证,按照业务链路走一遍回归清单:教师添加学生、添加课程 → 学生选课成功 → 学分超限选课被拒 → 重复选课被拒 → 退课后学分恢复 → 教师统计学分筛出不足学生。这套流程全绿,系统就可以拿出去演示。

我从这类 JSP 老项目里学到一条经验:改造之前先把数据库备份和项目源码备份各拷一份,改动过程中每通过一步测试就提交一次版本。遇到过最惨的翻车是改编码时把整个项目的换行符弄乱,IDE 全量格式化和原文件混在一起,最后靠备份才救回来。从那以后每个涉及全项目级别的改动我都强制走一遍「先备份、再小步改、每一步跑通再继续」的流程。这份选课系统入手后建议你也照这个节奏来,先跑通原始版本,再动手改代码。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进