尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
JSP+Servlet+MySQL学生选课系统实战
简介本资源是一套基于JSPServletMySQL实现的多角色学生选课管理系统源码面向Java Web初学者与课程设计实践者解决高校教务场景中学生选课、教师课程管理及学分监控等核心业务需求。系统支持学生与教师双角色登录学生可完成选课、查课、修改个人信息教师可维护课程与学生数据、设置学分上下限并统计未达最低学分人员。资源包共105个文件含25个Java源文件如serDoLogin、serQuerySc等核心Servlet、25个Class编译文件、15个JSP页面、19张界面截图PNG/JPG、1个SQL建库脚本、1份Word论文及1个MP4操作视频教程整体约108.89MB结构完整便于导入MyEclipse/Eclipse快速运行。已有4701人学习下载配套提供数据库导入指南、项目部署文档及高清录屏教程覆盖从环境配置、代码调试到功能验证的全流程助开发者零门槛上手并深入理解MVC分层开发逻辑。1. 这不是“老古董堆砌”而是用 JSP Servlet MySQL 搭建学生选课管理系统的现实路径你可能在招聘JD里看到“熟悉传统Web三层架构”、在面试题中被问到“Servlet生命周期如何影响登录校验”甚至在维护遗留系统时打开一个.jsp文件——它没用 Spring Boot不跑在 Docker 里但真能跑通选课、退课、成绩录入、角色权限切换。这个标题说的就是一个可本地一键启动、多角色学生/教师/管理员隔离、MySQL 表结构清晰、无第三方框架依赖、纯 Java Web 原生技术栈落地的最小可行系统。它不追求高并发或微服务但覆盖了真实教学管理系统最核心的业务闭环用户登录鉴权 → 课程浏览与选课 → 教师开课与录分 → 管理员审核与统计。对刚学完 Java Web 基础的新手它是把HttpServletRequest、HttpSession、JDBC连成线的实战沙盒对需要快速交付院系级教务模块的开发者它是去掉冗余抽象、直击数据流与控制流的轻量参考。关键在于所有代码都基于 JDK 8、Tomcat 9、MySQL 5.7/8.0 兼容设计没有隐藏的 classpath 冲突没有未声明的 jar 包依赖更不依赖任何商业中间件。2. 为什么选 JSP Servlet MySQL 而不是 Spring Boot从技术选型到三层解耦逻辑2.1 不是怀旧而是明确约束下的理性选择当目标是“3 天内交付一个可演示的院系级选课原型”且部署环境限定为老旧服务器仅开放 Tomcat MySQL 端口、运维团队只熟悉 JDBC 配置、开发人员刚结束 JavaSE 学习时Spring Boot 的自动装配反而成为负担。JSP Servlet 的优势在于可控性高每个请求路径如/login,/selectCourse对应明确的doPost()方法调试时可直接在service()中打断点观察request.getParameter(role)的值学习映射强HttpServletRequest获取表单参数 ↔HttpServletResponse输出 HTML 片段 ↔HttpSession存储登录态三者关系与 HTTP 协议天然对齐资源占用低无嵌入式 Tomcat、无 Spring 容器初始化耗时WAR 包体积通常 5MB适合教育网带宽受限场景。提示本系统不使用 Filter 做统一鉴权而是在每个 Servlet 的doGet/doPost开头手动校验session.getAttribute(userRole)看似重复实则让权限逻辑透明可查——这对理解“会话状态如何跨请求保持”至关重要。2.2 数据库设计用 ER 图锚定业务实体关系学生选课本质是典型的多对多关系需引入中间表。本系统 MySQL 表结构严格遵循第三范式共 5 张核心表表名主键关键外键说明userid(INT PK)—统一用户表含username,password,rolestudent/teacher/admincourseid(INT PK)—课程信息含name,credit,teacher_id关联 user.idstudent_course(student_id, course_id)(复合PK)student_id → user.id,course_id → course.id选课记录表含statusselected/dropped/gradedgradeid(INT PK)student_id → user.id,course_id → course.id成绩表含scoreDECIMAL(3,1)、teacher_id录分教师admin_logid(INT PK)—管理员操作日志含operator_id,action,target_table,create_time2.2.1 创建 course 表的关键 DDL 语句及字段设计依据CREATE TABLE course ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 课程名称如Java程序设计, credit TINYINT NOT NULL DEFAULT 2 COMMENT 学分取值1-6, teacher_id INT NOT NULL COMMENT 授课教师ID关联user.id, max_capacity SMALLINT NOT NULL DEFAULT 60 COMMENT 最大容量, current_enrolled SMALLINT NOT NULL DEFAULT 0 COMMENT 当前已选人数用于选课时实时校验, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX idx_teacher_id (teacher_id), CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES user (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;current_enrolled字段非冗余避免每次选课前执行SELECT COUNT(*) FROM student_course WHERE course_id?直接读取该字段并配合UPDATE ... SET current_enrolled current_enrolled 1 WHERE id? AND current_enrolled max_capacity实现原子性校验teacher_id显式外键而非字符串存储教师姓名确保数据一致性教师改名时只需更新user表无需遍历course表utf8mb4字符集支持 emoji虽本系统不用但为未来扩展留余地。2.3 Servlet 层用标准生命周期管理请求与响应以LoginServlet为例其doPost()方法完整体现原生 Web 开发核心流程protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username).trim(); String password request.getParameter(password); // 1. 参数校验空值、长度 if (username.isEmpty() || password.length() 6) { request.setAttribute(error, 用户名或密码格式错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 2. JDBC 查询使用预编译防止SQL注入 String sql SELECT id, username, role FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); // 实际应为BCrypt加密比对此处简化 ResultSet rs ps.executeQuery(); if (rs.next()) { // 3. 登录成功写入Session并重定向 HttpSession session request.getSession(true); session.setAttribute(userId, rs.getInt(id)); session.setAttribute(username, rs.getString(username)); session.setAttribute(userRole, rs.getString(role)); // 关键角色存于Session // 4. 角色路由不同角色跳转不同首页 String redirectPath / rs.getString(role) /dashboard.jsp; response.sendRedirect(request.getContextPath() redirectPath); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } catch (SQLException e) { e.printStackTrace(); request.setAttribute(error, 系统繁忙请稍后重试); request.getRequestDispatcher(/login.jsp).forward(request, response); } }关键参数说明request.getSession(true)true表示若 Session 不存在则创建新 Session这是维持登录态的基础response.sendRedirect(...)服务端重定向使浏览器 URL 变为/student/dashboard.jsp符合 RESTful 路径语义request.getRequestDispatcher(...).forward(...)服务器内部转发URL 不变用于展示错误页避免重复提交。注意DBUtil.getConnection()是封装好的静态方法内部使用DriverManager.getConnection(jdbc:mysql://localhost:3306/course_db?useSSLfalseserverTimezoneGMT%2B8, root, 123456)其中serverTimezoneGMT%2B8解决 MySQL 8.0 时区报错这是本地运行“绝对可用”的硬性配置项。3. 多角色登录实现从 Session 鉴权到页面动态渲染的全链路3.1 登录态管理用 HttpSession 存储角色并拦截非法访问系统不依赖容器级安全约束如web.xml中的security-constraint而是采用代码级细粒度控制。每个角色专属目录/student/,/teacher/,/admin/下首页面均包含统一鉴权逻辑% page importjava.util.* % % // 所有角色页面顶部强制校验 HttpSession session request.getSession(false); String userRole (String) session.getAttribute(userRole); Integer userId (Integer) session.getAttribute(userId); if (session null || userRole null || userId null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 角色路径校验防止学生直接访问 /admin/dashboard.jsp String currentPath request.getServletPath(); if (currentPath.startsWith(/student/) !student.equals(userRole)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 权限不足); return; } if (currentPath.startsWith(/teacher/) !teacher.equals(userRole)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 权限不足); return; } if (currentPath.startsWith(/admin/) !admin.equals(userRole)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 权限不足); return; } %此段 JSP 脚本在每次页面加载时执行return语句立即终止后续渲染response.sendError()返回 403 状态码比简单重定向更符合 HTTP 语义request.getServletPath()获取当前请求路径如/student/select.jsp与userRole字符串比对实现“路径前缀即权限”的轻量模型。3.2 动态菜单渲染JSP EL 表达式驱动角色专属界面/student/dashboard.jsp中的导航栏根据角色动态生成nav classnavbar a href${pageContext.request.contextPath}/student/dashboard.jsp首页/a a href${pageContext.request.contextPath}/student/courseList.jsp课程列表/a a href${pageContext.request.contextPath}/student/myCourses.jsp我的课表/a a href${pageContext.request.contextPath}/student/gradeReport.jsp成绩查询/a a href${pageContext.request.contextPath}/logout.jsp退出登录/a /nav !-- 教师角色专属入口仅对teacher显示 -- c:if test${sessionScope.userRole teacher} div classteacher-tools a href${pageContext.request.contextPath}/teacher/courseManage.jsp开课管理/a a href${pageContext.request.contextPath}/teacher/gradeInput.jsp录入成绩/a /div /c:if !-- 管理员专属入口仅对admin显示 -- c:if test${sessionScope.userRole admin} div classadmin-tools a href${pageContext.request.contextPath}/admin/userManage.jsp用户管理/a a href${pageContext.request.contextPath}/admin/courseAudit.jsp课程审核/a a href${pageContext.request.contextPath}/admin/statistics.jsp选课统计/a /div /c:if${sessionScope.userRole}直接读取 Session 属性无需 Java 代码块简洁且线程安全c:if标签来自 JSTL需在web.xml中声明taglib或 Maven 引入jstl依赖比% if(...) { %更易维护所有链接使用${pageContext.request.contextPath}动态获取应用上下文路径避免硬编码/course_system导致迁移失败。3.3 角色数据隔离同一 Servlet 复用不同 SQL 条件过滤以CourseListServlet为例它同时服务于学生选课和教师开课两个场景通过userRole参数动态切换查询逻辑protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String role (String) request.getSession().getAttribute(userRole); ListCourse courseList new ArrayList(); String sql; if (student.equals(role)) { // 学生查所有未满额、未被自己选过的课 sql SELECT c.* FROM course c WHERE c.current_enrolled c.max_capacity AND c.id NOT IN (SELECT course_id FROM student_course WHERE student_id ?); } else if (teacher.equals(role)) { // 教师查自己开设的课 sql SELECT c.* FROM course c WHERE c.teacher_id ?; } else { // 管理员查全部课 sql SELECT c.* FROM course c; } try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { if (student.equals(role) || teacher.equals(role)) { ps.setInt(1, (Integer) request.getSession().getAttribute(userId)); } // ... 执行查询并封装 Course 对象 } }同一 Servlet 类复用避免StudentCourseListServlet和TeacherCourseListServlet的重复代码PreparedStatement的setInt(1, ...)位置参数与 SQL 中?顺序严格对应防止因角色分支导致参数错位学生查询中NOT IN子查询需注意若student_course表为空NOT IN (NULL)返回空结果集故实际代码中会先判断子查询是否返回 NULL 并改用LEFT JOIN ... IS NULL这是 MySQL 兼容性关键点。4. 本地运行验证从 MySQL 初始化到 Tomcat 部署的零失败步骤4.1 MySQL 数据库初始化执行建库脚本并验证表结构启动 MySQL 服务确认端口 3306 可用使用命令行登录mysql -u root -p输入密码后执行CREATE DATABASE IF NOT EXISTS course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_db; -- 执行建表语句见 2.2 节 DDL -- 插入测试数据 INSERT INTO user (username, password, role) VALUES (stu001, 123456, student), (tea001, 123456, teacher), (admin001, 123456, admin); INSERT INTO course (name, credit, teacher_id, max_capacity) VALUES (Java程序设计, 3, 2, 50), (数据库原理, 2, 2, 40);验证表结构SHOW TABLES; -- 应返回 user, course, student_course, grade, admin_log DESCRIBE course; -- 检查字段类型与索引 SELECT * FROM user LIMIT 3; -- 确认测试数据存在提示若遇到ERROR 1067 (42000): Invalid default value for create_time说明 MySQL 5.7 严格模式开启需执行SET GLOBAL sql_mode(SELECT REPLACE(sql_mode,NO_ZERO_DATE,,));临时关闭或在 JDBC URL 中添加zeroDateTimeBehaviorconvertToNull。4.2 Tomcat 部署 WAR 包配置 server.xml 与 context.xml将项目导出为 WAR 包Eclipse 右键项目 → Export → WAR file命名为course_system.war复制到 Tomcat 的webapps/目录下修改conf/server.xml确保 Connector 端口未被占用Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /在conf/context.xml中添加数据库连接池配置替代硬编码 JDBCResource namejdbc/CourseDB authContainer typejavax.sql.DataSource maxTotal20 maxIdle10 minIdle5 initialSize5 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue usernameroot password123456 driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/course_db?useSSLfalseamp;serverTimezoneGMT%2B8amp;allowPublicKeyRetrievaltrue /修改WEB-INF/web.xml在web-app标签下添加资源引用resource-ref descriptionDB Connection/description res-ref-namejdbc/CourseDB/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-ref重启 Tomcat访问http://localhost:8080/course_system/login.jsp。4.3 关键验证点与常见报错定位现象可能原因快速定位命令页面 404WAR 包未解压或 Context Path 错误ls $TOMCAT_HOME/webapps/查看是否存在course_system/目录登录报ClassNotFoundException: com.mysql.cj.jdbc.DriverMySQL Connector/J JAR 未放入WEB-INF/lib/ls WEB-INF/lib/选课时报java.sql.SQLException: Field current_enrolled doesnt have a default valueMySQL 严格模式下 NOT NULL 字段未设 DEFAULTSELECT sql_mode;检查是否含STRICT_TRANS_TABLES成绩录入后页面空白JSP 中c:forEach循环未引入 JSTL 标签库检查WEB-INF/lib/是否有jstl-1.2.jar和standard-1.1.2.jar中文乱码如Java程序设计显示为???JDBC URL 缺少characterEncodingutf8修改context.xml中 URL 为...characterEncodingutf85. 进阶技巧用 JSP Fragment 实现页面复用与 AJAX 局部刷新5.1 JSP Fragment抽取公共头部与底部避免复制粘贴创建WEB-INF/jspf/header.jspf注意扩展名.jspf表示 JSP Fragment% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 titlec:out value${pageTitle} default学生选课系统//title link relstylesheet href${pageContext.request.contextPath}/css/style.css /head body header h1学生选课管理系统/h1 p欢迎 strongc:out value${sessionScope.username}//strong | a href${pageContext.request.contextPath}/logout.jsp退出/a /p /header main在student/dashboard.jsp中复用% include file/WEB-INF/jspf/header.jspf % c:set varpageTitle value学生首页 scoperequest/ !-- 学生专属内容 -- div classwelcome你好${sessionScope.username}/div !-- ... -- /main % include file/WEB-INF/jspf/footer.jspf %.jspf文件不会被 Tomcat 直接访问只能通过% include %包含提升安全性c:set在request作用域设置pageTitle供header.jspf中${pageTitle}读取实现标题动态化。5.2 AJAX 局部刷新用原生 JavaScript 替代整页跳转在student/select.jsp中选课按钮不提交表单而用 AJAX 提交button onclickselectCourse(${course.id})选课/button script function selectCourse(courseId) { const xhr new XMLHttpRequest(); xhr.open(POST, ${pageContext.request.contextPath}/SelectCourseServlet, true); xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded); xhr.onreadystatechange function() { if (xhr.readyState 4) { if (xhr.status 200) { const result JSON.parse(xhr.responseText); if (result.success) { alert(选课成功); location.reload(); // 刷新当前页更新课表 } else { alert(选课失败 result.message); } } } }; xhr.send(courseId courseId); } /scriptSelectCourseServlet的doPost()返回 JSONresponse.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); MapString, Object result new HashMap(); try { // 执行选课逻辑检查容量、插入 student_course、更新 current_enrolled result.put(success, true); result.put(message, 选课成功); } catch (Exception e) { result.put(success, false); result.put(message, e.getMessage()); } out.print(new Gson().toJson(result)); // 需引入 gson-2.8.9.jarXMLHttpRequest是原生方案不依赖 jQuery符合“最小依赖”原则location.reload()比window.location.href ...更高效避免重复加载 CSS/JSGson序列化确保中文不乱码response.setContentType显式声明 UTF-8。5.3 MySQL 性能优化为高频查询添加复合索引针对学生选课时的并发查询为student_course表添加复合索引-- 加速 SELECT * FROM student_course WHERE student_id ? AND course_id ? CREATE INDEX idx_student_course ON student_course (student_id, course_id); -- 加速 SELECT COUNT(*) FROM student_course WHERE course_id ? CREATE INDEX idx_course_enroll ON student_course (course_id);执行后验证索引生效EXPLAIN SELECT * FROM student_course WHERE student_id 1 AND course_id 5; -- 输出中 key 列应显示 idx_student_coursetype 为 refidx_student_course覆盖查询条件避免全表扫描idx_course_enroll专为COUNT(*)优化InnoDB 可直接从索引 B 树叶子节点统计行数无需回表。本文还有配套的精品资源点击获取
RELATED

相关推荐

SSM就业平台:岗位智能匹配与MySQL索引优化实战

SSM就业平台:岗位智能匹配与MySQL索引优化实战

简介:本资源是一套完整的毕业设计级学生就业服务平台实现方案,面向计算机专业本科生、Java初学者及SSM框架学习者,解决高校学生求职与企业招聘信息不对称、流程管理低效等实际问题。压缩包含867个文件,总计52.83MB,涵盖…

📅 2026/9/12 22:03:41
CookLikeHOC 复刻指南:剁椒鱼头(草鱼头版)——配料标准、蒸制工艺与营养成分全解析

CookLikeHOC 复刻指南:剁椒鱼头(草鱼头版)——配料标准、蒸制工艺与营养成分全解析

CookLikeHOC 复刻指南:剁椒鱼头(草鱼头版)——配料标准、蒸制工艺与营养成分全解析 【免费下载链接】CookLikeHOC 🥢像老乡鸡🐔那样做饭。已添加2026年发布的《老乡鸡菜品溯源报告 2.0中新出现的菜品。主要部分于2024年…

📅 2026/9/12 22:03:41
锂离子电池Simulink建模与配置优化指南

锂离子电池Simulink建模与配置优化指南

1. 锂离子电池模型与Simulink仿真概述锂离子电池作为当前储能领域的核心技术,其性能优化一直是工程师关注的重点。在电动汽车、储能系统等实际应用中,电池组的配置方案直接影响着整体系统的能量密度、循环寿命和安全性能。Simulink作为MATLAB中的模块化仿…

📅 2026/9/12 22:03:41
MORE NEWS

更多资讯

📰

ECharts文件引入方式与性能优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

AI模型部署与管理实战:从ONNX转换到FastAPI服务

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

消费电子ESD三级拦截法:从结构到器件的全链路防护

1. 项目概述:一场发生在耳机产线上的静电歼灭战 “静电打到死机、RGB灯永久烧毁”——这不是故障报告里的修辞,而是我们去年在某款旗舰游戏耳机量产爬坡阶段,每天早上开晨会时必听的“噩耗播报”。这款耳机主打低延迟无线双模全息空间音效可编…

📰

酒店数字化安全:从系统架构到隐私防护实战

1. 酒店住宿的数字化痛点解析每次假期出行订酒店时,最让人抓狂的莫过于前台系统突然卡死,或是发现房门电子锁失灵。去年我在三亚某五星级酒店就遭遇过这样的尴尬——排队半小时后被告知"系统正在升级",而隔壁房间的智能音箱竟能响应…

📰

五款主流抓包工具对比:Charles、Wireshark、Fiddler、Proxyman选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

维普标红论文案例分析整章:BunnyScholar批量改写时如何保留事实与引文

维普标红论文案例分析整章:BunnyScholar批量改写时如何保留事实与引文 在公共管理、工商管理(MBA/EMBA)、社会学以及法学等专业的研究生论文中,“案例分析”章节往往占据了全篇篇幅的三分之一以上(通常在 10,000 到 2…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬