
简介这份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.Driver5.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.servletTomcat 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 参数drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/student_course?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse usernameroot password你的密码serverTimezoneAsia/Shanghai不加的话MySQL 8.0 会报时区错误。useSSLfalse是关掉 SSL 连接本地开发不需要。如果你用的是 MySQL 5.7驱动类名改成com.mysql.jdbc.DriverURL 里不需要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 后面加?serverTimezoneAsia/ShanghaiuseSSLfalse。如果还报错检查db.properties里的驱动类名是不是com.mysql.cj.jdbc.Driver。MySQL 5.7 用com.mysql.jdbc.Driver写错了会报ClassNotFoundException。6.2 中文乱码从页面到数据库的三层排查现象JSP 页面输入中文存到数据库变成???或者数据库里是中文但页面显示乱码。原因乱码可能出现在三个环节——JSP 页面编码、请求编码、数据库编码。任何一层不是 UTF-8 都会出问题。解决JSP 文件头部加% page contentTypetext/html;charsetUTF-8 %。在 Servlet 里取参数之前设置request.setCharacterEncoding(UTF-8)。数据库连接 URL 加characterEncodingutf8。建库的时候用CREATE DATABASE student_course DEFAULT CHARACTER SET utf8mb4。三层都对齐 UTF-8乱码基本就消失了。如果用的是 Tomcat 8 以上GET 请求的中文乱码还需要在server.xml的 Connector 标签里加URIEncodingUTF-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 版本、导入步骤、默认账号全列进去答辩当天直接照着做不用临时回忆。希望帮到你。本文还有配套的精品资源点击获取