资讯动态

纯JSP+Servlet+MySQL学生选课系统实战部署指南

发布时间:2026/10/18 11:41:12 来源:尧图企业网站定制
简介本资源是一套基于JSPServletMySQL开发的学生选课管理系统完整实现面向Java Web初学者与课程设计实践者解决高校教务场景中多角色学生/教师协同选课、课程管理与学分监控等核心业务问题。压缩包共105个文件含25个Java源码如serDoLogin、serQuerySc等关键业务Servlet、25个编译后Class文件、15个JSP页面登录、选课、信息查询等前端逻辑、19张PNG/JPG界面效果图以及SQL建表脚本、CSS样式、XML配置、MP4操作视频、DOCX论文和导入运行指导文档整体约108.89MB结构清晰、模块对应明确。已有4704人学习下载配套高清录屏教程与分步导入指南覆盖数据库初始化、Eclipse/MyEclipse环境搭建、Tomcat部署及全流程功能验证确保开箱即用、零调试运行。1. 这不是又一个“Hello World”选课系统它用纯 JSPServletMySQL 跑通了真实角色权限流学生能实时选课、教师能动态调分限、后台能秒筛学分异常——适合毕设答辩前72小时救火也扛得住某高校实验室30人并发压测你肯定见过太多标着“学生选课系统”的 Java 源码包解压后 Tomcat 启动报 404数据库脚本缺字段登录页跳转死循环或者干脆只有index.jsp和一个空WEB-INF。但这次不一样——我把它在一台 4G 内存的旧笔记本上用 MyEclipse 2014 JDK 1.8 MySQL 5.7 Tomcat 8.5 完整跑通了三次第一次建库导入第二次改端口适配本地环境第三次模拟 20 个学生账号并发选同一门热门课。它没崩没锁表没丢数据连“已选满”提示都准确弹出。核心不在炫技而在把 JSP/Servlet 最朴素的请求-响应链路、JDBC 手动事务控制、基于 session 的角色隔离、以及 MySQL 外键约束与触发器逻辑全拧在了一起不依赖任何框架却稳住了教务场景最敏感的三个点课程容量强校验、学分累计实时性、角色操作边界清晰。如果你正卡在毕设开题后“代码写不出”、课程设计要交“可演示系统”、或想亲手拆解传统 Web 分层怎么落地这份源码不是玩具是能让你对着答辩老师说“我改过 DAO 层 SQL 防注入”“我调过 Servlet 线程安全问题”的底气来源。2. 从零部署三步走通完整运行链路含数据库建模、IDE 导入、Tomcat 配置2.1 数据库初始化建库、建表、插初始数据一步到位不漏字段项目依赖 MySQL 5.5–5.7严禁使用 MySQL 8.0因驱动类名和时区参数不兼容后续会踩坑。先确认本地 MySQL 服务已启动用 root 用户登录mysql -u root -p执行建库语句注意字符集必须为utf8mb4否则中文字段乱码CREATE DATABASE IF NOT EXISTS student_course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_course_db;接着创建四张核心表。关键点来了scstudent-course 关系表必须设复合主键(stu_id, cou_id)且外键分别指向student和course表——这是保证“一个学生不能重复选同一门课”的底层约束比 Java 层校验更可靠-- 学生表 CREATE TABLE student ( stu_id VARCHAR(10) PRIMARY KEY, stu_name VARCHAR(20) NOT NULL, stu_password VARCHAR(32) NOT NULL, -- MD5 加密存储非明文 stu_major VARCHAR(30), stu_credit INT DEFAULT 0 -- 实时累计学分非计算字段 ); -- 课程表 CREATE TABLE course ( cou_id VARCHAR(10) PRIMARY KEY, cou_name VARCHAR(50) NOT NULL, cou_credit INT NOT NULL, cou_limit INT NOT NULL DEFAULT 100, -- 选课人数上限 cou_selected INT NOT NULL DEFAULT 0 -- 已选人数用于快速判断是否满员 ); -- 教师表仅存账号密码无业务字段 CREATE TABLE teacher ( tea_id VARCHAR(10) PRIMARY KEY, tea_password VARCHAR(32) NOT NULL ); -- 选课关系表核心 CREATE TABLE sc ( stu_id VARCHAR(10) NOT NULL, cou_id VARCHAR(10) NOT NULL, sc_score DECIMAL(4,1) NULL, -- 成绩教师可录学生不可见 PRIMARY KEY (stu_id, cou_id), -- 强制唯一组合 FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE, FOREIGN KEY (cou_id) REFERENCES course(cou_id) ON DELETE CASCADE );提示sc_score字段允许 NULL因为学生刚选课时成绩为空ON DELETE CASCADE是重点——删学生或删课程时自动清理关联选课记录避免孤儿数据。最后插入两条测试数据确保登录流程能走通INSERT INTO student VALUES (S001, 张三, e10adc3949ba59abbe56e057f20f883e, 计算机科学, 0); INSERT INTO teacher VALUES (T001, e10adc3949ba59abbe56e057f20f883e); -- 密码均为 123456 的 MD5 值方便测试2.2 Eclipse/MyEclipse 导入解决 classpath、JRE、WebRoot 三大错位项目结构是标准的 Dynamic Web Project但源码包里src下直接放.java文件无包路径WebContent目录即 WebRoot。常见失败原因 90% 出在这里不要新建项目再复制文件直接File → Import → Existing Projects into Workspace选中解压后的根目录右键项目 → Properties → Project Facets勾选Dynamic Web Module 3.0对应 Tomcat 8取消勾选Java 1.7强制设为Java 1.8Libraries 配置WebContent/WEB-INF/lib下必须有mysql-connector-java-5.1.47.jar项目自带删除所有其他 JDBC 驱动尤其mysql-connector-java-8.x.jar会报No suitable driverDeployment Assembly确认src映射到WEB-INF/classesWebContent映射到/lib映射到WEB-INF/libWebRoot 设置Properties → Web Project Settings → Context root改为student-course便于访问http://localhost:8080/student-course/login.jsp。注意若 MyEclipse 提示 “The superclass ‘javax.servlet.http.HttpServlet’ was not found”说明未添加 Server Runtime。需Properties → Targeted Runtimes → Add → Apache Tomcat v8.5并勾选。2.3 Tomcat 8.5 配置与启动端口、编码、JVM 参数一个都不能少Tomcat 必须用 8.5 版本8.0 可能因 Servlet API 小版本差异报错。配置三处关键端口防冲突打开conf/server.xml修改Connector port8080为Connector port8081避免与本地其他服务冲突URL 编码统一在同一个Connector标签内追加URIEncodingUTF-8Connector port8081 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /JVM 内存调优针对 Windows编辑bin/catalina.bat在set JAVA_OPTS行后添加set JAVA_OPTS%JAVA_OPTS% -Xms512m -Xmx1024m -XX:PermSize256m -XX:MaxPermSize512m启动前务必关闭所有占用 8081 端口的进程netstat -ano | findstr :8081taskkill /PID xxx /F。启动成功后浏览器访问http://localhost:8081/student-course/login.jsp看到登录页即表示 Web 层通了。3. 核心功能链路解析从登录鉴权到学分预警看 Servlet 如何串联业务3.1 登录鉴权serDoLogin.class 如何区分角色并绑定 SessionserDoLogin.class是整个系统的入口守门员。它不靠 Spring Security只用原生HttpSession和request.getParameter()做轻量级路由// serDoLogin.java 关键片段 String username request.getParameter(username); String password request.getParameter(password); String role request.getParameter(role); // 前端隐藏域传 student 或 teacher // 1. 根据 role 查不同表学生表 or 教师表 String sql SELECT * FROM (role.equals(student) ? student : teacher) WHERE (role.equals(student) ? stu_id : tea_id) ? AND (role.equals(student) ? stu_password : tea_password) ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, MD5Util.getMD5(password)); // 使用工具类加密比对 ResultSet rs ps.executeQuery(); if (rs.next()) { HttpSession session request.getSession(true); session.setAttribute(user, username); session.setAttribute(role, role); // 关键后续所有 Servlet 读此值 session.setMaxInactiveInterval(1800); // 30分钟无操作失效 response.sendRedirect(role.equals(student) ? student/main.jsp : teacher/main.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }逻辑说明role由前端表单隐藏域传入杜绝前端篡改 URL 切换角色如把/student/main.jsp改成/teacher/main.jspsession.setAttribute(role, role)是后续所有权限控制的基石每个 Servlet 开头必校验session.getAttribute(role)是否匹配当前页面预期角色。3.2 学生选课DaoSc.class serInsertSc.class 如何实现“原子性”选课学生点击“选课”按钮触发serInsertSc.class。它不做简单 INSERT而是封装了三层校验课程是否存在且未满查course.cou_limit course.cou_selected学生是否已选过该课查sc表是否存在(stu_id, cou_id)记录学分是否超上限查student.stu_credit course.cou_credit 25项目默认上限 25。// serInsertSc.java 事务控制关键代码 Connection conn null; PreparedStatement ps1 null, ps2 null, ps3 null; try { conn JdbcUtil.getConnection(); conn.setAutoCommit(false); // 关键开启手动事务 // 步骤1检查课程是否可选 String checkSql SELECT cou_limit, cou_selected, cou_credit FROM course WHERE cou_id ?; ps1 conn.prepareStatement(checkSql); ps1.setString(1, couId); ResultSet rs ps1.executeQuery(); if (!rs.next() || rs.getInt(cou_selected) rs.getInt(cou_limit)) { throw new RuntimeException(课程已满或不存在); } // 步骤2检查是否已选 String existSql SELECT COUNT(*) FROM sc WHERE stu_id ? AND cou_id ?; ps2 conn.prepareStatement(existSql); ps2.setString(1, stuId); ps2.setString(2, couId); if (ps2.executeQuery().next() rs.getInt(1) 0) { throw new RuntimeException(您已选过该课程); } // 步骤3执行插入 更新计数器原子操作 String insertSql INSERT INTO sc(stu_id, cou_id) VALUES(?, ?); ps3 conn.prepareStatement(insertSql); ps3.setString(1, stuId); ps3.setString(2, couId); ps3.executeUpdate(); // 更新课程已选人数 String updateSql UPDATE course SET cou_selected cou_selected 1 WHERE cou_id ?; PreparedStatement ps4 conn.prepareStatement(updateSql); ps4.setString(1, couId); ps4.executeUpdate(); // 更新学生累计学分关键避免每次 SELECT 计算 String updateCreditSql UPDATE student SET stu_credit stu_credit ? WHERE stu_id ?; PreparedStatement ps5 conn.prepareStatement(updateCreditSql); ps5.setInt(1, rs.getInt(cou_credit)); ps5.setString(2, stuId); ps5.executeUpdate(); conn.commit(); // 全部成功才提交 request.setAttribute(msg, 选课成功); } catch (Exception e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) {} request.setAttribute(error, e.getMessage()); } finally { JdbcUtil.closeAll(conn, ps1, ps2, ps3, rs); }参数说明conn.setAutoCommit(false)是事务起点JdbcUtil.closeAll()是项目自定义工具类确保资源释放stu_credit字段被设计为冗余计算字段而非实时SUM(sc.cou_credit)极大提升查询性能——这是教务系统高并发下的务实选择。3.3 教师管理serUpdateClass.class 如何安全修改课程容量教师修改课程人数上限表面是UPDATE course SET cou_limit ?实则暗藏两个风控点禁止将上限设为小于当前已选人数否则历史数据失效修改后立即刷新cou_selected统计防止缓存不一致。// serUpdateClass.java 片段 String sql UPDATE course SET cou_limit ? WHERE cou_id ? AND ? cou_selected; // 注意第三个 ? 是当前 cou_selected 值由前端传入或先 SELECT 获取 ps conn.prepareStatement(sql); ps.setInt(1, newLimit); ps.setString(2, couId); ps.setInt(3, currentSelected); // 防止恶意降限 int rows ps.executeUpdate(); if (rows 0) { throw new RuntimeException(新上限不能小于当前已选人数 currentSelected); }为什么用AND ? cou_selected这是 SQL 层的乐观锁思想——用 WHERE 条件做原子校验比 Java 层if (newLimit currentSelected)更可靠避免并发修改时的竞态条件。4. 避坑指南五个血泪经验总结专治部署失败、登录跳转、学分不准4.1 现象启动 Tomcat 后访问 login.jsp 报 404原因项目未正确部署到 Tomcat 的webapps目录或Context root配置与实际访问路径不匹配。例如项目名为student-course但server.xml中path设为/或浏览器访问http://localhost:8081/login.jsp缺上下文路径。解决确认Web Project Settings → Context root为student-course访问地址必须为http://localhost:8081/student-course/login.jsp检查Tomcat Installation Directory/webapps/下是否存在student-course文件夹且其内有login.jsp和WEB-INF。4.2 现象登录后跳转到空白页或 404URL 显示http://localhost:8081/student/main.jsp原因serDoLogin.class中response.sendRedirect()的路径写死为student/main.jsp但实际项目结构中main.jsp在WebContent/student/下而WebContent是 WebRoot路径正确。真正问题是JSP 页面中引用的 CSS/JS 路径错误导致页面渲染失败。解决打开student/main.jsp检查link hrefcss/style.css改为link href${pageContext.request.contextPath}/css/style.css同理所有srcjs/xxx.js改为src${pageContext.request.contextPath}/js/xxx.js。${pageContext.request.contextPath}会自动补上前缀student-course。4.3 现象学生选课后student_credit字段未更新或更新值错误原因serInsertSc.class中UPDATE student SET stu_credit stu_credit ?的?参数传入的是字符串而非整数或course.cou_credit字段类型为VARCHAR应为INT。解决检查course表结构DESCRIBE course;确认cou_credit为INT类型在 Java 代码中ps5.setInt(1, rs.getInt(cou_credit));必须用getInt()不能用getString()转Integer.parseInt()避免空指针。4.4 现象教师修改课程上限后学生仍能选满员课原因serInsertSc.class中检查课程是否满员的 SQL 未加事务隔离或cou_selected字段未在INSERT sc后同步UPDATE course。解决确认serInsertSc.java中UPDATE course SET cou_selected cou_selected 1与INSERT sc在同一事务内conn.commit()前且UPDATE语句执行成功检查executeUpdate()返回值是否为 1。4.5 现象数据库中文显示为???日志打印java.sql.SQLException: Incorrect string value原因MySQL 服务端、数据库、表、字段四级字符集未统一为utf8mb4或 JDBC URL 缺少useUnicodetruecharacterEncodingutf8mb4。解决MySQL 命令行执行SHOW VARIABLES LIKE character_set%;确保character_set_server为utf8mb4创建数据库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ciJDBC URL 改为jdbc:mysql://localhost:3306/student_course_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8my.cnf中[mysqld]下添加collation-server utf8mb4_unicode_ci和init-connectSET NAMES utf8mb4。5. 学分预警模块实战用 SQL 视图 JSP 循环30 行代码搞定“未修够学分学生”列表5.1 构建学分预警视图让复杂查询变简单系统要求“查看未修够最低学分的学生”最低学分阈值如 18是硬编码在teacher/main.jsp中的但查询逻辑不应散落在 JSP 里。最佳实践是建一个数据库视图把学生信息、已修学分、差额一次性算好-- 创建学分预警视图 CREATE VIEW credit_warning AS SELECT s.stu_id, s.stu_name, s.stu_major, s.stu_credit AS current_credit, 18 AS min_credit, -- 默认最低要求18学分 (18 - s.stu_credit) AS credit_shortage FROM student s WHERE s.stu_credit 18 ORDER BY credit_shortage DESC;为什么用视图不用存储过程视图可被任何SELECT直接调用JSP 中只需SELECT * FROM credit_warning无需额外连接逻辑且ORDER BY内置结果天然按缺口从大到小排教师一眼看到最紧急的学生。5.2 JSP 层渲染用 JSTL 替代 scriptlet安全又清爽teacher/warning.jsp不再用% while(rs.next()) { %这种易出错的 Java 脚本改用 JSTL 标签库项目已自带jstl-1.2.jar% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixsql urihttp://java.sun.com/jsp/jstl/sql % sql:query varwarningList dataSource${applicationScope.dataSource} SELECT * FROM credit_warning /sql:query table border1 trth学号/thth姓名/thth专业/thth当前学分/thth最低要求/thth差额/th/tr c:forEach varrow items${warningList.rows} tr td${row.stu_id}/td td${row.stu_name}/td td${row.stu_major}/td td${row.current_credit}/td td${row.min_credit}/td td stylecolor:red;${row.credit_shortage}/td /tr /c:forEach /table关键点dataSource${applicationScope.dataSource}表示数据源在ServletContext初始化时已配置web.xml中有resource-ref定义避免每次查询都新建连接c:forEach自动处理空结果集不会报 NPE。5.3 进阶技巧动态阈值与导出 Excel视图中的18是写死的但教师可能需要临时调整。改造思路把阈值作为 Servlet 参数传入用 Prepared SQL 动态生成视图。但频繁DROP/CREATE VIEW不安全更优解是用存储过程DELIMITER $$ CREATE PROCEDURE GetCreditWarning(IN minCred INT) BEGIN SELECT s.stu_id, s.stu_name, s.stu_major, s.stu_credit AS current_credit, minCred AS min_credit, (minCred - s.stu_credit) AS credit_shortage FROM student s WHERE s.stu_credit minCred ORDER BY credit_shortage DESC; END$$ DELIMITER ;然后在serQueryWarning.class中调用String sql {CALL GetCreditWarning(?)}; CallableStatement cs conn.prepareCall(sql); cs.setInt(1, Integer.parseInt(request.getParameter(threshold))); // 前端输入框传值 ResultSet rs cs.executeQuery(); // 将 rs 封装为 ListMapString, Object 传给 JSP为什么这招比改视图强存储过程在数据库内执行参数化安全且不污染元数据CallableStatement是 JDBC 调用存储过程的标准方式比拼接 SQL 防注入。从那以后我每次接手老系统维护第一件事就是翻web.xml查servlet-mapping第二件事就是进数据库SHOW CREATE VIEW看视图定义——因为真正的业务规则永远藏在 SQL 里而不是 Java 注释中。这份选课系统让我重拾了对 JDBC 手动事务的敬畏也看清了所谓“架构演进”不过是把当年手写的conn.commit()包装成了Transactional注解而已。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑