高校选课系统教学实践:JSP+Servlet+JavaBean全栈开发指南 简介本资源是一份完整的高校数据库课程设计实践项目面向计算机专业本科生及Web开发初学者聚焦JSPServletJavaBean三层架构的选课系统开发全流程。项目覆盖管理员、教师、学生三类角色全部核心功能包含E-R图、实体关系模型、详细说明文档及完整可运行代码适用于课程设计答辩、毕业设计参考或Java Web技术栈综合实训。压缩包共237个文件5.95MB含35个Java业务逻辑类、18个JSP页面、66个JS交互脚本、27个CSS样式文件及22个PNG/JPG界面素材结构清晰便于模块化学习与二次开发。已有1052人学习下载提供Tomcat 8.5部署方案、SQL Server 2017建库脚本、Eclipse 2019-09工程配置说明以及Ace前端UI框架集成细节如ace.min.css、bootstrap.min.css等开箱即用显著降低环境搭建与调试门槛。1. 高校选课系统为什么非得用 JSP Servlet JavaBean 这套“老架构”——不是怀旧是教学闭环的刚性需求你打开一份高校《数据库设计》课程设计任务书看到“基于 JSP Servlet JavaBean Tomcat 实现选课管理系统”第一反应可能是这都 2024 年了Spring Boot 都卷到自动装配源码级了怎么还在写% request.getAttribute(msg) %但真相是这不是技术倒退而是教学场景下最扎实的“能力锚点”。这套组合拳不追求高并发、不拼云原生它强制你亲手画 E-R 图、手写建表语句、手动拼接 SQL、在web.xml里配servlet-mapping、在doPost()里解析request.getParameter()、把Student对象从 DAO 层一层层托举到 JSP 页面——每一步都暴露逻辑断点每一处都可打断点调试。学生交作业时老师能一眼看出E-R 图里“课程-教师”是 1:N 还是 M:NServlet 是否做了空值校验JavaBean 的setCourseId()是不是漏了Integer.parseInt()Tomcat 日志里有没有ClassNotFoundException: com.mysql.cj.jdbc.Driver这种“全链路裸奔式开发”恰恰是数据库设计能力落地的唯一验证路径。它不教你怎么用 MyBatis 自动生成 mapper它教你为什么需要 mapper——因为手写 JDBC 太容易出错。如果你正被课程设计卡在“数据库连不上”或“JSP 页面显示 null”别急着换框架先把这个栈的每个环节钉死。2. 从零搭起选课系统骨架Tomcat JDK MySQL 环境三件套实操指南2.1 Tomcat 安装与 JDK 版本强绑定为什么必须用 JDK 8而非 JDK 21很多同学在 IDEA 里配 Tomcat 时卡在“Application Server not configured”根源常是 JDK 版本错配。Tomcat 8.5课程设计最常用版本官方明确要求JDK 1.8但实际运行中若用 JDK 17 或 JDK 21会触发java.lang.UnsupportedClassVersionError—— 因为 Tomcat 8.5 编译目标字节码版本是 52对应 JDK 8而 JDK 17 编译出的是 61。更隐蔽的坑是JDK 21 的javax.servlet.*包已被移除而 JSP/Servlet 规范依赖它。正确做法下载 Adoptium Temurin JDK 8u392 LTS 版本兼容性最稳设置JAVA_HOME指向 JDK 8 根目录如C:\Program Files\Eclipse Adoptium\jdk-8.0.392.9-hotspot在 Tomcatbin\setenv.batWindows或setenv.shLinux中显式声明echo off set JAVA_HOMEC:\Program Files\Eclipse Adoptium\jdk-8.0.392.9-hotspot set JRE_HOME%JAVA_HOME%提示不要依赖系统默认 JDK。IDEA 中需在File → Project Structure → Project和Project Settings → Modules两级都设为 JDK 8Tomcat 配置页里的 JRE 也必须单独指定否则 IDEA 会偷偷用项目 SDK 覆盖。2.2 MySQL 数据库初始化按选课业务建模的 5 张核心表及字段约束选课系统不是 CRUD 练习它的表结构直指数据库设计核心能力——关系识别、范式控制、约束落地。我们按真实教学场景设计以下 5 张表含外键、非空、唯一等约束表名字段说明关键约束设计意图studentstu_id(PK, CHAR(10)),name(NOT NULL),major,gradestu_id主键name非空学生实体学号定长便于对齐teachertea_id(PK, CHAR(8)),name,depttea_id主键dept默认 计算机系教师实体院系可为空但有默认值coursecourse_id(PK, VARCHAR(10)),course_name,credit,tea_id(FK)course_id主键creditCHECK(credit BETWEEN 1 AND 8)课程实体学分必须 1~8外键关联教师select_coursestu_id(FK),course_id(FK),score(TINYINT),select_time联合主键(stu_id,course_id)scoreCHECK(score BETWEEN 0 AND 100)选课关系表成绩范围校验时间戳记录选课时刻adminadmin_id(PK),username,passwordusernameUNIQUEpassword加密存储MD5管理员表密码绝不明文建表 SQLMySQL 5.7-- 创建数据库并设字符集 CREATE DATABASE course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_db; -- 学生表 CREATE TABLE student ( stu_id CHAR(10) PRIMARY KEY, name VARCHAR(20) NOT NULL, major VARCHAR(30), grade INT ); -- 教师表 CREATE TABLE teacher ( tea_id CHAR(8) PRIMARY KEY, name VARCHAR(20), dept VARCHAR(30) DEFAULT 计算机系 ); -- 课程表外键指向教师 CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY, course_name VARCHAR(50) NOT NULL, credit TINYINT CHECK (credit BETWEEN 1 AND 8), tea_id CHAR(8), FOREIGN KEY (tea_id) REFERENCES teacher(tea_id) ON DELETE SET NULL ); -- 选课关系表联合主键 成绩校验 CREATE TABLE select_course ( stu_id CHAR(10), course_id VARCHAR(10), score TINYINT CHECK (score BETWEEN 0 AND 100), select_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (stu_id, course_id), FOREIGN KEY (stu_id) REFERENCES student(stu_id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ); -- 管理员表密码 MD5 加密 CREATE TABLE admin ( admin_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) UNIQUE NOT NULL, password VARCHAR(32) NOT NULL -- 存储 MD5 值 );注意ON DELETE CASCADE和ON DELETE SET NULL不是炫技而是体现数据库设计中的“业务一致性”意识——删教师时课程教师字段置空保留课程记录删学生则自动清空其所有选课记录避免孤儿数据。这些约束必须在建表时写死而不是靠 Java 代码补救。2.3 Tomcat 启动验证与 WAR 包部署从localhost:8080到http://localhost:8080/course的关键跳转环境配好后别急着写代码先验证 Tomcat 是否真跑通进入tomcat/bin目录双击startup.batWindows或执行./startup.shLinux观察控制台输出出现INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds即成功浏览器访问http://localhost:8080看到 Apache Tomcat 欢迎页注意不是 404部署你的选课系统 WAR 包将项目导出为 WARIDEAFile → Project Structure → Artifacts → → Web Application: Archive把生成的course.war文件复制到tomcat/webapps/目录下Tomcat 会自动解压成course/文件夹无需手动解压访问http://localhost:8080/course—— 此时若报 40490% 是web.xml配置错误或index.jsp路径不对关键检查点webapps/course/WEB-INF/web.xml必须存在且welcome-file-list指向index.jspwebapps/course/index.jsp必须在根目录不是webapps/course/pages/index.jsp若启动时报SEVERE: Error listenerStart大概率是web.xml里 servlet-class 类名写错如com.servlet.LoginServlet写成com.servlet.loginServletJava 区分大小写3. JavaBean Servlet 分层实现把数据库操作从 JSP 里彻底剥离3.1 JavaBean 设计原则不是 POJO而是“可序列化、有业务语义”的数据载体很多同学把 JavaBean 当作简单的 getter/setter 容器这是致命误区。在选课系统中JavaBean 必须承载业务规则和状态合法性。例如Student类不能只存字段还要封装校验逻辑// src/com/entity/Student.java package com.entity; import java.io.Serializable; public class Student implements Serializable { private static final long serialVersionUID 1L; // 防止序列化版本冲突 private String stu_id; private String name; private String major; private Integer grade; // 构造方法强制校验学号格式10位数字 public Student(String stu_id, String name) { if (stu_id null || !stu_id.matches(\\d{10})) { throw new IllegalArgumentException(学号必须为10位数字); } this.stu_id stu_id; this.name name null ? : name.trim(); } // getter/setterIDEA 自动生成即可 public String getStu_id() { return stu_id; } public void setStu_id(String stu_id) { this.stu_id stu_id; } public String getName() { return name; } public void setName(String name) { this.name name; } public String getMajor() { return major; } public void setMajor(String major) { this.major major; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade grade; } }关键点Serializable接口不是摆设——当 Student 对象存入 session如session.setAttribute(user, student)时Tomcat 需要序列化它serialVersionUID避免类升级后反序列化失败构造函数内嵌校验比在 Servlet 里if (stu_id.length()!10)更早拦截错误。3.2 DAO 层手写 JDBC为什么不用 Hibernate因为你要亲手看见 SQL课程设计禁用 ORM目的就是逼你写原生 JDBC。DAO 层是数据库与业务逻辑的唯一通道必须严格遵循“一个方法一个 SQL”原则// src/com/dao/StudentDAO.java package com.dao; import com.entity.Student; import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDAO { private static final String URL jdbc:mysql://localhost:3306/course_db?useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; // 生产环境必须加密 // 查询所有学生演示 ResultSet 映射 public ListStudent findAll() throws SQLException { ListStudent list new ArrayList(); String sql SELECT stu_id, name, major, grade FROM student ORDER BY stu_id; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student( rs.getString(stu_id), rs.getString(name) ); s.setMajor(rs.getString(major)); s.setGrade(rs.getInt(grade)); list.add(s); } } return list; } // 插入学生演示参数化防止 SQL 注入 public boolean insert(Student student) throws SQLException { String sql INSERT INTO student (stu_id, name, major, grade) VALUES (?, ?, ?, ?); try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, student.getStu_id()); ps.setString(2, student.getName()); ps.setString(3, student.getMajor()); ps.setInt(4, student.getGrade() ! null ? student.getGrade() : 0); return ps.executeUpdate() 0; } } }注意PreparedStatement的?占位符是防 SQL 注入的唯一防线try-with-resources自动关闭连接避免Connection leakserverTimezoneAsia/Shanghai解决 MySQL 8.0 时区报错密码明文仅限本地测试课程设计验收后必须改成配置文件读取。3.3 Servlet 控制器HTTP 请求到业务逻辑的精准路由Servlet 不是“写个 doPost 就完事”它是 HTTP 协议与 Java 业务的翻译官。以登录为例LoginServlet必须处理三种请求分支// src/com/servlet/LoginServlet.java package com.servlet; import com.dao.AdminDAO; import com.entity.Admin; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 1. 基础校验空值、长度 if (username null || username.trim().isEmpty() || password null || password.length() 6) { request.setAttribute(msg, 用户名或密码格式错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 2. 密码 MD5 加密与数据库存储一致 String md5Pass md5(password); // 3. 查询管理员DAO 层 AdminDAO dao new AdminDAO(); Admin admin dao.findByUsername(username); // 4. 业务判断用户存在且密码匹配 if (admin ! null admin.getPassword().equals(md5Pass)) { HttpSession session request.getSession(); session.setAttribute(admin, admin); // 登录态存入 session response.sendRedirect(request.getContextPath() /admin/main.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } // MD5 工具方法课程设计允许直接写不必引入 Apache Commons Codec private String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes()); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } }关键细节response.sendRedirect()用于登录成功后的页面跳转避免重复提交request.getRequestDispatcher().forward()用于登录失败时回传错误信息到login.jspsession.setAttribute()是维持登录态的核心md5()方法必须与数据库插入时的加密方式完全一致否则永远登不上。4. JSP 页面开发避坑指南从“页面空白”到“数据精准渲染”的 5 个血泪经验4.1 JSP 页面加载后刷新一次不是玄学是解决表单重复提交的务实方案课程设计常见需求“学生提交选课后页面自动刷新显示最新已选课程列表”。很多同学用 JavaScriptwindow.location.reload()结果导致表单重复提交F5 刷新时重发 POST。正确解法是 PRG 模式Post-Redirect-GetServlet 处理完选课逻辑后用response.sendRedirect(select_list.jsp)跳转select_list.jsp用GET请求加载数据此时刷新页面只是重新 GET不会重复选课!-- select_list.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle我的选课/title/head body h2已选课程/h2 c:choose c:when test${empty requestScope.courseList} p暂无选课记录/p /c:when c:otherwise table border1 trth课程编号/thth课程名称/thth学分/thth教师/thth成绩/th/tr c:forEach items${requestScope.courseList} varc tr td${c.course_id}/td td${c.course_name}/td td${c.credit}/td td${c.teacher_name}/td td${c.score null ? 未录入 : c.score}/td /tr /c:forEach /table /c:otherwise /c:choose /body /html注意${requestScope.courseList}显式指定作用域避免 EL 表达式因作用域混乱取不到值c:forEach循环前必须用% taglib %引入 JSTLc:choose替代 Java 脚本% if(...) %更安全易读。4.2 JSP 图片坐标定位不是 CSS 万能而是 HTML 表单控件的像素级对齐“JSP 页面让图片对坐标定位”常被误解为 CSS 定位实则是表单元素与图片的视觉对齐问题。例如选课系统首页需在“课程列表”旁放一张“选课指南”图片要求图片右上角紧贴表格右边界!-- index.jsp 片段 -- div styleposition: relative; width: 100%; table stylefloat: left; margin-right: 20px; !-- 课程表格内容 -- /table img srcimages/guide.png styleposition: absolute; top: 0; right: 0; width: 150px; alt选课指南/ /div关键position: relative为父容器建立定位上下文position: absolute让图片脱离文档流top: 0; right: 0锚定右上角width: 150px强制缩放避免大图撑破布局。纯 CSS 方案比img alignright更可控且兼容所有浏览器。4.3 JSP 个人信息展示页面EL 表达式失效的 3 个排查路径学生登录后进入profile.jsp期望显示欢迎张三却只看到空白或${student.name}原样输出。这不是 JSP 语法错而是环境链断裂现象原因解决方案页面显示${student.name}字面量web.xml 中缺失jsp-config或 JSP 版本过低在web.xml中添加jsp-configjsp-property-groupurl-pattern*.jsp/url-patternel-ignoredfalse/el-ignored/jsp-property-group/jsp-config页面显示nullServlet 中未将 Student 对象存入 request/session检查request.setAttribute(student, student)是否执行且forward()前调用页面部分字段为空如student.major为空数据库该字段为 NULL而 JavaBean 未处理 null 安全在 JSP 中用c:out value${student.major} default未填写/或 JavaBean 的 getter 返回空字符串血泪经验EL 表达式失效 80% 是web.xml配置缺失不是代码错。Tomcat 8.5 默认支持 EL但老式web.xmlDTD 声明如!DOCTYPE web-app PUBLIC -//Sun Microsystems...可能禁用 EL改用web.xml3.1 schema 声明可根治。4.4 Tomcat 后台上传 WAR 被限制 IP不是安全策略而是 manager-gui 的默认访问控制课程设计需用 Tomcat Manager 上传 WAR但访问http://localhost:8080/manager/html时提示 403。这不是端口错而是 Tomcat 8.5 默认禁止远程 IP 访问 Manager解决步骤编辑tomcat/conf/tomcat-users.xml添加角色和用户role rolenamemanager-gui/ role rolenamemanager-script/ user usernameadmin password123456 rolesmanager-gui,manager-script/编辑tomcat/webapps/manager/META-INF/context.xml注释掉 IP 过滤段!-- Valve classNameorg.apache.catalina.valves.RemoteAddrValve allow127\.\d\.\d\.\d|::1|0:0:0:0:0:0:0:1 / --重启 Tomcat用admin/123456登录 Manager 页面上传 WAR注意生产环境绝不可注释 IP 限制课程设计本地开发时才可临时放开。manager-script角色支持 CURL 上传比网页更稳定。5. 数据库设计验证用 3 个真实业务场景检验你的 E-R 图是否“活”着5.1 场景一学生跨专业选课——检验外键约束与 NULL 处理业务规则计算机系学生可选“数学分析”数学系开课此时course.tea_id应允许为 NULL因数学系教师不在teacher表中。验证动作手动执行INSERT INTO course (course_id, course_name, credit, tea_id) VALUES (MA101, 数学分析, 4, NULL);若报错Cannot add or update a child row: a foreign key constraint fails说明tea_id外键未设ON DELETE SET NULL或字段不允许 NULL修正后再插入一条select_course记录确认stu_id和course_id联合主键能唯一标识选课行为5.2 场景二教师离职后课程归属——检验级联删除与数据完整性业务规则张教授退休其开设的“数据结构”课程由李教授接手原课程记录保留仅更新tea_id。验证动作执行UPDATE course SET tea_idT002 WHERE course_idCS201;查看select_course表中该课程的选课记录是否完整保留不应被删若误设ON DELETE CASCADE会导致学生选课记录丢失——这是范式设计失误的铁证5.3 场景三期末成绩批量导入——检验 CHECK 约束与事务原子性业务规则教务员上传 Excel 成绩需保证每门课成绩在 0~100 之间且同一学生同一课程只能有一条成绩记录。验证动作写一个批量更新 Servlet用PreparedStatement批量执行String sql INSERT INTO select_course (stu_id, course_id, score) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score); // 使用 ON DUPLICATE KEY UPDATE 处理重复主键联合主键故意插入score105观察是否触发CHECK约束报错Data truncation: Data too long for column score若未报错说明建表时CHECK未生效MySQL 5.7 默认忽略 CHECK需升级到 8.0 或用触发器替代这些场景不是考试题而是数据库设计是否“活”着的试金石。E-R 图画得再漂亮挡不住一条INSERT就崩的现实。我带过 12 届课程设计学生交稿前必做这三步验证——不是为了拿高分是确保答辩时老师问“如果教师离职数据怎么保证”你能指着ON DELETE SET NULL的 SQL 说“老师这里已经处理了。”6. 最后一道关卡Tomcat 日志里藏了所有答案学会读它比写代码更重要课程设计最后 24 小时你卡在“点击登录按钮没反应”浏览器控制台干净页面静默。别猜直接翻tomcat/logs/catalina.out—— 这里才是真相发生地。我整理了 5 类高频日志线索按出现频率排序日志关键词典型位置代表问题速查命令LinuxClassNotFoundException: com.mysql.cj.jdbc.Drivercatalina.out 开头MySQL 驱动 JAR 未放入WEB-INF/lib/ls webapps/course/WEB-INF/lib/ | grep mysqlSEVERE: Servlet.service() for servlet [LoginServlet] threw exceptioncatalina.out 中段Servlet 代码抛异常如空指针、SQL 语法错grep -A 10 LoginServlet catalina.outCaused by: java.sql.SQLException: Access denied for user rootlocalhostcatalina.out 尾部数据库账号密码错或权限不足mysql -u root -p -e SHOW GRANTS FOR rootlocalhost;WARNING: An illegal reflective access operation has occurredcatalina.out 中间JDK 版本与 Tomcat 不兼容如 JDK 17 Tomcat 8.5java -version cat VERSION.txtINFO: Server startup in [xxx] millisecondscatalina.out 结尾Tomcat 启动成功问题在应用层tail -n 50 catalina.out | grep -i course实战技巧Windows 用户用notepad打开catalina.outCtrlF 搜Exception或Caused byLinux 用户用tail -f catalina.out实时监控同时在浏览器触发操作日志秒级刷新若日志无报错检查localhost_access_log.*.txt—— 这里记录每次 HTTP 请求看POST /course/login是否 200 还是 404/500我带学生答辩时常问“你昨天遇到的最大问题是什么怎么解决的”答“看日志”者基本都能过。因为日志不撒谎它不关心你写了多漂亮的 JSP只忠实地记录每一行字节的生死。课程设计不是比谁代码多是比谁离真相最近。希望帮到你。本文还有配套的精品资源点击获取