JavaWeb学生成绩管理系统毕设实战:从环境搭建到源码交付
发布时间:2026/9/24 19:00:58 作者:尧图编辑部 阅读量:1,286

简介这是一套面向计算机相关专业毕设学生与Java项目实战学习者的学生成绩管理系统完整源码包采用JSP、Servlet、JDBC技术栈结合MySQL数据库开发基于B/S结构可直接作为毕业设计或课程设计使用。系统划分学生、教师、管理员三种角色涵盖学生与教师基本信息管理、班级信息管理、课程信息管理、成绩信息管理、系统设置及考勤管理七大模块支持成绩导入、统计汇总与多条件查询功能完善、界面美观、操作便捷。资源包共3个文件包含项目源码压缩包、数据库脚本sql文件及项目说明txt文档整体约6.38MB结构清晰便于快速部署与二次开发。目前已有7845人学习下载适合需要完整项目案例、排错思路与数据库设计参考的Java学习者可帮助读者掌握JavaWeb开发流程与成绩管理业务逻辑。1. 从一份能跑起来的 JavaWeb 学生成绩管理系统说起很多同学做毕设时选题定的是「基于 JavaWeb 的学生成绩管理系统」结果卡在第一步环境跑不起来。IDEA 里 Tomcat 一启动就报 404数据库连不上JSP 页面中文乱码改了两天还在原地打转。这个标题背后其实是一套非常典型的 JavaWeb 技术栈组合——Servlet/JSP 或 Spring Boot 做后端MySQL 存成绩数据前端用 JSP 或 Vue 渲染页面最后交付物是「项目源码 数据库脚本」两样东西。它解决的核心问题是把学生、课程、成绩、教师这几类数据管起来支持录入、查询、统计和导出。适合谁计算机相关专业的毕设选手、刚学完 JavaWeb 想找一个完整案例练手的人、以及需要一套可二次开发的成绩管理底座的开发者。下面我按「先跑通、再讲透、最后避坑」的顺序把这条路走一遍。2. 技术选型Servlet/JSP 还是 Spring Boot数据库脚本怎么设计2.1 两套主流技术栈的取舍做学生成绩管理系统绕不开的第一个决策就是用传统 Servlet JSP还是 Spring Boot 前端框架。这不是玄学选错了后面全是返工。传统 Servlet JSP 的优点是「教学案例多、依赖少、答辩好讲」。你打开黑马程序员的 JavaWeb 笔记或者搜「javaweb项目完整案例mysql」绝大多数都是这套。它的请求链路清晰浏览器请求 → web.xml 或注解映射 → Servlet 处理 → JDBC 操作 MySQL → 转发/重定向到 JSP。答辩老师问「请求怎么走的」你能一条线讲完。缺点是 JSP 里写 Java 代码页面和逻辑耦合代码量大前端体验一般。Spring Boot Vue 的优点是「工程化程度高、简历好看」。后端用 Spring Boot 提供 REST 接口前端 Vue 调接口渲染前后端分离。缺点是环境配置复杂IDEA 创建 JavaWeb 项目、跨域、打包部署每一步都可能翻车而且答辩时如果对 Spring 的自动配置原理说不清反而容易被追问。我的建议很直接如果时间紧、基础一般选 Servlet JSP把功能做完整如果时间充裕、想冲优秀毕设选 Spring Boot Vue。两条路都能交付「项目源码 数据库脚本」区别只在工程复杂度。2.2 数据库脚本的设计要点数据库脚本是整个系统的地基很多人的脚本一跑就报错问题往往出在字符集、外键顺序和初始数据上。下面是一份可直接执行的核心建表脚本覆盖学生、课程、成绩三张主表和一张用户表。-- 创建数据库显式指定字符集避免中文乱码 CREATE DATABASE IF NOT EXISTS score_manage DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE score_manage; -- 用户表登录用role 区分管理员/教师/学生 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 建议存MD5或BCrypt, role TINYINT NOT NULL DEFAULT 3 COMMENT 1管理员 2教师 3学生, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表学号唯一班级用于分组统计 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL, gender CHAR(2) DEFAULT 男, class_name VARCHAR(50) COMMENT 班级, user_id INT COMMENT 关联user表, CONSTRAINT fk_student_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 课程表课程编号唯一学分影响绩点计算 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(100) NOT NULL, credit DECIMAL(3,1) DEFAULT 0.0 COMMENT 学分, teacher_id INT COMMENT 授课教师 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 成绩表学生课程唯一防止重复录入 CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) DEFAULT NULL COMMENT 0-100, exam_time DATE, UNIQUE KEY uk_stu_course (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本有几个关键点必须说清楚。第一utf8mb4而不是utf8因为 MySQL 的utf8是残缺的三字节实现存 emoji 或某些生僻字会报错成绩系统里学生姓名偶尔有生僻字用utf8mb4一劳永逸。第二外键的创建顺序必须是「被引用的表先建」user在student之前student和course在score之前否则脚本执行到一半就报errno 150。第三score表上加了uk_stu_course唯一索引这是防止同一个学生同一门课被录入两次业务上比在代码里查重更可靠。第四password字段长度给到 100是因为如果你用 BCrypt 加密密文长度是 60 位留足余量。提示导出数据库脚本时用mysqldump -u root -p --databases score_manage score_manage.sql加上--databases参数会把建库语句一起导出别人拿到脚本直接 source 就能跑不用手动建库。2.3 初始数据与自增主键的坑脚本里最好带几条初始数据否则系统跑起来是空的登录都进不去。但初始数据有个坑如果你手动指定了id比如INSERT INTO user (id, username...) VALUES (1, admin...)那么后续自增主键会从 2 开始没问题但如果你插入了id100的记录自增会跳到 101中间的空洞不影响使用只是看着别扭。更稳妥的做法是不指定id让数据库自己分配然后用SELECT LAST_INSERT_ID()拿回来做关联插入。3. 在 IDEA 里把项目跑起来环境配置与最小可运行链路3.1 环境配置的四个必查项「idea运行javaweb项目配置」是热搜里的高频问题绝大多数启动失败都出在四个地方。第一JDK 版本。Servlet/JSP 项目建议 JDK 8 或 11Spring Boot 2.x 建议 JDK 8/11Spring Boot 3.x 要求 JDK 17。版本不匹配会报Unsupported class file major version。第二Tomcat 版本。Tomcat 9 对应 Servlet 4.0Tomcat 10 对应 Servlet 5.0而 Servlet 5.0 把包名从javax.servlet改成了jakarta.servlet如果你用 Tomcat 10 跑老代码所有import javax.servlet.*全部报红。这是最隐蔽的翻车点很多人以为是代码问题其实是 Tomcat 版本选错了。第三Maven 依赖。pom.xml里 servlet-api 的 scope 要设成provided因为 Tomcat 自带这个包打进 war 会冲突。第四项目结构。IDEA 里要确认Project Structure → Modules → Web的 web 根目录指向src/main/webapp否则 JSP 找不到。!-- pom.xml 关键依赖Servlet/JSP 项目 -- dependencies !-- servlet-api 必须 providedTomcat 已自带 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSTL 标签库JSP 页面循环遍历用 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动8.x 用 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies依赖说明javax.servlet-api的provided是关键少了它启动报ClassNotFoundException加了compile又可能和 Tomcat 的包冲突。jstl用于 JSP 里写c:forEach遍历成绩列表不引入的话标签不生效。MySQL 驱动 8.x 的类名是com.mysql.cj.jdbc.DriverURL 里要带serverTimezoneAsia/Shanghai否则时间字段会差 8 小时。3.2 数据库连接与 JDBC 工具类连接数据库的代码如果每个 Servlet 都写一遍维护起来是灾难。常见做法是抽一个DBUtil工具类用静态块加载驱动提供获取连接和关闭资源的方法。public class DBUtil { // 8.x 驱动类名5.x 是 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/score_manage?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD 你的密码; static { try { Class.forName(DRIVER); // 加载驱动只需一次 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 关闭顺序ResultSet - Statement - Connection public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }逻辑说明静态块保证驱动只加载一次避免重复注册。URL 里的useSSLfalse关掉 SSL 警告characterEncodingutf8保证中文正常。关闭顺序必须是 ResultSet 先关再关 Statement最后关 Connection反了会报错。参数方面如果你用的是连接池如 Druid、HikariCP把getConn换成从DataSource取连接即可其余代码不用动。3.3 一个最小可运行的查询链路把环境配好后先别急着写全部功能跑通一条「查询学生列表」的链路验证 Tomcat、数据库、JSP 三者是通的。// StudentListServlet.java WebServlet(/student/list) public class StudentListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListStudent list new ArrayList(); String sql SELECT id, stu_no, name, gender, class_name FROM student; try (Connection c DBUtil.getConn(); PreparedStatement ps c.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStuNo(rs.getString(stu_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setClassName(rs.getString(class_name)); list.add(s); } } catch (SQLException e) { throw new ServletException(查询学生失败, e); } req.setAttribute(students, list); req.getRequestDispatcher(/student_list.jsp).forward(req, resp); } }这段代码用 try-with-resources 自动关闭连接比手动 close 更安全。查询结果放进request域转发到 JSP 渲染。JSP 里用 JSTL 遍历% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth学号/thth姓名/thth性别/thth班级/th/tr c:forEach items${students} vars tr td${s.stuNo}/td td${s.name}/td td${s.gender}/td td${s.className}/td /tr /c:forEach /table访问http://localhost:8080/你的项目名/student/list如果能看到学生表格说明整条链路通了。这一步跑通后增删改查都是同样的套路只是 SQL 不同。4. 成绩录入与统计业务逻辑里最容易写错的三个地方4.1 成绩录入的校验与事务成绩录入不是简单的 INSERT它涉及三个校验学生是否存在、课程是否存在、成绩是否在 0-100 之间。更关键的是如果一次录入多门课的成绩要么全成功要么全失败这就需要用事务。public void batchInsertScore(ListScore scores) throws SQLException { String sql INSERT INTO score (student_id, course_id, score, exam_time) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score), exam_time VALUES(exam_time); Connection c null; try { c DBUtil.getConn(); c.setAutoCommit(false); // 开启事务 try (PreparedStatement ps c.prepareStatement(sql)) { for (Score s : scores) { if (s.getScore() 0 || s.getScore() 100) { throw new IllegalArgumentException(成绩越界: s.getScore()); } ps.setInt(1, s.getStudentId()); ps.setInt(2, s.getCourseId()); ps.setBigDecimal(3, s.getScore()); ps.setDate(4, new java.sql.Date(s.getExamTime().getTime())); ps.addBatch(); } ps.executeBatch(); c.commit(); // 全部成功才提交 } } catch (Exception e) { if (c ! null) c.rollback(); // 任何异常都回滚 throw new SQLException(批量录入失败, e); } finally { if (c ! null) { c.setAutoCommit(true); c.close(); } } }逻辑说明ON DUPLICATE KEY UPDATE利用了score表上的唯一索引如果同一学生同一课程已存在记录就更新成绩而不是报错这比先查后插更高效也避免了并发下的重复插入。事务的边界是「所有成绩」任何一条越界或 SQL 异常都触发rollback。参数方面addBatch和executeBatch适合批量场景如果只有一两条直接executeUpdate更简单。4.2 绩点与加权平均的 SQL 写法成绩统计里最常见的是算加权平均分和绩点。加权平均分是SUM(score * credit) / SUM(credit)绩点各校算法不同常见的是(score - 50) / 10低于 60 记 0。-- 每个学生的加权平均分和总学分 SELECT s.stu_no, s.name, ROUND(SUM(sc.score * c.credit) / SUM(c.credit), 2) AS avg_score, SUM(c.credit) AS total_credit FROM student s JOIN score sc ON s.id sc.student_id JOIN course c ON sc.course_id c.id GROUP BY s.id, s.stu_no, s.name ORDER BY avg_score DESC;这个查询的关键是GROUP BY后面要带上s.id因为stu_no和name理论上可能重复用主键分组最准确。ROUND保留两位小数避免出现85.33333333这种结果。如果某个学生没有成绩JOIN会把他排除想保留的话要用LEFT JOIN但那样SUM会返回 NULL需要IFNULL兜底。4.3 分页查询的两种实现成绩列表数据量大时必须分页。MySQL 用LIMIT offset, size但offset很大时性能会下降因为要扫描前 offset 行。-- 方式一LIMIT 偏移简单但大偏移慢 SELECT * FROM score ORDER BY id LIMIT 20, 10; -- 第3页每页10条 -- 方式二游标分页记住上一页最后一条的 id SELECT * FROM score WHERE id 100 ORDER BY id LIMIT 10;方式一适合页数不多的场景方式二适合无限滚动或数据量大的场景。毕设里用方式一就够了但答辩时如果能说出方式二的优势是个加分项。分页还要配一个COUNT(*)查询算总页数注意COUNT和列表查询要用相同的WHERE条件否则页数对不上。5. 避坑与排查那些让毕设卡三天的真实问题5.1 中文乱码从请求到响应到数据库现象JSP 页面输入中文存进数据库变成???或者查询出来显示乱码。原因有三层请求编码、响应编码、数据库编码。解决要三管齐下。请求端POST 请求在 Servlet 里第一行写req.setCharacterEncoding(UTF-8)GET 请求在 Tomcat 8 以后默认 UTF-8但保险起见在server.xml的 Connector 上加URIEncodingUTF-8。响应端resp.setContentType(text/html;charsetUTF-8)。数据库端建库建表都用utf8mb4连接 URL 带characterEncodingutf8。三层都对了乱码才会消失。5.2 404路径对了但访问不到现象Tomcat 启动成功访问http://localhost:8080/student/list报 404。原因通常是项目上下文路径不对。IDEA 里部署 war 时Application context默认是/或者项目名如果你设成了/score那访问路径应该是http://localhost:8080/score/student/list。另一个原因是WebServlet的路径写错或者web.xml的url-pattern和注解冲突。排查方法看 Tomcat 启动日志里Deploying web application那一行确认 context path再对照 Servlet 映射。5.3 数据库连接池耗尽现象系统用一会儿就报Cannot get a connection, pool exhausted。原因是连接用完没归还。用DBUtil.getConn()手动获取的连接必须在finally里 close否则连接池很快被占满。更稳妥的做法是用 try-with-resources让 JVM 自动关闭。如果用 Druid 连接池配置maxActive别设太大毕设场景 10-20 足够设成 100 反而可能把 MySQL 的max_connections撑爆。5.4 外键约束导致删除失败现象删除一个学生报Cannot delete or update a parent row: a foreign key constraint fails。原因是score表里有这个学生的成绩记录外键挡住了删除。解决方案有两种一是先删成绩再删学生用事务包起来二是建表时把外键设成ON DELETE CASCADE删学生时成绩自动删。毕设里推荐第一种因为级联删除太危险误删学生连带成绩全没了答辩时也不好解释。5.5 JSP 修改后不生效现象改了 JSP 页面刷新浏览器还是旧内容。原因是 Tomcat 缓存了编译后的 JSP。解决在 IDEA 的 Tomcat 配置里On Update action选Update classes and resourcesOn frame deactivation也选同样的。或者直接删掉 Tomcat 工作目录下的work文件夹强制重新编译。这个坑不致命但很烦改一次重启一次效率极低。6. 让成绩系统更像一个「作品」导出、权限与二次开发把基本功能跑通后想让毕设从「能跑」变成「能看」有三个方向可以加码。第一个是成绩导出 Excel。用 Apache POI 库把查询结果写成.xlsx答辩时演示一键导出比截图有说服力。核心代码是XSSFWorkbook建工作簿createSheet建表createRow和createCell填数据最后workbook.write(outputStream)。注意导出时响应头要设Content-Disposition: attachment; filename成绩表.xlsx否则浏览器会直接打开而不是下载。第二个是权限控制。系统里有管理员、教师、学生三种角色管理员能管所有数据教师只能录自己课的成绩学生只能查自己的成绩。实现方式是在登录成功后把role和userId存进session每个 Servlet 开头做一次校验。比如学生查成绩时SQL 里强制加WHERE student_id ?参数从 session 取而不是从请求参数取这样学生改 URL 也查不到别人的成绩。这是安全底线答辩时老师很可能会问。第三个是二次开发的接口设计。如果你的系统想被别人拿去改Service 层要抽干净Servlet 只负责收参数和转发业务逻辑全在 Service 里。这样别人换前端比如换成 Vue时Service 不用动。我一般会把StudentService、CourseService、ScoreService各写一个接口和一个实现类Servlet 里依赖接口而不是实现方便替换。最后一个具体技巧用mysqldump导出脚本时加上--skip-comments和--no-create-db可以去掉注释和建库语句脚本更干净但毕设交付建议保留建库语句别人拿到就能跑。导出前先SET NAMES utf8mb4避免脚本里的中文注释变乱码。我自己踩过的最大教训是别等到答辩前一天才导出数据库脚本那时候数据里全是测试用的脏数据导出前先清一遍或者单独维护一份init.sql只放结构和必要初始数据。希望帮到你。本文还有配套的精品资源点击获取