资讯详情

资讯详情

JSP+Servlet+JavaBean开发高校选课管理系统:从建表到并发控制

简介高校选课管理系统是一套面向数据库课程设计的完整项目资源适合计算机专业学生用作实训参考或毕设方案。基于jspservletjavabean技术栈配合Tomcat8.5与SQL Server 2017完整演示了从数据建模到Web交互的开发流程。系统内置完整登录校验管理员、教师、学生三类角色拥有不同操作菜单与权限边界。资源共237个文件以Java源码、JSP页面、JavaScript脚本和CSS样式为主包含22张PNG图片及说明文档压缩包仅5.95MB目录结构清晰便于按模块查找。已有1052人学习下载。内容除E-R图、实体关系模型外还覆盖三类功能管理员可实现教师、学生、课程信息的增删改查教师可开课、录入成绩并查询排名学生可在线选课退选并按学年查分。借助完整源代码和配套说明可快速复现系统、撰写课程设计报告并系统理解权限划分与成绩管理逻辑。1. 高校选课管理系统为什么JSPServletJavaBean这套老技术栈还没过时上学期帮某高校的 A 同学看课程设计题目他拿到“高校选课管理系统JSPServletJavaBeanTomcat”时有点泄气觉得这套技术栈太老想换成 Spring Boot 交差。等真正上手才发现选课系统的业务逻辑远比想象中复杂一个学生选一门课背后要处理容量校验、时间冲突、学分上限、重复选课还要保证多人同时选课时不多录一个人。这些约束即使放到 Spring Boot 里也一样要写只是换了个壳子。这个题目的价值恰恰在于它逼你把 Web 应用的三层骨架亲手搭一遍JSP 负责展示、Servlet 负责接收请求和跳转、JavaBean 负责数据和业务规则、Tomcat 负责把这一切跑起来。任何框架都只是在这套骨架上做封装把底层逻辑吃透了以后看别的框架会快很多。这篇笔记就是按课程设计的完整流程从数据库建模到部署排错把能复现的步骤和踩过的坑一次性写清楚。2. 数据库设计是评分的关键从 ER 图到三张表的完整落地课程设计挂在“数据库设计”名下评分的重心往往不在页面多好看而在表结构是否合理、约束是否到位、数据能不能自圆其说。很多同学一上来就写代码写到选课逻辑才发现表设计缺字段回头改表又得连带改 JavaBean 和页面白白浪费大量时间。先花半天把表和关系定死后面能省两三天。2.1 实体关系梳理学生与课程的多对多关系怎么拆选课系统的核心实体有两类学生和课程。一个学生可以选多门课一门课可以被多个学生选这是典型的多对多关系。数据库里多对多不能直接用两张表表达必须拆出一张中间表——选课表。这个中间表不只是存两个外键还承载着选课时间、成绩、退课状态这些业务字段。我一般先把 ER 关系画在纸上不急着建表学生学号、姓名、密码、院系、年级、已选学分、课程课程号、课程名、学分、任课教师、上课时间、容量、已选人数、选课记录学号、课程号、选课时间、状态。注意“已选学分”放在学生表看起来冗余但选课校验时要频繁查询冗余字段能减少一次连表查询。如果追求第三范式可以把这字段去掉课程设计里在性能和范式之间取平衡是可以写进文档说明的。这里有一个很多同学忽略的点课程表里的“上课时间”不能只存一个字符串“周一 3-4 节”。选课校验需要判断新选课程是否与已选课程时间冲突存纯文本意味着每次都要做字符串解析代码写得非常别扭。比较稳妥的做法是把星期和节次拆成两个整数字段或者用“星期几点位表”单独存。我采用的是后者业务逻辑会更清爽后面会详细说。2.2 建表 SQL选课表为什么要用联合主键以下这套 SQL 是在 MySQL 5.7 上验证过的字符集统一用 utf8mb4避免中文乱码和 emoji 存储问题。直接用 Navicat 或命令行执行即可。CREATE DATABASE IF NOT EXISTS course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE course_selection; -- 学生表 CREATE TABLE student ( sno VARCHAR(20) PRIMARY KEY COMMENT 学号, sname VARCHAR(50) NOT NULL COMMENT 姓名, spassword VARCHAR(64) NOT NULL COMMENT 登录密码, sdept VARCHAR(50) COMMENT 院系, sgrade VARCHAR(10) COMMENT 年级, selected_credits DECIMAL(4,1) DEFAULT 0 COMMENT 已选学分 ) ENGINEInnoDB COMMENT学生表; -- 课程表 CREATE TABLE course ( cno VARCHAR(20) PRIMARY KEY COMMENT 课程号, cname VARCHAR(100) NOT NULL COMMENT 课程名, credit DECIMAL(3,1) NOT NULL COMMENT 学分, teacher VARCHAR(50) COMMENT 任课教师, capacity INT NOT NULL DEFAULT 60 COMMENT 容量, selected_count INT DEFAULT 0 COMMENT 已选人数, weekday TINYINT COMMENT 星期几 1-7, start_section TINYINT COMMENT 开始节次, end_section TINYINT COMMENT 结束节次 ) ENGINEInnoDB COMMENT课程表; -- 选课记录表 CREATE TABLE selection ( sno VARCHAR(20) NOT NULL, cno VARCHAR(20) NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0 COMMENT 0选课中 1已退课, PRIMARY KEY (sno, cno), FOREIGN KEY (sno) REFERENCES student(sno), FOREIGN KEY (cno) REFERENCES course(cno), INDEX idx_cno (cno) ) ENGINEInnoDB COMMENT选课记录表;联合主键 (sno, cno) 是这里最关键的决定。它直接在数据库层面拦住了“同一个学生重复选同一门课”的非法数据应用程序就算逻辑有漏洞数据库也会报错兜底。很多同学习惯加一个自增 id 当主键结果同一个学号课程号能插两条记录靠代码去查重查重逻辑一旦写漏就是严重的重复选课事故。外键约束在课程设计里要有。虽然互联网大厂的线上库为了性能经常禁用外键但这个题目的核心是展示数据库设计能力外键能让评分老师直观看到关系完整性。注意创建外键前提是两张表的关联字段类型完全一致比如 sno 都是 VARCHAR(20)否则建表会报错。2.3 JavaBean 数据模型Student、Course、Selection 类的写法JavaBean 在这个项目里承担两层职责一是作为数据模型对应数据库表的字段二是承载业务逻辑方法。先看数据模型部分类的字段必须与表字段一一对应否则后面 JDBC 取值时容易错位。public class Student { private String sno; private String sname; private String spassword; private String sdept; private String sgrade; private double selectedCredits; public Student() {} public Student(String sno, String sname, String spassword, String sdept, String sgrade, double selectedCredits) { this.sno sno; this.sname sname; this.spassword spassword; this.sdept sdept; this.sgrade sgrade; this.selectedCredits selectedCredits; } // getter 和 setter 方法每个属性都要有 public String getSno() { return sno; } public void setSno(String sno) { this.sno sno; } // 其余 getter/setter 略 }核心规则是类名对应表名属性名对应列名类型对应字段类型。表里 DECIMAL(4,1) 对应 Java 的 doubleTINYINT 对应 int。写完 Student 后Course 和 Selection 照同样的模式复制改字段即可。需要特别强调的是必须保留一个无参构造方法。后面用反射或者某些框架自动实例化 JavaBean 时默认调用无参构造只写了带参构造会导致运行期抛 InstantiationException这类错误课堂里不容易遇到排查起来很绕。2.4 JavaBean 与表的映射注意类型对齐与字段命名课程设计最常见的翻车点不是 SQL 写错而是 Java 字段与数据库字段对不上。比如数据库字段叫 selected_creditsJava 属性写成 selectedCreditsJDBC 取数据时用 rs.getDouble(selected_credits)列名用 SQL 里的下划线写法Java 端则保持驼峰这样映射关系是清晰的。如果你用了某些工具类自动映射才需要设置命名策略。手写 JDBC 的项目里统一把 SQL 列名作为字符串传入 getXxx 方法即可不要图省事用列索引。另一个容易踩的是学分字段。credit 在表里是 DECIMAL(3,1)即最大 99.9 学分JavaBean 用 double 接没问题但算总分时浮点数可能会产出 0.30000000000000004 这种值。处理方式是用 BigDecimal 做金额和学分运算或者算完后用 Math.round 保留一位小数。课程设计阶段用 double 加四舍五入能应付但写进数据库的最终值要保证只有一位小数否则统计页面上会出现刺眼的诡异数字。3. 从零搭建项目JDK、Tomcat、目录结构与 web.xml 配置环境搭建看起来是体力活但版本搭配不对后面全是莫名其妙的报错。这里直接给出我验证过的一组稳定组合照着搭不需要纠结兼容性。如果你已经有环境跳到 3.2 节看目录结构也行。3.1 版本组合JDK 8、Tomcat 8.5、Eclipse 的搭配选这套版本组合的原因是市场存量最大网上搜任何报错都能找到别人走过的记录。如果你用较新的 JDK 17 配 Tomcat 9 也不是不行但课程设计的代码大多是老写法新 JDK 对某些反射操作限制更严没必要给自己加戏。组件推荐版本说明JDK1.8稳定课程设计代码无害兼容Tomcat8.5 或 9.0支持 Servlet 3.0运行 JSP 无压力EclipseEclipse IDE for Enterprise Java 版本自带服务器插件省去手动部署MySQL5.7 或 8.0字符集选 utf8mb4Eclipse 里配置服务器的步骤Window → Preferences → Server → Runtime Environments添加 Tomcat选择安装目录并选中 JRE 1.8。这里有个细节添加 Tomcat 时 Eclipse 会要求选择 JRE如果下拉框是空的说明没配置 JDK 而不是 JRE需要先到 Window → Preferences → Java → Installed JREs 里把 JDK 加进去。有个血泪教训是直接把 Tomcat 的 server.xml 里的端口改了结果 Eclipse 发布项目时用的还是默认 8080怎么都访问不到。改端口要同步修改 Eclipse 中 Server 配置的端口。如果 8080 被什么进程占用不如先查占用再换个冷门端口比如 8088。3.2 项目目录结构与两个必须手写的 XMLEclipse 动态 Web 项目创建后目录结构是固定的。但课程设计里很多同学不知道 WEB-INF 下要放什么乱放一通导致发布后 404。标准结构如下CourseSelectionSystem/ │ ├── src/ # Java 源码目录 │ ├── bean/ # JavaBeanStudent, Course, Selection │ ├── dao/ # 数据访问类可选 │ ├── servlet/ # Servlet 控制器 │ └── util/ # DBUtil 数据库工具类 │ ├── WebContent/ # Web 根目录有的版本叫 webapp │ ├── css/ # 样式文件 │ ├── js/ # 前端脚本 │ ├── jsp/ # JSP 页面目录 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 依赖 jar 包注意必须放在这里 │ ├── index.jsp # 入口页面 │ └── login.jsp # 登录页 │ └── build/ # Eclipse 自动生成WEB-INF 目录有两个作用第一里面的 web.xml 是 Servlet 映射和欢迎页面的配置中心第二lib 下的 jar 包会被 Tomcat 自动加载到 classpath。很多同学把 mysql-connector-java 的 jar 包往项目的 build path 里一丢就完事Eclipse 编译通过但 Tomcat 一启动就报 ClassNotFoundException原因就是 build path 只是编译期可见运行时 Tomcat 只认 WEB-INF/lib。这是整个项目里最典型的翻车点。web.xml 新版本里可以只写根配置Servlet 映射用 WebServlet 注解搞定。但课程设计推荐保留显式映射评分老师看 web.xml 就知道你掌握了 URL 到控制器的映射原理。下面是一个包含欢迎页面和字符编码过滤器的 web.xml?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-nameCourseSelectionSystem/display-name welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list filter filter-nameencodingFilter/filter-name filter-classutil.EncodingFilter/filter-class /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-appwelcome-file 决定访问项目根路径时打开哪个页面。有人把它配成 index.jsp 然后又在 index.jsp 里写重定向多此一举不如直接指向 login.jsp。EncodingFilter 是解决中文乱码的关键后面避坑章节还要细说。3.3 部署到 Tomcat 的两种方式与第一个测试页面在 Eclipse 里部署只需右键项目 → Run As → Run on ServerEclipse 会自动把项目打成 war 或按 exploded directory 发布到临时目录。如果没有用 Eclipse也可以把整个 WebContent 目录复制到 Tomcat 的 webapps 下改名成项目名启动 Tomcat 后用 http://localhost:8080/项目名/ 访问。第一次部署完成后建议不要直接写业务代码先写一个最小测试页面确认环境通。新建一个 hello.jsp内容就一句话% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% !DOCTYPE html html headmeta charsetUTF-8title测试/title/head body 恭喜JSP 可以正常执行。当前时间% new java.util.Date() % /body /html在浏览器能正常显示中文和服务器时间说明 JSP 编译、Tomcat 运行、字符集三条链路都通了。这时候再开始写 DAO 和 Servlet。如果这一步都打不开先检查 Tomcat 是否启动、端口对不对、项目是否成功发布不要急着改代码环境问题排查比代码问题更依赖系统性排查。3.4 数据库连接工具类DBUtil 的封装与测试数据库连接不能每个 Servlet 都重复写一遍 DriverManager封装一个 DBUtil 是 JavaBean 之外所有代码的地基。常见的写法是静态代码块加载驱动提供一个获取连接的静态方法。package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }URL 里的三组参数各有用途。characterEncodingutf8 保证向数据库写入中文时编码一致useSSLfalse 避免 MySQL 8 默认的 SSL 连接告警serverTimezoneAsia/Shanghai 是 MySQL 8 必须加的否则会报 CST 时区错误。驱动类名注意区分版本MySQL 5.7 用 com.mysql.jdbc.DriverMySQL 8 用 com.mysql.cj.jdbc.Driver写错直接报 ClassNotFoundException。关闭资源的顺序也很重要先关 ResultSet再关 Statement最后关 Connection顺序颠倒在高并发下可能报连接泄漏。为了让代码简洁后面的 DAO 示例会直接调用这个工具类。4. 核心业务落地的完整链路Servlet 接收、JavaBean 处理、JSP 展示这一章是整个系统的灵魂。很多同学写完登录就开始卡壳不知道选课请求从点击按钮到数据库更新之间应该穿行哪些类。记住一条主线JSP 页面发起请求 → Servlet 接收参数解析动作 → 调用 JavaBean 的业务方法完成校验和数据库操作 → 把结果存到 request 域 → forward 到结果 JSP 页面渲染页面。4.1 选课 Servlet接收请求和处理逻辑的边界在哪Servlet 不能写业务逻辑。它只做三件事解析请求参数、决定调用哪个 JavaBean 方法、根据返回结果决定跳转哪个页面。下面的选课 Servlet 处理的是“学生点击选课按钮”这个动作。package servlet; import bean.Course; import bean.Selection; import bean.Student; import java.io.IOException; 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 service.CourseService; public class SelectCourseServlet extends HttpServlet { private CourseService courseService new CourseService(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); Student student (Student) session.getAttribute(student); String cno request.getParameter(cno); if (student null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String msg; boolean result courseService.selectCourse(student.getSno(), cno); if (result) { msg 选课成功; } else { msg courseService.getLastError(); } request.setAttribute(msg, msg); request.getRequestDispatcher(/jsp/result.jsp).forward(request, response); } }注意几个边界学生信息从 session 取不从前端传防止有人改学号给别的学生选课这是安全意识的体现。课程序号从请求参数拿因为它是这次操作的业务目标。成功与失败统一用一个 result.jsp 展示消息比分别写 success.jsp 和 fail.jsp 少两个文件。请求转发用 forward不用 sendRedirect因为要带着 request 里的属性到结果页。这段代码输出了“service 层从哪来”的疑问。课程设计的严格分层是Servlet → Service 类业务逻辑→ DAO 类数据库访问。但标题里只写了 JavaBean常见的应对做法是把业务逻辑直接写进名为 CourseService 的 JavaBean它内部调用 JDBC 工具类完成数据访问。这样页面、控制、业务三层清晰又贴着题目的组件要求不算夹带私货。4.2 选课业务规则容量、冲突、重复三项校验的实现选课不是无脑 insert 一条记录先做四件事判断课程是否已选、是否满员、是否时间冲突、学分是否超限。每一项校验失败都要返回给用户一个能看懂的原因。下面这段取自 CourseService 的核心方法。public boolean selectCourse(String sno, String cno) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 检查是否已选 String checkSql SELECT status FROM selection WHERE sno? AND cno?; ps conn.prepareStatement(checkSql); ps.setString(1, sno); ps.setString(2, cno); rs ps.executeQuery(); if (rs.next() rs.getInt(status) 0) { lastError 你已经选过这门课了; return false; } // 2. 检查容量 String capSql SELECT capacity, selected_count FROM course WHERE cno? FOR UPDATE; ps conn.prepareStatement(capSql); ps.setString(1, cno); rs ps.executeQuery(); if (rs.next()) { int capacity rs.getInt(capacity); int selected rs.getInt(selected_count); if (selected capacity) { lastError 该课程已满员; return false; } } else { lastError 课程不存在; return false; } // 3. 查询学生已选课程检查时间冲突和学分 String timeSql SELECT c.credit, c.weekday, c.start_section, c.end_section FROM selection s JOIN course c ON s.cnoc.cno WHERE s.sno? AND s.status0; ps conn.prepareStatement(timeSql); ps.setString(1, sno); rs ps.executeQuery(); double totalCredit 0; while (rs.next()) { totalCredit rs.getDouble(credit); // 时间冲突判断逻辑代码较长缩写为调用方法 if (isTimeConflict(rs, targetWeekday, targetStart, targetEnd)) { lastError 该课程与已选课程时间冲突; return false; } } if (totalCredit targetCredit 25.0) { lastError 本学期学分已超过25分上限; return false; } // 4. 插入选课记录 String insertSql INSERT INTO selection (sno, cno) VALUES (?, ?); ps conn.prepareStatement(insertSql); ps.setString(1, sno); ps.setString(2, cno); ps.executeUpdate(); // 5. 更新课程已选人数 String updateSql UPDATE course SET selected_count selected_count 1 WHERE cno?; ps conn.prepareStatement(updateSql); ps.setString(1, cno); ps.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } lastError 选课失败 e.getMessage(); return false; } finally { DBUtil.close(rs, ps, conn); } }这段代码的核心点在于 SELECT ... FOR UPDATE 锁住了课程的容量行。在两个学生同时选同一门课时数据库会让第二个事务等待第一个事务提交后才继续执行避免把已选人数写超。没有这个锁两个请求同时读到 selected_count59capacity60都可能顺利插入最后变成 61 人选定。这个并发问题在课程设计答辩时被问到的概率极高把这里讲清楚是货真价实的加分点。关于学分上限我调的是 25 分你可以按学校规则改。判断时机是算完当前已选学分再加目标课程学分超过则拒绝。时间冲突的判断要先知道目标课程的时间然后遍历学生已选课程对比 weekday 和节次区间。课程设计是单机版本学生数据量不大这个写法足够。4.3 退课与改选处理选课记录的另一个方向退课逻辑比选课简单但容易遗漏一个点删除选课记录后课程表的 selected_count 也要同步减一。这两个操作同样要放在事务里否则会出现学生退了课但课程已选人数不减的情况。退课之后该课程释放一个容量名额后续学生才能选上。退课的 SQL 逻辑是 UPDATE selection SET status1 WHERE sno? AND cno?注意不是 DELETE因为要保留学生的历史选课记录。然后执行 UPDATE course SET selected_count selected_count - 1 WHERE cno?。这两条用一个事务包住和选课事务结构完全对称。有个细节值得写进设计文档退课后重新选同一门课走 4.2 里的“检查是否已选”时查到 status1 的旧记录不应该拦截。所以判断条件必须是 status0不是“存在即拦截”。很多同学用 SELECT COUNT(*) 判断是否重复选课会把退课记录也算进去导致退课后无法重选这是一类非常隐蔽的业务逻辑错误。4.4 JSP 页面与 Servlet 的交互form 提交和数据显示JSP 页面在这个架构里不做任何逻辑处理只负责两件事往 Servlet 发请求以及展示 Servlet 传过来的数据。选课列表页的核心就是一个表格加一个提交按钮% page importjava.util.List % % page importbean.Course % % page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% form action${pageContext.request.contextPath}/selectCourse methodpost table tr th选择/thth课程号/thth课程名/th th学分/thth教师/thth容量/thth已选/thth时间/th /tr c:forEach varcourse items${courseList} tr tdinput typeradio namecno value${course.cno} required/td td${course.cno}/td td${course.cname}/td td${course.credit}/td td${course.teacher}/td td${course.capacity}/td td${course.selectedCount}/td td周${course.weekday} 第${course.startSection}-${course.endSection}节/td /tr /c:forEach /table button typesubmit提交选课/button /formaction 路径前必须加 ${pageContext.request.contextPath} 拼上项目名。很多学生在本地测试没问题把项目改名部署到服务器上之后表单提交路径全失效就是漏了这个前缀。如果页面里没引入 JSTL可以把 c:forEach 换成 Java 的 for 循环加 out.print代码难看一点但是能跑。JSP 里禁止出现 % page importjava.sql.* % 和 SQL 语句。凡是看到 JSP 里写 JDBC 的课程设计基本都会被评分为“架构混乱”。记住 JSP 的边界渲染 Servlet 放进去的数据不做数据访问。做到这一点项目在结构规范性上就已经超过多数同学。5. 选课系统避坑指南从中文乱码到并发超员的六个翻车点这部分写的是我自己和帮别人排查时遇到的高频问题按现象、原因、解决的格式列出来每一条都值得在开发时提前对应检查。课程设计的时间往往很紧与其出问题后花半天排查不如写代码时直接避开。5.1 项目里的中文全是问号数据库中文也是问号现象运行后 JSP 页面上的中文显示正常但从表单提交到数据库的中文变成“??”。原因三层编码不一致。浏览器提交时的页面编码、Tomcat 接收请求时的解码编码、MySQL 存储时的字符集任何一层断开都会产生乱码。解决先在 MySQL 端把所有相关地方统一成 utf8mb4然后写一个编码过滤器在请求进入 Servlet 之前强制把 request 和 response 的编码设为 UTF-8代码如下package util; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setContentType(text/html; charsetUTF-8); chain.doFilter(request, response); } Override public void init(FilterConfig filterConfig) {} Override public void destroy() {} }注意request.setCharacterEncoding 必须在读取请求参数之前调用。过滤器能保证这一点因为在 Servlet 的 doPost 执行前过滤器已经完成了编码设置。如果不用过滤器就得在每个 Servlet 的第一行手动写这两句漏一个就是一个乱码。web.xml 里把这个 Filter 的 url-pattern 配成 /*对全站生效。5.2 Tomcat 报错 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象Eclipse 里代码没有红叉Run on Server 一启动就报加载驱动失败。原因jar 包只加了 build path没有复制到 WEB-INF/lib 目录。Eclipse 的 build path 是编译期的 classpath运行时 Tomcat 类加载器不认这个路径。解决把 mysql-connector-java-版本号.jar 文件直接复制粘贴到 WebContent/WEB-INF/lib 下Eclipse 会自动识别为 Web App Libraries。或者右键项目 → Build Path → Configure Build Path → Add JARs 添加但最稳妥的还是物理拷贝到 lib 目录。这个坑整个项目周期只踩一次踩完就不会忘。另外检查一下 jar 包里的驱动类名。MySQL 5.x 的 jar 用 com.mysql.jdbc.DriverMySQL 8.x 的 jar 必须用 com.mysql.cj.jdbc.Driver连接 URL 也需要配套改。混用报的错很迷比如“Loading class com.mysql.jdbc.Driver. This is deprecated”这是 jar 版本与驱动名不匹配的提示。5.3 表单提交后 404 或 405现象点击提交按钮页面变成 404 Not Found 或 405 Method Not Allowed。原因两种可能居多。404 是 Servlet 的 URL 映射路径不对表单 action 里的路径和 web.xml 里配的 servlet-mapping 路径不一致405 是表单 methodpost但 Servlet 只重写了 doGet没有重写 doPost。解决Servlet 映射路径建议统一用“/doXxx”这种带功能的命名例如 /selectCourse、/dropCourse避免用 /servlet1、/servlet2 这类毫无语义的路径。表单 action 写成 action${pageContext.request.contextPath}/selectCourse。服务器重启一次再试。如果是 405在 Servlet 里补一个 doPost或让它调 doGet。5.4 浏览器显示旧页面改了代码没生效现象明明改完了 JSP 甚至改了 Java 代码刷新页面还是老样子。原因三层缓存叠加。浏览器缓存了页面Eclipse 的自动构建没开Tomcat 没有重新加载新编译的 class。解决开发阶段先禁用浏览器缓存浏览器开发者工具里勾选 Disable cache。Eclipse 里确认 Project → Build Automatically 是勾选状态。改了 Java 代码后如果 Run on Server 里的模块没有自动重启就手动右键服务器 → Restart。最后再看 Tomcat 的 server.xml 里 Context 的 reloadable 属性是否等于 true开发阶段设 true 可以热加载正式发布前要改回 false。5.5 少数情况选课人数超过课程容量现象容量 60 人的课最终选了 61 甚至 62 人。原因application 中没做并发控制。两个浏览器同时提交选课都先查询 selected_count59都判断小于 60然后都执行插入和更新最终变成 61 人。解决业务方法里用 SELECT ... FOR UPDATE 锁住课程行并在事务内完成查询、校验和更新的完整操作。在单机课程设计场景这个方案已经足够严谨。如果后续想演示更高端的做法可以用乐观锁加版本号字段但 FOR UPDATE 的实现更直观也更容易在答辩时讲清楚。5.6 部署到别的机器上就报数据库连接失败现象在自己电脑上一切正常把项目导出 war 包部署到同学电脑或服务器上登录就报数据库连接错误。原因DBUtil 里的 URL、用户名、密码写的是自己机器上的配置。别人的机器上 MySQL 密码不一样或者数据库没创建。解决把 DBUtil 的数据库连接参数提取到 db.properties 配置文件里部署时只需改配置不用改代码。课程设计阶段至少也要做到代码里不出现真实数据库密码用别人机器能跑的方式说明清楚。如果答辩演示时用自己电脑确保 MySQL 服务已启动、数据库已导入很多人在答辩现场的失败都来自 MySQL 服务没开而不是代码问题。6. 加分技巧给选课系统加连接池、批量导入和统计图表课程设计做完基本功能只能算合格想拿高分或者作为作品集项目展示需要在工程化方面补三个常见缺憾。这三个方向不需要改架构在现有代码上小步扩展即可性价比很高。第一是把 DriverManager 换成数据库连接池。手工获取连接每一次都走 TCP 握手并发请求多了连接开销明显。用 DBCP 或 HikariCP 可以显著优化在 web.xml 里配置数据源或直接用 Java 配置类初始化一个连接池把 DBUtil 的 getConnection 方法改为从池中获取。代码上只是替换一行但“连接池”这个关键词在文档里含金量很高答辩时能讲出为什么不用 DriverManager对工程认知是正向加分。第二是增加批量选课导入功能。课程表如果一门门手动插入确实麻烦做一个文件上传接口让管理员上传 CSV 文件后台解析后批量执行 PreparedStatement 的 addBatch 和 executeBatch。关键点是解析时逐行校验格式有一行出错则跳过该行并记录错误原因最后在结果页面返回成功导入多少条、失败多少条及原因列表。批量导入功能在现实场景中是刚需展示的是从“单条操作”到“批量操作”的思路转变。第三是统计页面的图表可视化。在 JSP 里引入前端图表库从数据库查询各院系的平均学分、选课人数最多的课程排行、课程容量利用率等数据通过 AJAX 请求一个返回 JSON 的 Servlet然后渲染成柱状图或饼图。这个功能不需要额外依赖 Java 库前端图表库直接下载放在 js 目录下就行但视觉冲击力最强是最直观的“这系统做得深”的证据。当初我给 A 同学的项目只加了连接池和 CSV 导入两个功能课程设计文档验收时老师特意问了连接池的实现思路和并发处理细节。这也是技术债问题连接池确实有配置参数要调起初只改默认配置跑起来效果平平后来根据课程设计演示的负载情况调了最大连接数和超时时间演示时才没有出现连接排队的情况。如果你时间足够再把事务的隔离级别和 FOR UPDATE 的行锁机制查一遍写进设计文档的“难点与解决方案”一节这比在页面里堆功能更能体现你对系统的理解深度。课程设计不只是交差把它当做一个能讲清楚原理的迷你项目来打磨收获一定不止一个分数。希望这篇笔记能帮你在做选课系统时少走弯路早点把正确的事做对。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →