尧图精选

JavaWeb学生选课系统实训:从建库到答辩全流程避坑指南

🕒 发布时间:2026/10/1 4:27:14 📁 来源:尧图网络
简介这份JavaWeb实训资源面向高校计算机相关专业学生与课程设计开发者围绕学生选课系统这一经典Web项目提供从需求分析到代码落地的完整参考。系统按角色划分功能学生可注册登录、浏览课程、选课退选并查询已选结果教师可发布与删除课程、查看选课情况涵盖注册登录、学生选课、选课结果浏览、教师浏览结果、教师添加课程五大模块。资源包共111个文件约20.84MB包含java源码、jsp页面、class编译文件、css与scss样式、sql建库脚本以及实训报告docx和答辩pptx另附jar依赖与前端字体资源结构完整便于二次开发与答辩准备。目前已有4575人学习下载适合需要快速搭建选课系统、撰写实训报告或准备课程答辩的读者参考借鉴。1. 学生选课系统实训从建库到答辩一套能跑通的 JavaWeb 全流程每年学期中后段计算机专业的学生都会撞上同一件事JavaWeb 实训。题目往往不复杂学生选课系统就是最经典的那一类但真正动手时很多人卡在环境配置、数据库连不上、JSP 页面 404 这些具体问题上而不是业务逻辑本身。这套「实训报告 答辩 PPT 源代码 SQL 文件」的组合本质上是一个完整的交付物清单代码要能跑数据库要能建报告要能写答辩要能讲清楚。它适合正在做课程设计的学生也适合想用一个完整案例把 JavaWeb 知识点串起来的自学者。下面按实际开发顺序把建库、搭环境、写代码、排错、答辩这几件事拆开讲。2. 先定技术栈再动手为什么是 JSP Servlet MySQL2.1 选型不是越新越好而是看实训评分点很多同学一上来就想用 Spring Boot觉得新技术加分。但实训评分表里通常明确要求体现 MVC 分层、JSP 页面、Servlet 跳转、JDBC 连接这些基础点。用 Spring Boot 反而可能因为「封装太深」被扣分答辩时老师问「你的请求是怎么从浏览器到数据库的」答不上来就尴尬了。常见做法是JSP 负责视图Servlet 负责控制JavaBean 负责数据封装DAO 层用 JDBC 直接操作 MySQL。这套组合虽然老但每一层都看得见摸得着答辩时能讲清楚数据流向。技术栈定下来之后后面所有代码和配置都围绕它展开。层次技术职责视图层JSP JSTL展示课程列表、选课结果控制层Servlet接收请求、调用 DAO、跳转页面模型层JavaBean封装学生、课程、选课记录数据层JDBC MySQL增删改查、事务控制2.2 数据库设计三张表撑起整个系统选课系统的核心关系是一个学生可以选多门课一门课可以被多个学生选所以需要一张中间表来记录选课关系。建库语句如下直接复制到 MySQL 客户端执行即可。-- 创建数据库字符集用 utf8mb4 避免中文乱码 CREATE DATABASE course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE course_selection; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(50) NOT NULL DEFAULT 123456 COMMENT 登录密码, major VARCHAR(50) COMMENT 专业 ); -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, teacher VARCHAR(50) COMMENT 任课教师, credit INT DEFAULT 2 COMMENT 学分, capacity INT DEFAULT 50 COMMENT 容量, selected INT DEFAULT 0 COMMENT 已选人数 ); -- 选课记录表student_id 和 course_id 组合唯一防止重复选课 CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) ); -- 插入测试数据 INSERT INTO student (student_no, name, major) VALUES (2024001, 张三, 计算机科学), (2024002, 李四, 软件工程); INSERT INTO course (course_no, course_name, teacher, credit, capacity) VALUES (C001, Java程序设计, 王老师, 3, 40), (C002, 数据库原理, 刘老师, 3, 35), (C003, Web开发技术, 陈老师, 2, 30);这段 SQL 里有几个关键点utf8mb4字符集必须显式指定否则 JSP 页面提交的中文姓名会变成问号selection表的唯一索引uk_student_course是防止重复选课的第一道防线比在 Java 代码里查一遍再插入更可靠外键约束保证不会出现选了不存在的课。测试数据里课程容量先留足方便后面演示选课和退课。2.3 项目目录结构按 MVC 分包别全塞一个文件夹在 IDEA 里新建一个 JavaWeb 项目目录结构建议这样组织src/ com.example.entity/ -- Student.java, Course.java, Selection.java com.example.dao/ -- StudentDao.java, CourseDao.java com.example.servlet/ -- LoginServlet.java, CourseListServlet.java, SelectCourseServlet.java com.example.util/ -- DBUtil.java web/ WEB-INF/ web.xml -- Servlet 映射配置 lib/ -- mysql-connector-java.jar login.jsp course_list.jsp my_courses.jsp包名用com.example开头就行不用纠结域名。DBUtil单独抽出来后面换数据库连接参数只改一个地方。web.xml里配置 Servlet 映射或者用WebServlet注解也可以但实训报告里通常要求写出web.xml配置所以建议保留。3. 从登录到选课Servlet JSP 的最小闭环实现3.1 数据库连接工具类一个地方管住所有连接JDBC 连接如果每个 Servlet 都写一遍改密码时会想哭。抽一个DBUtil用静态代码块加载驱动提供获取连接和关闭资源的方法。package com.example.util; import java.sql.*; public class DBUtil { // 数据库连接参数改这里就行 private static final String URL jdbc:mysql://localhost:3306/course_selection?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { // 加载 MySQL 驱动新版驱动类名是 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源顺序不能反先 ResultSet再 Statement最后 Connection public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }URL里的serverTimezoneAsia/Shanghai是必须的否则 MySQL 8 会报时区错误。characterEncodingutf8保证中文参数不乱码。close方法的关闭顺序不能颠倒先关结果集再关语句再关连接这是 JDBC 的基本规矩。3.2 登录 Servlet把学号和密码对上了就放行登录逻辑很简单接收表单的学号和密码去student表里查查到了就把学生对象放进 Session跳转到课程列表查不到就回登录页并提示错误。package com.example.servlet; import com.example.entity.Student; import com.example.util.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.*; WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 必须放在获取参数之前 String studentNo req.getParameter(studentNo); String password req.getParameter(password); String sql SELECT id, student_no, name, major FROM student WHERE student_no ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentNo); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { Student stu new Student(); stu.setId(rs.getInt(id)); stu.setStudentNo(rs.getString(student_no)); stu.setName(rs.getString(name)); stu.setMajor(rs.getString(major)); // 登录成功学生信息存入 Session req.getSession().setAttribute(student, stu); resp.sendRedirect(courseList); } else { req.setAttribute(msg, 学号或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } catch (SQLException e) { e.printStackTrace(); resp.sendError(500, 数据库异常); } } }req.setCharacterEncoding(UTF-8)必须写在getParameter之前否则中文参数会乱码。用PreparedStatement而不是Statement拼接字符串既防 SQL 注入又避免引号转义问题。登录成功后用sendRedirect而不是forward避免刷新页面时重复提交表单。3.3 课程列表与选课事务控制是核心课程列表 Servlet 查询所有课程转发到 JSP 展示。选课 Servlet 需要做三件事检查是否已选、检查容量是否已满、插入选课记录并更新已选人数。这三步必须在一个事务里完成否则并发时会出现超选。package com.example.servlet; import com.example.entity.Student; import com.example.util.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.*; WebServlet(/selectCourse) public class SelectCourseServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Student stu (Student) req.getSession().getAttribute(student); if (stu null) { resp.sendRedirect(login.jsp); return; } int courseId Integer.parseInt(req.getParameter(courseId)); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查课程容量 String checkSql SELECT capacity, selected FROM course WHERE id ? FOR UPDATE; PreparedStatement checkPs conn.prepareStatement(checkSql); checkPs.setInt(1, courseId); ResultSet rs checkPs.executeQuery(); if (!rs.next()) { conn.rollback(); resp.sendRedirect(courseList?msg课程不存在); return; } int capacity rs.getInt(capacity); int selected rs.getInt(selected); if (selected capacity) { conn.rollback(); resp.sendRedirect(courseList?msg课程已满); return; } // 2. 插入选课记录唯一索引会拦住重复选课 String insertSql INSERT INTO selection (student_id, course_id) VALUES (?, ?); PreparedStatement insertPs conn.prepareStatement(insertSql); insertPs.setInt(1, stu.getId()); insertPs.setInt(2, courseId); insertPs.executeUpdate(); // 3. 更新已选人数 String updateSql UPDATE course SET selected selected 1 WHERE id ?; PreparedStatement updatePs conn.prepareStatement(updateSql); updatePs.setInt(1, courseId); updatePs.executeUpdate(); conn.commit(); // 三步都成功才提交 resp.sendRedirect(courseList?msg选课成功); } catch (SQLIntegrityConstraintViolationException e) { // 唯一索引冲突说明已经选过这门课 try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } resp.sendRedirect(courseList?msg你已经选过这门课); } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); resp.sendError(500, 选课失败); } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }FOR UPDATE在查询课程时加行锁防止两个学生同时读到相同的selected值。conn.setAutoCommit(false)开启事务后任何一步失败都要rollback。捕获SQLIntegrityConstraintViolationException来处理重复选课比先查再插更简洁。最后finally里恢复自动提交并关闭连接避免连接池耗尽。3.4 JSP 页面用 JSTL 循环展示别写 Java 脚本课程列表页面用 JSTL 的c:forEach遍历请求域里的课程列表避免在 JSP 里写% %脚本段。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle课程列表/title/head body h2欢迎${sessionScope.student.name}/h2 p stylecolor:red${param.msg}/p table border1 trth课程编号/thth课程名称/thth教师/thth学分/thth已选/容量/thth操作/th/tr c:forEach items${courseList} varcourse tr td${course.courseNo}/td td${course.courseName}/td td${course.teacher}/td td${course.credit}/td td${course.selected}/${course.capacity}/td td c:if test${course.selected course.capacity} a hrefselectCourse?courseId${course.id}选课/a /c:if c:if test${course.selected course.capacity} 已满 /c:if /td /tr /c:forEach /table /body /html${param.msg}直接读取 URL 参数里的提示信息选课成功后重定向回来就能显示。c:if判断容量决定是否显示选课链接避免点进去才报错。JSTL 标签需要引入jstl.jar和standard.jar放在WEB-INF/lib下。4. 环境配置与联调IDEA 里跑 JavaWeb 的避坑清单4.1 Tomcat 配置别用最新版8.5 或 9.0 最稳IDEA 里配置 Tomcat 时常见问题是「Artifact 没有部署」或「端口被占用」。正确步骤是Run → Edit Configurations → 加号 → Tomcat Server → Local → 在 Deployment 标签页点加号 → 选 Artifact → 选项目名:war exploded。war exploded支持热部署改 JSP 不用重启。Tomcat 版本建议用 8.5 或 9.0不要用 Tomcat 10。Tomcat 10 把javax.servlet包名改成了jakarta.servlet所有 Servlet 代码都要改 import实训项目没必要折腾这个。4.2 MySQL 驱动版本8.0 以上用 com.mysql.cj.jdbc.DriverMySQL 5.7 和 8.0 的驱动类名不同。5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver。如果驱动版本和类名对不上启动时会报ClassNotFoundException。连接 URL 里 MySQL 8.0 必须加serverTimezone否则报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。4.3 中文乱码三个地方都要设 UTF-8中文乱码是 JavaWeb 实训里最高频的问题血泪经验是JSP 页面pageEncodingUTF-8、Servlet 里req.setCharacterEncoding(UTF-8)、数据库连接 URL 里characterEncodingutf8三个地方缺一不可。如果还乱码检查 MySQL 数据库和表的字符集是不是utf8mb4。5. 答辩前必须排查的 5 个翻车点5.1 现象登录后跳转 404地址栏显示 /login 而不是 /courseList原因web.xml里 Servlet 映射配错了或者WebServlet注解的路径和表单 action 不一致。解决检查表单form actionlogin和WebServlet(/login)是否匹配web.xml里url-pattern是否写成了/login。5.2 现象选课成功但已选人数没变原因更新course表的 SQL 没执行或者事务提交前抛了异常被 catch 吞掉。解决在catch块里加e.printStackTrace()看控制台有没有异常。检查UPDATE语句的WHERE id ?参数是否传对。5.3 现象同一学生能重复选同一门课原因selection表的唯一索引没建或者建了但插入时没触发。解决执行SHOW INDEX FROM selection;确认uk_student_course存在。如果不存在用ALTER TABLE selection ADD UNIQUE KEY uk_student_course (student_id, course_id);补上。5.4 现象Tomcat 启动报 Port 8080 already in use原因8080 端口被其他程序占用常见的是之前没关干净的 Tomcat 进程。解决Windows 下netstat -ano | findstr 8080找到 PID任务管理器结束进程或者改 Tomcat 配置里的端口号。5.5 现象答辩时老师问「你的系统怎么防 SQL 注入」答不上来原因代码里用了Statement拼接 SQL。解决把所有Statement换成PreparedStatement用?占位符传参。答辩时直接指出LoginServlet和SelectCourseServlet里用的都是PreparedStatement这就是防注入的标准做法。6. 实训报告与答辩 PPT 的写法把代码讲成故事6.1 报告结构按「需求→设计→实现→测试」四段走实训报告不要写成代码注释的堆砌。常见结构是第一章需求分析写清楚系统要解决什么问题、有哪几个角色第二章数据库设计贴 E-R 图和建表 SQL第三章详细实现按登录、课程列表、选课、退课四个功能分别写流程第四章测试贴运行截图和测试用例表。每章配一张图E-R 图、流程图、界面截图都行。6.2 答辩 PPT10 页以内重点讲你踩过的坑PPT 页数控制在 8 到 10 页。封面、项目背景、技术栈、数据库设计、核心功能演示、难点与解决、总结。其中「难点与解决」是加分项把第 5 章里的翻车点挑两个讲比如「重复选课怎么用唯一索引解决」「并发选课怎么用事务和行锁解决」。老师听到你讲踩坑就知道代码是你自己写的。6.3 一个具体技巧用 Postman 或浏览器直接测 Servlet答辩前把每个 Servlet 的 URL 单独在浏览器里跑一遍比如http://localhost:8080/项目名/courseList看能不能正常返回。如果报 500看 Tomcat 日志里的异常堆栈定位到具体行号。这个习惯能帮你在答辩前把大部分低级错误清掉。我自己的习惯是代码写完先不急着写报告把整个流程从登录到选课到退课手动走三遍每遍记录一个卡住的地方这些就是报告里「测试」章节的素材。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →