简介:基于Servlet+Jsp实现的学生成绩管理系统,包含完整源码与数据库脚本,主要面向计算机相关专业毕业设计学生及需要项目实战的Java学习者。系统运行环境为Tomcat7及以上、MySQL5及以上,可配合Eclipse或IDEA开发工具使用,项目经过调试可直接部署运行。压缩包共166个文件,涵盖69个JSP页面、25个Java类及对应Class编译文件,并包含SQL数据库脚本、XML配置与少量图片资源,整体包体仅1.53MB,结构轻量、便于导入与二次开发。资源已吸引538人学习下载。系统按三种用户划分功能:管理员可进行学生、教师、课程信息管理;教师可开设课程、提交成绩;学生可在线选课和查询成绩,覆盖高校成绩管理的主要业务场景。相关数据库表设计完善,源码按DAO、Servlet、JSP分层,职责清晰,适合作为课程设计、毕业设计或初学Java Web项目的练手参考。
1. 学生成绩管理系统的第一印象:Servlet+Jsp 老项目值得抄吗
解压这份学生成绩管理系统源码包时,我猜你跟我当初一样,手比脑子快,直接把整个文件夹拖进了 Tomcat 的 webapps 目录,然后对着 404 页面愣了半天。这类基于 Servlet+Jsp+MySQL 的老项目,能不能跑起来从来不取决于代码本身,只取决于你有没有把环境参数对齐,这里面其实没什么玄学。它是标准的 Java Web 三层结构:JSP 出页面、Servlet 收请求、DAO 层访问数据库;管理员管学生、教师、课程,教师开课录成绩,学生选课查成绩,六张表撑起三种角色。适合正在赶毕设的在校生,也适合想把 Java Web 全链路串一遍的初学者。下面从结构拆解、环境部署、数据库设计到踩坑清单,一步步讲完。
2. 源码结构拆解:从 DAO 类名看懂分层与请求走向
2.1 压缩包里的真实内容:三层结构与一份.class 清单
打开压缩包,你大概率会先看到一堆名字带 DAO 和 slt 的文件,这些是编译后的 class 文件,不是源码。先别急着删,它们的命名透露出整个项目的分层思路:DAO 结尾的是数据访问层,负责跟 MySQL 打交道;slt 结尾的是 Servlet 层,负责接收 HTTP 请求;剩下的 JSP 页面负责展示。这种按职责命名的习惯,在小项目里看起来冗余,但真到改需求的时候,你能一眼定位该动哪个文件,比那种全堆在 JSP 里的写法好维护得多。
| 类名 | 推断职责 |
|---|---|
| StudentDAO / TeacherDAO | 学生、教师基本信息读写 |
| ScDAO / TcDAO / MscDAO | 关联表读写:选课关系、授课关系、管理端统计 |
| sc_slt / tc_slt / msc_slt | 对应的请求处理 Servlet |
| teacher_slt | 教师端请求入口 |
| jxl_slt | 基于 jxl 库的 Excel 导出处理 |
这里有个值得留意的点:jxl 是 Java 操作 Excel 的老牌库,jxl_slt 大概率承担成绩单导出功能。很多人在部署时发现 lib 里少了 jxl.jar,项目能启动但一点导出就报 ClassNotFoundException,原因就在这。建议拿到包先看一眼 WEB-INF/lib 目录,把 jxl.jar 和 mysql 驱动确认在不在,能省掉后面一大半排错时间。
2.2 DAO 层写法:一段标准的 JDBC 访问代码
这类项目最常见的 DAO 写法是这样,从连接、预编译、执行到结果集封装,一套流程固定不变。下面以查询学生为例:
public Student findByNo(String no) { Student s = null; String sql = "SELECT * FROM student WHERE stu_no = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, no); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { s = new Student(); s.setStuNo(rs.getString("stu_no")); s.setName(rs.getString("stu_name")); s.setClassNo(rs.getString("class_no")); } } } catch (SQLException e) { e.printStackTrace(); } return s; }逻辑说明:先拿连接,再预编译 SQL,用占位符?传参,避免字符串拼接带来的注入风险。这是那个年代 DAO 的固定模板,几乎所有表都能套这个结构。参数说明:stu_no是学生表主键;?占位符下标从 1 开始;try-with-resources 能自动关闭连接、语句、结果集,不用手写 finally 里的一堆 close()。这个方法返回 null 就表示没查到这个人,Servlet 拿到 null 后判断登录失败。
2.3 Servlet 层怎么接请求:一个提交成绩的例子
看一个成绩提交的 Servlet 骨架,就能明白这项目里请求是怎么流转的:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String stuNo = request.getParameter("stu_no"); String courseNo = request.getParameter("course_no"); String score = request.getParameter("score"); TcDAO dao = new TcDAO(); boolean ok = dao.updateScore(stuNo, courseNo, Double.parseDouble(score)); if (ok) { response.sendRedirect("teacher/score_ok.jsp"); } else { request.setAttribute("msg", "成绩提交失败"); request.getRequestDispatcher("teacher/score_input.jsp").forward(request, response); } }逻辑说明:Servlet 只做三件事——收参数、调 DAO、决定跳转。这里用了 sendRedirect 重定向和 forward 转发两种方式,区别是重定向会改地址栏,适合提交成功后防刷屏;转发能带 request 里的 msg 回原页面展示错误信息。参数说明:request.getParameter拿到的全是字符串,数字字段要手动转换;Double.parseDouble遇到空值会抛 NumberFormatException,实际项目里最好先判空再转。常见误用是把这段逻辑塞进 JSP 的<% %>里,页面一多就乱成一团。这份资源把 DAO 和 Servlet 分开,改表结构时只需要动 DAO 层,这个分层习惯值得保留。
提示:后面你跑通项目后如果想改功能,先遵循这个分层:页面改动找 JSP,请求处理找 slt,数据操作找 DAO,别跨层改。
3. 环境搭建与部署:Tomcat+MySQL+Navicat 的参数清单
3.1 版本组合怎么选才不翻车
这项目要求 Tomcat7 以上、MySQL5 以上。我的建议是别追求最新:JDK8 + Tomcat8.5 + MySQL5.7 是这类老项目最稳的组合。Tomcat9 也能跑,但 Tomcat7 早期版本配 JDK8 容易出现 JSP 编译期类加载问题;MySQL8 不是不行,要换驱动类名再加时区参数,对新手不友好。我见过太多卡在 mysql 安装上的人,其实跟版本号没关系,是把驱动和时区参数对齐。
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 | 别用 JDK11+,老项目的 JSP 编译时常出类加载问题 |
| Tomcat | 8.5.x | 兼容 Servlet 3.1,稳定 |
| MySQL | 5.7.x | 5.5 也行,5.7 最省心 |
| 驱动 | mysql-connector-java 5.1.49 | MySQL5.7 专用;用 MySQL8 见 5.2 节 |
| IDE | Eclipse 或 IDEA | 任选,导入方式略不同 |
3.2 导入 IDE:让项目进入可编译状态
Eclipse 里:File -> Import -> General -> Existing Projects into Workspace,选中解压后的目录,Finish。IDEA 里用 Open 选到含 .project 的目录,会自动识别成 Java Web 项目。导入后第一件事是检查 Project Structure 里的 JDK 是不是 1.8,再把 WEB-INF/lib 下的 jar 全部加进 Build Path。很多人栽在这一步:项目能打开但报一堆红色叉,其实就是 lib 没加进编译路径。
# 在项目根目录确认 lib 下的核心依赖 ls -l src/main/webapp/WEB-INF/lib/ 2>/dev/null || ls -l WebContent/WEB-INF/lib/ 2>/dev/null # 期望至少看到 mysql-connector-java 和 jxl 两个 jar逻辑说明:这行命令同时兼容两种项目目录结构,找不到就换另一个路径再试。参数说明:管道符||表示前一条命令失败才执行后一条,避免目录不存在时刷一堆报错。看到两个 jar 后,再去 IDE 的 Build Path 里确认它们被引用,缺一步都会在运行时报 ClassNotFound。
3.3 数据库初始化:脚本执行与连接参数
先用 Navicat 新建一个数据库,名字跟项目里的连接串保持一致,字符集选 utf8mb4。然后右键数据库选运行 SQL 脚本,选中项目自带的 .sql 文件,执行完会生成六张表。命令行执行效果一样:
mysql -uroot -p123456 < student_score.sql # 如果脚本里没有 CREATE DATABASE,先手动建库再执行逻辑说明:<把文件内容重定向给 mysql 命令逐条执行。参数说明:-u指定用户,-p后直接跟密码不能留空格;密码含特殊字符时建议只写-p,回车后再输入,避免特殊字符被 shell 解析。执行完用show tables;确认六张表都在,再往下走。
接着改连接配置,这项目的连接串一般集中在 db.properties 或 DBUtil.java 里:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/student_score?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456参数说明:driver 的类名跟 MySQL 版本强相关,MySQL5 用com.mysql.jdbc.Driver,MySQL8 要换成com.mysql.cj.jdbc.Driver并在 url 后补useSSL=false&serverTimezone=Asia/Shanghai。useUnicode=true&characterEncoding=utf8是中文不乱码的前提,漏掉的话第 5.3 节的乱码问题等着你。username 和 password 改成你自己 MySQL 的账号密码。
3.4 部署到 Tomcat 并启动
Eclipse 里右键项目 -> Run As -> Run on Server,选 Tomcat8.5,启动后浏览器会自动打开。IDEA 则在 Run Configuration 的 Deployment 里加 artifact,Application context 改成 /score。启动后先确认端口在听:
netstat -ano | findstr 8080 # Windows 用 netstat,Linux 用 ss -lntp逻辑说明:看到 LISTENING 表示 Tomcat 起来了;页面打不开时去 logs 目录翻 catalina.out 或 localhost.log 看最后 20 行,大多数启动失败的原因日志里都有。参数说明:应用 context 路径决定了访问地址,http://localhost:8080/score/login.jsp里的 /score 就是 Application context,改了这个路径,访问地址要跟着改,这是 404 的高发区。
4. 六张表与三种角色:权限设计是怎么转起来的
4.1 表结构:每张表管什么
六张表分别是:系统用户表、学生表、教师表、课程表、学生课程关联表(sc)、教师课程关联表(tc)。用户表统一存登录账号和密码,角色字段区分管理员、教师、学生;业务数据放在各自的表里,选课和授课通过关联表连接。这个设计的核心思想是把「谁是谁」和「谁做了什么」分开,账号体系独立出来,以后要加个教务员角色,不用动业务表。
| 表名 | 关键字段 | 作用 |
|---|---|---|
| sys_user | id, username, password, role | 登录入口,role 0/1/2 区分角色 |
| student | stu_no, stu_name, class_no | 学生基础信息 |
| teacher | tea_no, tea_name, dept | 教师基础信息 |
| course | course_no, course_name, credit | 课程档案 |
| sc | stu_no, course_no, score | 选课关系 + 成绩 |
| tc | tea_no, course_no | 教师开课关系 |
这里有个值得学习的设计:成绩字段放在 sc 表而不是单独建一张成绩表,因为成绩本质上是「学生-课程」这个关系的属性。你用一条 join 就能查出某个学生的所有成绩,不用来回联三张表。这个数据建模思路,拿到别的管理系统里同样适用。
4.2 建表 SQL 与关联约束
看两个核心表的建表语句,重点是约束怎么加:
CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, role TINYINT NOT NULL DEFAULT 2 -- 0 管理员 / 1 教师 / 2 学生 ); CREATE TABLE sc ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL, course_no VARCHAR(20) NOT NULL, score DECIMAL(5,1) DEFAULT NULL, UNIQUE KEY uk_sc (stu_no, course_no), CONSTRAINT fk_sc_stu FOREIGN KEY (stu_no) REFERENCES student(stu_no), CONSTRAINT fk_sc_course FOREIGN KEY (course_no) REFERENCES course(course_no) );逻辑说明:UNIQUE KEY 防止同一个学生重复选同一门课;外键保证不存在的学号、课程号进不了关联表。如果你导入时发现建表顺序报错,先建课程表和学生表,再建关联表。参数说明:DECIMAL(5,1)能存最大 999.9 的分数,保留一位小数足够;AUTO_INCREMENT让 id 自增,作为无业务意义的代理主键,真实业务编号用 stu_no、course_no 这类业务主键。
4.3 登录判定与角色分流
登录接口收到用户名密码后,先去 sys_user 表查,匹配成功就把 user 对象和 role 放进 session,再按 role 做分支跳转。我一般把角色判断放在同一个 Servlet 里,比写三个 login Servlet 干净得多:
User u = dao.findByUsernameAndPwd(username, md5(password)); if (u == null) { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } session.setAttribute("loginUser", u); switch (u.getRole()) { case 0: response.sendRedirect("admin/index.jsp"); break; case 1: response.sendRedirect("teacher/index.jsp"); break; case 2: response.sendRedirect("student/index.jsp"); break; }逻辑说明:登录失败用 forward 回到登录页并带出错误提示,登录成功用 sendRedirect 跳到对应角色的首页,避免刷新页面时重复提交表单。参数说明:session 是服务端对象,重定向后 JSP 页面照样能通过${loginUser.name}取到用户信息。别用 request 传登录用户,重定向会丢。密码这里我写了 md5(password),如果原始资源存的是明文,建议你改成 MD5 或加盐哈希,答辩时能加不少分。
4.4 三条核心业务 SQL:选课、录分、查成绩
学生选课:
INSERT INTO sc (stu_no, course_no) VALUES ('2024001', 'C001'); -- 如果 sc 表加过 UNIQUE KEY,重复选课会被数据库拦截逻辑说明:这条语句只插入学号和课程号,成绩字段保持 NULL,表示还没录入。参数说明:NULL 和 0 是两回事,页面判断「未录入」时要用score IS NULL,不要用score = 0,否则没录分的课会被当成 0 分。
教师录成绩:
UPDATE sc SET score = 88.5 WHERE stu_no = '2024001' AND course_no = 'C001'; -- 没有成绩就是 NULL,页面显示「未录入」逻辑说明:成绩提交本质是更新选课记录里的 score 字段。参数说明:WHERE 条件务必带全学号和课程号,漏一个就可能把同一个学生的所有课程成绩全改掉,这是录成绩功能最危险的误操作。
查成绩(带课程名和教师名):
SELECT c.course_name, t.tea_name, s.score FROM sc s JOIN course c ON s.course_no = c.course_no JOIN tc ON c.course_no = tc.course_no JOIN teacher t ON tc.tea_no = t.tea_no WHERE s.stu_no = '2024001';逻辑说明:这个 join 把选课关系、课程信息、授课关系全串起来,一次查出课程名、教师名和分数。参数说明:如果出现重复行,说明 tc 表里同一门课被多个老师开了班,业务上要按班级或教学班再加条件。这种「一行变多行」的 join 问题是查成绩最容易踩的,遇到先看 tc 表有没有重复记录。
5. 部署运行避坑:五个最容易卡住新手的位置
5.1 打开页面 404:Tomcat 部署路径和访问路径对不上
现象:Tomcat 能启动,浏览器输入 localhost:8080 看得到 Tomcat 首页,但访问项目路径就 404。
原因:项目没有部署成功,或者 application context 和你输入的不一致。老手常用的排查顺序是:先看有没有把项目发布到 webapps,再看 context 名字,最后确认 URL。
解决:Eclipse 的 Servers 标签页右键项目 -> Add and Remove,确认项目已经加进右边列表;IDEA 则在 Run Configuration 的 Deployment 里看 Application context。我一般把 context 固定设成 /score,访问地址就固定为http://localhost:8080/score/login.jsp,省得记来记去。也可以直接在 tomcat 的 conf/server.xml 里加:
<Context docBase="D:/student_score" path="/score" reloadable="true"/>参数说明:docBase 写项目磁盘路径,path 是访问前缀。改完必须重启 Tomcat,这个 Context 是启动时加载的,不重启不生效。这个坑我踩过不止一次,改完配置不重启,盯着浏览器看了十分钟不知道怎么还是 404。
5.2 MySQL 报 Communications link failure:驱动与时区参数的组合问题
现象:点登录后页面卡很久,然后报 Communications link failure 或 SSL connection error,控制台出现 Server returns invalid timezone。
原因:十有八九是 MySQL8 的驱动类名和时区问题。项目原配的com.mysql.jdbc.Driver在 MySQL8 下虽然能用,但连接串里没有 serverTimezone,导致握手时失败。
解决:把 db.properties 改成下面这个版本:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/student_score?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai参数说明:useSSL=false 跳过 SSL 握手,本地开发完全够用;serverTimezone=Asia/Shanghai 解决时区报错。如果你坚持用 MySQL5,驱动保持com.mysql.jdbc.Driver,这两个参数不用加。选哪条路,取决于你机器上装的是哪个版本的 MySQL,打开 Navicat 看一眼版本就知道,不用纠结。
5.3 登录成功后全文乱码:三层编码不一致
现象:页面 title 是好的,从数据库查出来的学生姓名全是问号或乱码。
原因:JSP 页面、Servlet 请求、MySQL 连接三个环节各有一套编码,任何一个不是 UTF-8 就乱。最常见的是连接串没带 characterEncoding=utf8,以及 JSP 页面头部的 pageEncoding 和 contentType 不一致。
解决:先统一三处。页面头改成:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>Servlet 在 doGet 和 doPost 最上面加request.setCharacterEncoding("UTF-8");连接串加 characterEncoding=utf8。MySQL 那边如果建库时用了 latin1,改库的字符集要重建表,建议新建库重导脚本,别在旧库上改,改不干净的。
5.4 8080 端口被占用:Tomcat 起不来
现象:启动时报 Address already in use: JVM_Bind,或者 Tomcat 页面打不开。
原因:其它程序占了 8080,常见的是另外的 Tomcat、Oracle 数据库、或者一些开发工具的本地服务。
解决:先找占用者,再决定杀还是换端口:
netstat -ano | findstr 8080 tasklist | findstr <PID>如果是个无关进程,直接任务管理器结束;如果是你自己另一台 Tomcat,就把当前项目改成 8081。改法在 conf/server.xml 的 Connector 标签:
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>改完重启,访问地址同步换成 8081。注意 IDE 里如果配置了多个 Tomcat 实例,改的是你要 Run 的那一个 server.xml,别改错了安装目录里的那份,改错的结果就是怎么改都不生效。
5.5 一提交成绩就 ClassNotFoundException:lib 里少了 jxl.jar
现象:登录、选课都正常,教师一点「导出成绩」,控制台报 java.lang.ClassNotFoundException: jxl.write.WritableWorkbook。
原因:jxl_slt 这个 Servlet 用 jxl 库写 Excel,但 WEB-INF/lib 下没有 jxl.jar,或者 IDE 的 Build Path 没加进去。项目启动时不会立即检查到这个类,等你点导出才触发加载,属于「用到才炸」的典型。
解决:确认两个位置都要有 jar。物理目录里:
find . -name "jxl*.jar"然后 IDE 的 Build Path 也要引用。Eclipse 是右键 jar -> Build Path -> Add to Build Path;IDEA 在 File -> Project Structure -> Libraries。很多情况下物理目录有 jar 而 IDE 没引用,一样报错。这类问题光看启动日志发现不了,跑一遍三个角色的完整流程才能暴露出来。
注意:jxl 是 Java Excel API 的缩写,不是某个中文拼音,别手滑把 jxl_slt 当成无关文件删了,它是成绩导出的核心。
6. 进阶:两个把项目变成「你自己的」的验证习惯
6.1 拿到 .class 没有 .java:反编译三分钟找回源码
压缩包里只有 .class 文件的情况,多半是打包机器只出了编译产物,拿到手一样能用。JDK 自带的 javap 能看字节码:
javap -c -p StudentDAO.class # 会输出方法签名和字节码指令,够你读明白每个方法的参数和调用关系想直接还原成 Java 源码,用 JD-GUI 或 Fernflower 这类反编译工具,把整个 classes 目录拖进去就能导出。反编译出来的代码可能有符号丢失,但拿来理解结构和补全业务逻辑足够。答辩前花半小时把每个类的职责捋一遍,比背稿子管用得多。
6.2 答辩/验收前走一遍三角色回归流程
我建议按这个顺序过一遍:管理员登录新增一个学生、一个教师、一门课程;教师登录开课并录入成绩;学生登录选课再查成绩,确认分数显示正常;教师导出成绩 Excel,确认 jxl 导出不报错;最后换一个错密码登录,确认有错误提示而不是 500。这五步走完,项目才算在你这台机器上真正可信。
我自己的一个血泪教训是:有次以为只改了 context 路径,看着配置没问题,没重启就接着调页面,结果所有请求都 404,查了半小时才发现 server.xml 的改动要重启才生效。从那以后我每次改完配置都强制走一遍「重启 + 看日志 + 跑五步流程」这套动作,希望帮到你。
本文还有配套的精品资源,点击获取