☰
JavaWeb学生选课系统实战:从环境搭建到答辩避坑全指南
2026/10/4 7:59:56 网站建设 项目流程

简介:这份JavaWeb实训资料包面向计算机相关专业学生与JavaWeb初学者,围绕学生选课系统这一典型课程设计场景,提供从需求分析到代码落地的完整参考。系统按角色划分功能:学生可注册登录、浏览课程、选课退课并查询已选结果;教师可发布与删除课程、查看选课情况,涵盖注册登录、学生选课、选课结果浏览、教师浏览结果、教师添加课程五大模块。包内共111个文件,约20.84MB,包含java源码、jsp页面、class编译文件、css与scss样式、sql建库脚本,以及实训报告docx和答辩pptx,另有jar依赖与字体资源,结构完整便于对照复现。目前已有4575人学习下载,适合需要完成课程实训、撰写报告或准备答辩的读者参考,可据此理解Servlet与DAO分层、数据库连接及前后端交互流程,快速搭建可运行的选课系统。

1. 从零跑通 JavaWeb 学生选课系统:一套能写进实训报告、也能扛住答辩的完整方案

如果你正在找一套能直接跑起来、代码结构清晰、数据库脚本齐全的 JavaWeb 项目,学生选课系统大概率是最合适的选择。它业务不复杂,但把增删改查、多表关联、事务控制、权限区分这些 JavaWeb 核心知识点全串了一遍。更关键的是,实训报告、答辩 PPT、源代码、SQL 文件这四件套凑齐之后,你既能在本地用 IDEA 跑通,也能在答辩现场把技术点讲明白。我带过几届学生的课程设计,发现真正卡住大家的从来不是业务逻辑,而是环境配置、数据库连接、依赖版本这些“玄学”问题。这篇笔记就按“先跑通、再讲透、最后避坑”的顺序,把一套完整的学生选课系统从导入到答辩的路径拆开讲,适合正在做 JavaWeb 实训、需要交课程设计、或者想拿一个完整案例练手 MySQL 的开发者。

2. 学生选课系统的技术选型与数据库设计:为什么这套组合最稳

2.1 技术栈怎么选才不容易翻车

做 JavaWeb 实训项目,技术栈的选择直接决定你后面是顺风顺水还是天天查报错。我一般会推荐这套组合:Servlet + JSP + JDBC + MySQL,前端用 Bootstrap 加一点 jQuery。原因很实际——很多学校的实训指导书就是按这个体系写的,答辩老师问的也是这些。你如果用 Spring Boot 加 MyBatis,代码是简洁了,但老师一问“Servlet 生命周期”“JDBC 事务怎么控制”,你反而答不上来,得不偿失。

Servlet 负责接收请求和控制跳转,JSP 负责页面渲染,JDBC 直接操作数据库。这套看起来“老”的技术,恰恰能让你把请求响应流程、会话管理、数据库连接池这些底层机制摸清楚。MySQL 选 5.7 或 8.0 都行,但要注意驱动包版本要匹配:MySQL 8.0 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver,这个细节后面避坑章节还会展开。

项目结构上,建议按 MVC 分层:dao层写数据库操作,service层写业务逻辑,servlet层做请求分发,entity层放实体类。JSP 统一放在WEB-INF下面,防止被直接访问。这种结构在答辩时能直接画成架构图,老师一看就知道你懂分层。

2.2 选课系统的数据库表怎么设计

学生选课系统的核心表其实就四张:学生表、课程表、教师表、选课记录表。但设计的时候有几个关键点决定了你后面写 SQL 顺不顺手。

学生表student至少要有学号、姓名、密码、专业、年级。学号做主键,不要用自增 ID 当主键,因为学号本身就有业务含义,查起来方便。课程表course要有课程号、课程名、学分、授课教师、容量上限。教师表teacher放教师工号、姓名、职称。选课记录表sc是核心关联表,字段包括学号、课程号、选课时间、成绩,学号和课程号做联合主键,防止同一个人重复选同一门课。

这里有个容易忽略的点:课程容量控制。你需要在选课的时候先查sc表里该课程已选人数,再和course表的容量字段比较。这个逻辑必须放在 Service 层,并且用事务包起来,否则并发选课时会出现超选。虽然实训项目并发量不大,但答辩时老师很可能问“你怎么保证不超过容量”,这时候你能说出“在 Service 层加同步锁或者用数据库行锁”,就是加分项。

CREATE TABLE student ( sno VARCHAR(20) PRIMARY KEY, sname VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL DEFAULT '123456', major VARCHAR(50), grade VARCHAR(20) ); CREATE TABLE course ( cno VARCHAR(20) PRIMARY KEY, cname VARCHAR(100) NOT NULL, credit DECIMAL(3,1), tno VARCHAR(20), capacity INT DEFAULT 50, selected INT DEFAULT 0 ); CREATE TABLE sc ( sno VARCHAR(20), cno VARCHAR(20), select_time DATETIME DEFAULT CURRENT_TIMESTAMP, score DECIMAL(5,1), PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno) );

上面这段 SQL 可以直接在 MySQL 里执行。注意sc表用了联合主键,这样同一个学生选同一门课第二次插入就会报主键冲突,天然防重。course表里我加了selected字段来记录已选人数,这样查容量的时候不用每次都COUNT(*),选课成功时selected加一,退课时减一。这个设计在答辩时可以说成“冗余字段优化查询性能”,老师会觉得你考虑得比较细。

2.3 外键约束要不要加

很多实训教程会建议你加外键,保证数据一致性。我的经验是:加,但要知道什么时候会出问题。比如你删除一个学生,如果sc表里有他的选课记录,外键约束会阻止删除。这时候你有两个选择:要么先删sc里的记录再删学生,要么在sc表的外键上加ON DELETE CASCADE。我一般会手动在 Service 层控制删除顺序,因为级联删除在实训项目里容易误删数据,调试的时候一个不小心就把选课记录全清了。

如果你用 Navicat 或者 DataGrip 导入 SQL 文件,注意字符集要选utf8mb4,否则学生姓名里有生僻字会乱码。排序规则用utf8mb4_general_ci就行,不用纠结unicode_ci。

3. 在 IDEA 里跑通项目:从导入到登录成功的完整命令与配置

3.1 导入项目后第一件事:检查 JDK 和 Tomcat 版本

拿到源代码压缩包解压后,用 IDEA 打开项目根目录。第一件事不是急着运行,而是检查File -> Project Structure里的 JDK 版本。如果项目是用 JDK 8 写的,你本地配了 JDK 17,大概率会报Unsupported class file major version。我一般会统一用 JDK 8 或 JDK 11 跑这种传统 JavaWeb 项目,兼容性最好。

Tomcat 版本也要注意。Tomcat 9 和 Tomcat 10 的 Servlet API 包名不一样:Tomcat 9 是javax.servlet,Tomcat 10 变成了jakarta.servlet。如果你导入的项目里 import 的是javax.servlet,那就必须用 Tomcat 9 或更早版本。这个坑我见过太多人踩,明明代码没问题,一启动就报ClassNotFoundException,查半天才发现是 Tomcat 版本不匹配。

在 IDEA 里配置 Tomcat 的路径是Run -> Edit Configurations -> + -> Tomcat Server -> Local。然后在Deployment标签页里点+选择Artifact,把项目的 war 包或者 exploded 包加进去。Application context建议设成/,这样访问路径就是http://localhost:8080/,不用在后面加项目名。

3.2 数据库连接配置:改对这三处就能连上

源代码里数据库连接信息一般在src目录下的db.properties或者c3p0-config.xml里。你需要改三个地方:数据库地址、用户名、密码。如果是 MySQL 8.0,地址后面要加时区和 SSL 参数:

driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/student_course?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username=root password=你的密码

serverTimezone=Asia/Shanghai不加的话,MySQL 8.0 会报时区错误。useSSL=false是关掉 SSL 连接,本地开发不需要。如果你用的是 MySQL 5.7,驱动类名改成com.mysql.jdbc.Driver,URL 里不需要serverTimezone参数。

改完配置后,先在 IDEA 的 Database 工具窗口里测试连接。能连上再启动 Tomcat,否则你会在控制台看到一堆Communications link failure。数据库名要和你导入 SQL 文件时创建的库名一致,我一般叫student_course,你可以在 SQL 文件第一行看到CREATE DATABASE语句。

3.3 启动 Tomcat 并验证登录

配置好后点击 IDEA 右上角的绿色三角启动 Tomcat。控制台如果输出Server startup in xxx ms并且没有红色报错,说明启动成功。打开浏览器访问http://localhost:8080/,应该能看到登录页。

默认账号一般在 SQL 文件的INSERT语句里。学生登录用学号,管理员登录用admin。如果登录后页面跳转 404,检查web.xml或者@WebServlet注解里的 URL 映射路径。如果登录后提示“用户名或密码错误”,但数据库里明明有数据,那大概率是密码字段加密方式不匹配。有些项目用 MD5 存密码,你插入的是明文,自然对不上。解决办法是在 SQL 里把密码字段改成明文,或者用项目里的工具类重新生成 MD5 值。

// 登录 Servlet 核心逻辑示例 protected void doPost(HttpServletRequest request, HttpServletResponse response) { String sno = request.getParameter("sno"); String password = request.getParameter("password"); Student student = studentService.login(sno, password); if (student != null) { request.getSession().setAttribute("student", student); response.sendRedirect("course_list.jsp"); } else { request.setAttribute("msg", "学号或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }

这段代码展示了典型的 Servlet 登录流程:从请求里取参数,调用 Service 层查数据库,成功就把用户信息放进 Session,失败就带错误信息转发回登录页。注意sendRedirect和forward的区别——重定向会改变浏览器地址栏,转发不会。登录成功用重定向是为了防止刷新页面重复提交表单。

4. 选课、退课、成绩录入:核心业务代码与事务控制

4.1 选课功能的完整实现与容量校验

选课是这套系统里最核心也最容易出问题的功能。表面上看就是往sc表插一条记录,但实际上要处理三件事:判断课程是否已满、判断学生是否已经选过、更新课程已选人数。这三步必须在一个事务里完成,否则会出现数据不一致。

public String selectCourse(String sno, String cno) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 查课程容量和已选人数 String sql1 = "SELECT capacity, selected FROM course WHERE cno = ? FOR UPDATE"; PreparedStatement ps1 = conn.prepareStatement(sql1); ps1.setString(1, cno); ResultSet rs = ps1.executeQuery(); if (!rs.next()) return "课程不存在"; int capacity = rs.getInt("capacity"); int selected = rs.getInt("selected"); if (selected >= capacity) return "课程已满"; // 2. 检查是否重复选课 String sql2 = "SELECT COUNT(*) FROM sc WHERE sno = ? AND cno = ?"; PreparedStatement ps2 = conn.prepareStatement(sql2); ps2.setString(1, sno); ps2.setString(2, cno); ResultSet rs2 = ps2.executeQuery(); rs2.next(); if (rs2.getInt(1) > 0) return "已选过该课程"; // 3. 插入选课记录并更新已选人数 String sql3 = "INSERT INTO sc(sno, cno) VALUES(?, ?)"; PreparedStatement ps3 = conn.prepareStatement(sql3); ps3.setString(1, sno); ps3.setString(2, cno); ps3.executeUpdate(); String sql4 = "UPDATE course SET selected = selected + 1 WHERE cno = ?"; PreparedStatement ps4 = conn.prepareStatement(sql4); ps4.setString(1, cno); ps4.executeUpdate(); conn.commit(); return "选课成功"; } catch (Exception e) { if (conn != null) try { conn.rollback(); } catch (SQLException ex) {} return "系统错误:" + e.getMessage(); } finally { DBUtil.close(conn); } }

这段代码的关键在于FOR UPDATE行锁和setAutoCommit(false)。FOR UPDATE会在查询时锁住这一行,防止其他事务同时修改。虽然实训项目并发低,但答辩时你能说出“用行锁防止超选”,老师会认为你有实际工程思维。conn.commit()在所有操作成功后提交,任何一步抛异常都会走rollback(),保证要么全成功要么全回滚。

参数说明:sno和cno从 JSP 表单提交过来,Service 层不直接拼 SQL,全部用PreparedStatement防止 SQL 注入。返回字符串给 Servlet 层,Servlet 再决定跳转到哪个页面并显示提示信息。

4.2 退课与成绩录入的边界处理

退课逻辑比选课简单,但也要注意:只能退还没录入成绩的课程。如果sc表里score字段已经有值,就不允许退课。这个判断要在 Service 层做,SQL 写成DELETE FROM sc WHERE sno = ? AND cno = ? AND score IS NULL,根据executeUpdate()返回的影响行数判断是否退课成功。

成绩录入是教师端功能。教师登录后看到自己教的课程,选择课程后列出所有选了这门课的学生,然后逐个填分数。这里要注意权限控制:教师只能录入自己教的课程成绩。在 SQL 里加一个AND tno = ?条件,把当前教师的工号传进去。如果不加这个条件,教师 A 就能改教师 B 的课程成绩,答辩时被问到就是安全漏洞。

-- 教师查询自己课程的学生列表 SELECT s.sno, s.sname, sc.score FROM sc JOIN student s ON sc.sno = s.sno JOIN course c ON sc.cno = c.cno WHERE c.tno = ? AND sc.cno = ?; -- 录入成绩 UPDATE sc SET score = ? WHERE sno = ? AND cno = ?;

成绩字段用DECIMAL(5,1)可以存 0 到 100 分,保留一位小数。如果学校要求整数,改成INT也行。录入成绩后,学生端就能查到自己的分数。这个功能在答辩演示时很直观,建议提前准备几条测试数据。

4.3 分页查询与模糊搜索

课程列表如果数据多了,一页显示不完,需要分页。MySQL 分页用LIMIT关键字:SELECT * FROM course LIMIT 0, 10表示从第 0 条开始取 10 条。在 Java 里你需要计算起始位置:int start = (pageNum - 1) * pageSize。同时还要查总记录数SELECT COUNT(*) FROM course,用来算总页数。

模糊搜索用LIKE关键字:SELECT * FROM course WHERE cname LIKE CONCAT('%', ?, '%')。注意用CONCAT而不是直接拼%?%,因为PreparedStatement会把问号替换成带引号的字符串,直接写%?%语法不对。这个细节很多新手会踩坑,SQL 报错但看不出原因。

分页和搜索结合的时候,总记录数的查询也要带上同样的WHERE条件,否则分页页码会算错。我一般会写一个通用的分页工具类,把pageNum、pageSize、totalCount、list封装成一个PageBean对象,JSP 页面直接遍历list并渲染页码按钮。

5. 答辩 PPT 与实训报告:怎么把代码讲成老师想听的内容

5.1 答辩 PPT 的页面结构与讲解节奏

答辩 PPT 不需要花哨,10 到 12 页足够。我建议的结构是:封面、项目背景与意义、技术选型、系统功能模块图、数据库设计(E-R 图加表结构)、核心功能实现(选课事务、分页查询)、运行截图、遇到的问题与解决方案、总结与展望。

重点放在“遇到的问题与解决方案”这一页。老师最喜欢听你踩过什么坑、怎么解决的。比如你可以写“MySQL 8.0 时区报错,通过添加 serverTimezone 参数解决”“Tomcat 10 与 javax.servlet 不兼容,降级到 Tomcat 9 解决”。这些真实问题比堆砌技术名词更有说服力。

讲解节奏控制在 8 到 10 分钟。前 3 分钟讲背景和功能,中间 4 分钟演示系统,最后 2 分钟讲问题和收获。演示的时候提前把数据准备好,不要现场注册新用户,浪费时间还容易出错。

5.2 实训报告里必须写清楚的几个部分

实训报告一般要求 3000 到 5000 字,包含需求分析、系统设计、详细实现、测试、总结。需求分析部分用用例图说明学生和教师分别能做什么。系统设计部分画 E-R 图和系统架构图。详细实现部分贴关键代码,但不要全贴,选课事务、登录验证、分页查询这三段就够了。

测试部分要写测试用例:正常选课、重复选课、课程已满、退课、录入成绩。每个用例写输入、预期输出、实际输出。如果实际输出和预期不一致,把调试过程也写进去,这反而是加分项。老师看报告不是看你写得有多完美,而是看你有没有真正动手做过。

数据库设计部分把建表 SQL 贴上去,并解释每个字段的含义。外键约束、联合主键、冗余字段这些设计决策要说明理由。比如“sc 表使用联合主键防止重复选课”“course 表增加 selected 字段减少 COUNT 查询”。

5.3 源代码和 SQL 文件的整理规范

提交之前把源代码里的注释补全,尤其是 Service 层和 DAO 层的方法。类名和方法名用英文,不要用拼音。SQL 文件里加上DROP TABLE IF EXISTS语句,方便老师重复导入。文件编码统一用 UTF-8,避免中文乱码。

目录结构建议这样组织:src下分dao、service、servlet、entity、util五个包。WebContent或webapp下分css、js、jsp、WEB-INF。SQL 文件单独放在项目根目录,命名成student_course.sql。README 文件写清楚环境要求、导入步骤、默认账号密码。这些细节能让老师觉得你做事规范。

6. 避坑与排查:那些让我熬夜的报错和解决思路

6.1 数据库连接报错:从 Communications link failure 到时区异常

现象:启动 Tomcat 后访问登录页,控制台报Communications link failure或者The server time zone value '?D1ú±ê×?ê±??' is unrecognized。

原因:第一种是 MySQL 服务没启动,或者端口不是 3306。第二种是 MySQL 8.0 的时区参数没配,驱动无法识别系统时区。

解决:先确认 MySQL 服务在运行,命令行执行mysql -u root -p能登录。然后在 JDBC URL 后面加?serverTimezone=Asia/Shanghai&useSSL=false。如果还报错,检查db.properties里的驱动类名是不是com.mysql.cj.jdbc.Driver。MySQL 5.7 用com.mysql.jdbc.Driver,写错了会报ClassNotFoundException。

6.2 中文乱码:从页面到数据库的三层排查

现象:JSP 页面输入中文,存到数据库变成???,或者数据库里是中文但页面显示乱码。

原因:乱码可能出现在三个环节——JSP 页面编码、请求编码、数据库编码。任何一层不是 UTF-8 都会出问题。

解决:JSP 文件头部加<%@ page contentType="text/html;charset=UTF-8" %>。在 Servlet 里取参数之前设置request.setCharacterEncoding("UTF-8")。数据库连接 URL 加characterEncoding=utf8。建库的时候用CREATE DATABASE student_course DEFAULT CHARACTER SET utf8mb4。三层都对齐 UTF-8,乱码基本就消失了。如果用的是 Tomcat 8 以上,GET 请求的中文乱码还需要在server.xml的 Connector 标签里加URIEncoding="UTF-8"。

6.3 404 和 500 报错:路径与空指针的快速定位

现象:访问某个页面报 404,或者提交表单后报 500。

原因:404 一般是 URL 映射路径写错了,或者 JSP 文件放错目录。500 最常见的是空指针异常,比如request.getSession().getAttribute("student")返回 null 但没做判断。

解决:404 先检查@WebServlet("/login")注解里的路径和表单action是否一致。如果 JSP 放在WEB-INF下,不能直接通过浏览器访问,必须通过 Servlet 转发。500 报错看控制台堆栈信息,找到报错的行号。空指针一般是因为 Session 过期或者用户没登录就访问了需要登录的页面。在 Servlet 里加一个登录拦截器,判断 Session 里有没有用户信息,没有就跳回登录页。

6.4 Tomcat 版本不兼容:javax 与 jakarta 的抉择

现象:项目在别人电脑上能跑,在你这里启动就报java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet。

原因:Tomcat 10 把 Servlet API 的包名从javax.servlet改成了jakarta.servlet。你的项目代码用的是旧包名,但 Tomcat 10 里没有这些类。

解决:换 Tomcat 9 或者更早版本。如果非要用 Tomcat 10,需要把代码里所有javax.servlet改成jakarta.servlet,但这样项目在 Tomcat 9 上又跑不了。实训项目建议统一用 Tomcat 9,稳定且资料多。在 IDEA 里重新配置 Tomcat 9 的路径,然后重新部署 Artifact。

6.5 选课人数超限:并发场景下的数据不一致

现象:课程容量 50 人,但选课结束后sc表里有 52 条记录。

原因:两个学生同时选课,都查了selected发现小于容量,然后都执行了插入和更新。这是典型的并发问题。

解决:在查询课程容量时加FOR UPDATE行锁,或者在 Service 层方法上加synchronized关键字。更优雅的做法是用数据库的乐观锁:UPDATE course SET selected = selected + 1 WHERE cno = ? AND selected < capacity,根据影响行数判断是否选课成功。如果返回 0 行,说明容量已满,回滚事务。这个方案不需要显式加锁,性能更好,答辩时讲出来也更有深度。

7. 从能跑到能讲:一套选课系统的进阶打磨与验证方法

项目跑通只是第一步,真正拉开差距的是你能不能把它讲清楚、能不能在原有基础上做一点扩展。我一般会建议在答辩前做三件事:加一个登录过滤器、把 JDBC 换成 Druid 连接池、给选课接口加一个简单的压力测试。

登录过滤器用Filter实现,拦截所有需要登录的路径,检查 Session 里有没有用户信息。这个功能代码量不大,但能体现你对 Web 安全的理解。Druid 连接池替换掉原生的DriverManager,配置initialSize、maxActive、maxWait几个参数,然后在web.xml里配ServletContextListener初始化。压力测试不用太复杂,用 Java 写个多线程循环调选课接口,观察selected字段有没有超过capacity。如果没超过,说明你的事务控制是有效的。

验证方法上,我习惯用“三查一看”:查数据库记录数、查控制台日志、查页面提示信息,看事务回滚后数据是否一致。比如模拟选课失败的情况,手动把课程容量改成 0,然后选课,看sc表有没有多出记录,course表的selected有没有变化。如果都没变,说明回滚生效了。

答辩前一周,把项目在干净的环境里重新导入一遍,从解压到登录成功掐个表,记录每一步的耗时和报错。这个过程能帮你发现很多“在我电脑上好好的”问题。我自己的习惯是写一个setup.md,把 JDK 版本、Tomcat 版本、MySQL 版本、导入步骤、默认账号全列进去,答辩当天直接照着做,不用临时回忆。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询