拓冰建站拓冰建站
首页 / 资讯中心 / 正文

JSP+Servlet+JavaBean选课系统:老架构实战与避坑指南

简介这是一套面向高校计算机相关专业学生的数据库设计课程设计完整项目采用jspservletjavabean技术路线基于Tomcat8.5、Eclipse与SQL Server 2017开发适合作为课程设计、毕业设计或Java Web入门练手参考。项目围绕高校选课管理系统展开覆盖管理员、教师、学生三类角色管理员可管理教师、学生与课程信息并修改密码教师可查看任课信息、开设或取消课程、按班级或课程统计学生成绩与排名、录入成绩并自动更新学分学生可查看课程、选课退选及按学年查询成绩。资源包共237个文件以js脚本、java源码、css样式、jsp页面及png、jpg图片为主另含xml配置与字体资源压缩包约5.95MB并附有说明文档、E-R图与实体关系模型便于理解数据库设计思路。目前已有1052人学习下载可帮助读者快速掌握Java Web分层开发与数据库建模方法。1. 高校选课管理系统拆包jspservletjavabean 这套老架构还值不值得跑一遍如果你正在做数据库设计课程设计或者被安排了一个 JavaWeb 大作业大概率绕不开“选课管理系统”这个题目。我拿到这份资源的时候第一反应是jspservletjavabeanTomcat 8.5SQL Server 2017Eclipse 2019——这套组合放在今天确实不算新但它恰好是很多高校课程设计、头歌实训、毕设选题里反复出现的标准模板。换句话说它不是让你追新技术而是让你把 MVC 三层结构、数据库表设计、E-R 图、事务处理这些基本功一次性跑通。这份资源包含完整源码、说明文档、E-R 图和实体关系模型角色分管理员、教师、学生三端。管理员管教师、学生、课程和密码教师看任课信息、开课退课、查班级均绩排名、录成绩并自动更新学分学生看个人信息、选课退选、按学年查成绩。Web 服务器 Tomcat 8.5数据库 SQL Server 2017技术路线就是 jspservletjavabean。适合谁适合需要交课程设计、想理解传统 JavaWeb 项目怎么分层、或者想拿一个能跑通的选课系统做二次开发的人。不适合想学 Spring Boot 的人但如果你连 servlet 生命周期都没搞明白这套代码反而是最好的教材。2. 环境搭建与数据库还原Tomcat 8.5 和 SQL Server 2017 怎么配才不翻车2.1 为什么选 Tomcat 8.5 而不是 Tomcat 10Tomcat 8.5 对应的是 Servlet 3.1 规范而 Tomcat 10 已经切到 Jakarta EE 9包名从javax.servlet变成jakarta.servlet。这份资源里的 servlet 代码用的是javax.servlet如果你直接扔进 Tomcat 10启动就报 ClassNotFoundException。常见做法是要么老老实实用 Tomcat 8.5要么把代码里所有javax.servlet改成jakarta.servlet但后者工作量不小还容易漏。我一般会建议课程设计阶段直接用 Tomcat 8.5省去迁移的麻烦。Eclipse Java 2019 版本对 Tomcat 8.5 的支持很成熟新建 Dynamic Web Project 时 target runtime 选 Tomcat 8.5Dynamic web module version 选 3.1这样生成的 web.xml 和 servlet 映射才不会出玄学问题。2.2 数据库还原先建库再跑脚本SQL Server 2017 还原这份资源的数据库步骤不复杂但有几个参数容易设错。先创建一个空数据库字符集用默认的Chinese_PRC_CI_AS排序规则别乱改。然后执行资源里的建表脚本。-- 创建数据库 CREATE DATABASE CourseSelectionDB ON PRIMARY ( NAME CourseSelectionDB_data, FILENAME D:\SQLData\CourseSelectionDB_data.mdf, SIZE 10MB, MAXSIZE 200MB, FILEGROWTH 10MB ) LOG ON ( NAME CourseSelectionDB_log, FILENAME D:\SQLData\CourseSelectionDB_log.ldf, SIZE 5MB, MAXSIZE 100MB, FILEGROWTH 5MB ); GO -- 用户表区分管理员、教师、学生 CREATE TABLE Users ( user_id VARCHAR(20) PRIMARY KEY, password VARCHAR(50) NOT NULL, role VARCHAR(10) NOT NULL CHECK (role IN (admin,teacher,student)), name NVARCHAR(20) NOT NULL ); GO -- 课程表 CREATE TABLE Courses ( course_id VARCHAR(20) PRIMARY KEY, course_name NVARCHAR(50) NOT NULL, credit INT NOT NULL, teacher_id VARCHAR(20) FOREIGN KEY REFERENCES Users(user_id) ); GO -- 选课表记录学生选课和成绩 CREATE TABLE Enrollments ( enroll_id INT IDENTITY(1,1) PRIMARY KEY, student_id VARCHAR(20) FOREIGN KEY REFERENCES Users(user_id), course_id VARCHAR(20) FOREIGN KEY REFERENCES Courses(course_id), score DECIMAL(5,2) NULL, enroll_year VARCHAR(10) NOT NULL ); GO这段脚本里Users表用role字段区分三种角色避免建三张结构相似的表。Enrollments表的score允许为空因为选课的时候还没成绩教师录入后才更新。enroll_year用来支持“按学年查询考试成绩”这个功能。注意外键约束的顺序先建Users再建Courses最后建Enrollments否则外键引用会失败。2.3 JDBC 连接配置SQL Server 的坑比 MySQL 多SQL Server 的 JDBC 驱动和 MySQL 不一样类名是com.microsoft.sqlserver.jdbc.SQLServerDriverURL 格式也特殊。资源里一般会有一个DBUtil.java或者db.properties我习惯把连接参数单独抽出来。// DBUtil.java public class DBUtil { private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; private static final String URL jdbc:sqlserver://localhost:1433;databaseNameCourseSelectionDB;encryptfalse; private static final String USER sa; private static final String PASSWORD 你的密码; public static Connection getConnection() throws SQLException { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new SQLException(SQL Server 驱动未找到, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }encryptfalse这个参数在 SQL Server 2017 之后的驱动里必须加否则会报 SSL 握手错误。databaseName后面直接跟库名不要写成jdbc:sqlserver://localhost:1433;CourseSelectionDB那是 MySQL 的写法。端口默认 1433如果你装 SQL Server 的时候改了实例名URL 里还要加instanceName。提示SQL Server 配置管理器里要确保 TCP/IP 协议已启用并且 IP 地址里的 TCP 端口是 1433。很多人装完默认是禁用状态连不上就以为是代码问题。3. 三端功能怎么落地servlet 分发与 javabean 数据封装3.1 用 javabean 承载数据别在 servlet 里拼字符串这份资源的核心分层是 jsp 做展示、servlet 做控制、javabean 做数据模型。我见过不少课程设计把数据库查询结果直接塞进HttpServletRequest的 attribute 里当 Map 用短期能跑但代码一多就乱。正确的做法是每个实体一个 javabean。// User.java public class User { private String userId; private String password; private String role; private String name; public User() {} public User(String userId, String password, String role, String name) { this.userId userId; this.password password; this.role role; this.name name; } // getter 和 setter 省略 }javabean 的要求是无参构造方法、私有字段、public 的 getter/setter。jsp 页面里用jsp:useBean或者 EL 表达式${user.name}取值时底层就是靠反射调用 getter。如果你字段名写成userName但 getter 写成getUsername()EL 表达式会取不到值这种大小写问题排查起来很费时间。3.2 servlet 按角色分发请求三端功能多但 servlet 不需要写几十个。常见做法是用一个LoginServlet处理登录根据role字段把 session 里的用户信息设好然后重定向到对应的主页。后续每个功能模块一个 servlet比如TeacherServlet、StudentServlet、CourseServlet。// LoginServlet.java 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userId request.getParameter(userId); String password request.getParameter(password); UserDao dao new UserDao(); User user dao.login(userId, password); if (user null) { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); return; } HttpSession session request.getSession(); session.setAttribute(currentUser, user); switch (user.getRole()) { case admin: response.sendRedirect(admin/index.jsp); break; case teacher: response.sendRedirect(teacher/index.jsp); break; case student: response.sendRedirect(student/index.jsp); break; default: response.sendRedirect(login.jsp); } }这里用sendRedirect而不是forward是为了避免刷新页面时重复提交登录表单。session.setAttribute(currentUser, user)之后jsp 页面里用${currentUser.name}就能拿到当前登录人。注意 session 超时时间在web.xml里配置默认 30 分钟课程设计演示够用。3.3 教师录入成绩与学分自动更新教师端最核心的功能是录入成绩并且自动更新学生已修总学分。这个逻辑不能只靠一条 UPDATE 语句因为学分更新依赖于成绩是否及格。常见做法是在Enrollments表更新 score 之后再查一次该学生所有及格课程的总学分写回Users表或者单独的学生扩展表。// ScoreServlet.java 录入成绩 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int enrollId Integer.parseInt(request.getParameter(enrollId)); BigDecimal score new BigDecimal(request.getParameter(score)); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 更新成绩 String updateScore UPDATE Enrollments SET score ? WHERE enroll_id ?; PreparedStatement ps1 conn.prepareStatement(updateScore); ps1.setBigDecimal(1, score); ps1.setInt(2, enrollId); ps1.executeUpdate(); // 查询该学生所有及格课程的总学分 String sumCredit SELECT SUM(c.credit) FROM Enrollments e JOIN Courses c ON e.course_id c.course_id WHERE e.student_id (SELECT student_id FROM Enrollments WHERE enroll_id ?) AND e.score 60; PreparedStatement ps2 conn.prepareStatement(sumCredit); ps2.setInt(1, enrollId); ResultSet rs ps2.executeQuery(); int totalCredit 0; if (rs.next()) { totalCredit rs.getInt(1); } // 更新学生总学分 String updateCredit UPDATE Users SET total_credit ? WHERE user_id (SELECT student_id FROM Enrollments WHERE enroll_id ?); PreparedStatement ps3 conn.prepareStatement(updateCredit); ps3.setInt(1, totalCredit); ps3.setInt(2, enrollId); ps3.executeUpdate(); conn.commit(); response.sendRedirect(teacher/scoreList.jsp?msgsuccess); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); response.sendRedirect(teacher/scoreList.jsp?msgerror); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码的关键点是事务。更新成绩和更新学分必须在一个事务里否则成绩更新了但学分没更新学生查成绩时就会对不上。conn.setAutoCommit(false)开启事务conn.commit()提交异常时conn.rollback()回滚。注意Users表里需要有一个total_credit字段如果资源里没有自己加一列INT DEFAULT 0。注意SQL Server 的 JDBC 驱动在事务回滚后PreparedStatement 最好重新创建不要复用。我踩过这个坑回滚后复用同一个 ps 会报“该语句已关闭”。4. 避坑与排查这套老架构最容易翻车的五个地方4.1 中文乱码POST 和 GET 要分开处理现象登录时输入中文用户名后台拿到的是乱码或者 jsp 页面显示问号。原因Tomcat 8.5 默认的 URI 编码是 UTF-8但请求体编码取决于页面。POST 请求如果不设request.setCharacterEncoding(UTF-8)默认用 ISO-8859-1 解码。GET 请求在 Tomcat 8.5 里由server.xml的URIEncoding决定默认不是 UTF-8。解决在每一个 servlet 的doPost第一行加request.setCharacterEncoding(UTF-8)server.xml的 Connector 标签里加URIEncodingUTF-8。jsp 页面头部统一写% page contentTypetext/html;charsetUTF-8 languagejava %。4.2 选课退选时并发导致学分算错现象两个学生同时选同一门课课程容量只剩一个名额结果两个人都选上了。原因先查容量再插入中间没有加锁两个线程都查到“还有名额”。解决在Courses表加一个current_count字段选课时用UPDATE Courses SET current_count current_count 1 WHERE course_id ? AND current_count capacity根据executeUpdate返回的影响行数判断是否选上。返回 1 就是选课成功返回 0 就是已满。这个做法比先查后插可靠得多。4.3 jsp 页面里 Java 代码太多改一个字段要动五个文件现象学生信息展示页面里嵌了大段% %脚本数据库字段一改jsp 和 servlet 都要跟着改。原因没有用 EL 和 JSTL直接在 jsp 里写 JDBC 查询。解决把数据库操作全部收进 DAO 层jsp 只负责用${}取 request 或 session 里的 javabean。如果资源里已经有 DAO检查一下 jsp 里还有没有漏网的% Class.forName... %有就删掉。4.4 Tomcat 启动报 404但项目明明部署了现象Eclipse 里 Tomcat 启动正常访问http://localhost:8080/项目名/login.jsp却 404。原因Eclipse 默认把项目部署到 workspace 的.metadata目录下而不是 Tomcat 的webapps。有时候 context path 被改成了/访问路径就变了。解决双击 Tomcat 服务器在 Modules 标签里看 Path 是不是项目名。如果是/访问时就不需要加项目名。另外检查web.xml里welcome-file-list有没有配login.jsp。4.5 SQL Server 驱动版本和 JDK 不匹配现象ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver但 jar 包明明放进WEB-INF/lib了。原因SQL Server 的 JDBC 驱动分mssql-jdbc-7.x和mssql-jdbc-9.x7.x 支持 JDK 89.x 需要 JDK 11。Eclipse 2019 默认用 JDK 8 的话放 9.x 的驱动会加载失败。解决JDK 8 用mssql-jdbc-7.4.1.jre8.jarJDK 11 用mssql-jdbc-9.4.1.jre11.jar。放进去之后在 Eclipse 里右键项目 → Build Path → Configure Build Path → Libraries确认 jar 在 Classpath 下。5. 从能跑到好用E-R 图验证与查询性能的一个小技巧资源里附了 E-R 图和实体关系模型很多人拿到就扔一边其实这张图是验证数据库设计是否合理的最后一道防线。我一般会做一件事把 E-R 图里的实体和关系逐个对照建表脚本看有没有多对多关系被漏掉。比如“学生选课”是多对多必须有一张中间表Enrollments如果只靠Users和Courses两张表选课记录就存不下来。再比如“教师开课”是一对多Courses表里的teacher_id外键就是体现。如果 E-R 图里画了某个关系但建表脚本里没有对应字段那查询的时候一定会出问题。另一个容易被忽略的是索引。这份资源的查询里教师端要“按行政班级查看学生均绩及排名”学生端要“按学年查询考试成绩”这两个查询都涉及Enrollments表的student_id和enroll_year字段。如果数据量到了几千条不加索引会明显变慢。-- 在 Enrollments 表上建复合索引 CREATE NONCLUSTERED INDEX IX_Enrollments_Student_Year ON Enrollments (student_id, enroll_year) INCLUDE (course_id, score);INCLUDE里把course_id和score带上这样查询成绩时可以直接从索引页取数据不用回表。SQL Server 的执行计划里如果看到 Key Lookup就说明缺了 INCLUDE 字段。这个技巧在课程设计答辩的时候提一句比单纯说“我用了 JDBC”要加分。验证方法也简单在 SQL Server Management Studio 里开“显示实际执行计划”跑一遍排名查询看有没有 Index Scan。如果是 Scan 而不是 Seek说明索引没命中检查一下查询条件里的字段顺序和索引定义是否一致。提示索引不是越多越好Enrollments表的插入操作会因为索引变慢。选课高峰期如果并发高索引维护的开销要算进去。课程设计数据量小加一两个关键索引就够了。从那以后我每次拿到一个数据库课程设计都会先把 E-R 图摊开对着建表脚本一行行核对外键和中间表再跑一遍执行计划看索引。这个习惯帮我省了很多答辩时被问“为什么这里查这么慢”的后悔药。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门