☰
JavaEE学生成绩管理系统源码解析:JSP+Servlet+JDBC实现与避坑指南
2026/10/1 18:28:00 网站建设 项目流程

简介:这是一套基于JSP、Servlet、JDBC与MySQL技术栈实现的学生成绩管理系统完整源码包,面向JavaWeb课程设计、毕业设计及JavaEE入门练手的开发者。系统划分学生、教师、管理员三种角色:学生可完成考勤、请假、选课、成绩查询与个人信息修改;教师在此基础上增加请假审核、选课与班级课程信息管理、成绩录入及导入导出、可视化成绩统计;管理员则额外拥有教师管理权限。代码中引入MD5加密算法,注释详尽,便于理解业务逻辑与二次开发。压缩包共93个文件,约1.7MB,包含24个java源文件、24个class、21个jsp页面、6个jar依赖,以及sql建库脚本、properties配置、xml描述与doc说明文档,覆盖从数据库到前端的完整结构。资源对JDK、Tomcat与MySQL版本要求宽松,推荐JDK1.8、Tomcat8.0与MySQL8.0以下,Eclipse或IDEA均可导入运行。目前已有1487人学习,适合作为课程实践与项目参考。

1. 从一份 10072 的 JavaEE 成绩管理系统压缩包说起

如果你正在做 Java 课程设计,或者需要一套能跑通「学生—教师—管理员」三角色的成绩管理系统,这个名为10072-javaEE学生成绩管理系统(源码+数据库+文档).rar的资源值得先看一眼。它基于 JSP + Servlet + JDBC + MySQL 实现,内置 MD5 加密,包含学生、教师、管理员三个角色,功能覆盖考勤、请假、选课、成绩录入、成绩导入导出和可视化统计。压缩包里除了src、WebContent、.classpath、.project这些 Eclipse 工程文件,还附带了ssms.sql建库脚本和一份代码说明.doc。对新手来说,它是一份能照着复现的课程设计案例;对熟手来说,它是一套可以拆开看 Servlet 分层和 JDBC 封装的参考代码。下面我按「先跑起来、再看结构、最后避坑」的顺序,把这份资源拆开讲清楚。

2. 环境搭建与数据库初始化:把 ssms.sql 跑进 MySQL

2.1 为什么推荐 JDK 1.8 + Tomcat 8 + MySQL 5.7

这套代码用的是 JSP + Servlet 原生技术栈,没有 Spring Boot 那种自动装配,所以版本兼容性反而更宽松。但宽松不等于随便选,我一般会按下面这个组合来配,翻车概率最低。

组件推荐版本说明
JDK1.8代码里用了javax.servlet包,JDK 8 对老项目兼容最好
Tomcat8.0 / 8.5Servlet 3.1 规范,和代码里的web.xml配置匹配
MySQL5.7驱动包mysql-connector-java版本较老,5.7 最稳
IDEEclipse / IDEA工程自带.project和.classpath,Eclipse 导入更顺

摘要里写「MySQL 推荐 8.0 以下」,这不是随口说的。MySQL 8.0 默认改了认证插件为caching_sha2_password,而老版本驱动包连不上,会报Unable to load authentication plugin。如果你只有 8.0,要么换驱动包,要么在 MySQL 里执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';把认证方式改回去。

2.2 导入工程与配置数据库连接

Eclipse 导入步骤不复杂,但有几个地方容易点错。先解压压缩包,然后在 Eclipse 里File → Import → General → Existing Projects into Workspace,选中解压后的根目录。导入后如果项目上有红叉,大概率是 JDK 版本没对上,右键项目Properties → Java Build Path → Libraries,把 JRE 改成 1.8。

数据库连接配置一般在src下的工具类里,常见命名是DBUtil.java或BaseDao.java。打开后你会看到类似下面的代码:

// 数据库连接工具类,通常位于 src/com/xxx/util/DBUtil.java public class DBUtil { // 注意:这里的库名要和 ssms.sql 里创建的库名一致 private static final String URL = "jdbc:mysql://localhost:3306/ssms?useUnicode=true&characterEncoding=utf-8&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "你的数据库密码"; public static Connection getConnection() throws SQLException { try { // 加载驱动,老项目必须显式加载 Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段代码的逻辑很直白:Class.forName把驱动注册进 DriverManager,然后拿连接。参数上重点看三个地方——3306是你 MySQL 的端口,ssms是库名,characterEncoding=utf-8保证中文不乱码。如果你本地 MySQL 端口不是 3306,或者库名不是ssms,这里必须同步改。useSSL=false是关掉 SSL 警告,老驱动不写这句会在控制台刷一堆红字。

2.3 执行 ssms.sql 并验证表结构

建库脚本ssms.sql在压缩包根目录。我一般用命令行导入,比图形化工具少踩编码的坑:

# 先登录 MySQL mysql -u root -p # 创建数据库并指定字符集 CREATE DATABASE ssms DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; # 退出后导入 sql 文件 exit mysql -u root -p ssms < /path/to/ssms.sql

导入完成后,用SHOW TABLES;看一眼。正常应该能看到学生表、教师表、管理员表、课程表、选课表、成绩表、考勤表、请假表这些。如果表数量明显偏少,说明 sql 文件没导全,检查一下是不是在导入时中途报错被截断了。常见报错是Unknown collation: 'utf8mb4_0900_ai_ci',这是 sql 文件在 MySQL 8.0 环境下导出、却拿到 5.7 里导入导致的,把 sql 文件里的utf8mb4_0900_ai_ci全部替换成utf8_general_ci再导一次即可。

2.4 部署到 Tomcat 并跑通登录

数据库通了之后,把项目部署到 Tomcat。Eclipse 里右键项目Run As → Run on Server,选你配好的 Tomcat 8。启动后访问http://localhost:8080/项目名/,一般会跳到登录页。

登录页通常有三个角色切换:学生、教师、管理员。初始账号密码在ssms.sql的INSERT语句里,或者代码说明.doc里有写。如果登录报NullPointerException,先看控制台是不是数据库连接失败;如果提示密码错误,注意这套系统用了 MD5 加密,数据库里存的是 32 位密文,你输入的明文会被 MD5 后再比对。想手动加账号,得先把明文转成 MD5 再插库。

3. 三角色功能拆解:Servlet 分层与 MD5 登录逻辑

3.1 学生端:选课、成绩查询与考勤

学生端的功能在摘要里列得很清楚:考勤管理、请假、选课、查询成绩、修改个人信息。从代码结构看,这些功能基本是一个 Servlet 对应一个模块,比如CourseServlet处理选课,ScoreServlet处理成绩查询。

选课的逻辑值得单独说一下。它不是简单地把课程 ID 插进选课表,通常会先查这门课是否已选、是否超过容量、是否和时间冲突。你可以在CourseServlet的addCourse方法里看到类似这样的判断:

// 选课前的校验逻辑,伪代码形式 int count = courseDao.getSelectedCount(courseId); if (count >= course.getCapacity()) { request.setAttribute("msg", "该课程已满"); return "course_list.jsp"; } if (selectionDao.isSelected(studentId, courseId)) { request.setAttribute("msg", "你已经选过这门课"); return "course_list.jsp"; } selectionDao.add(studentId, courseId);

这段逻辑说明选课不是无脑插入,而是有容量和重复校验的。参数上,capacity来自课程表,studentId从 session 里取。如果你测试时发现选课按钮点了没反应,先看course_list.jsp里表单的action路径对不对,再看 Servlet 的@WebServlet注解或web.xml映射是否匹配。

成绩查询一般是按学期或按课程筛选,SQL 里会关联成绩表、课程表和学生表。这部分代码在ScoreDao里,用的是原生 JDBC 拼接 SQL,没有用 ORM 框架。好处是逻辑透明,坏处是 SQL 写错不容易发现,建议在executeQuery前后打日志。

3.2 教师端:成绩导入导出与可视化统计

教师端比学生端多了一堆管理功能:考勤管理、请假审核、选课信息管理、学生信息管理、班级管理、课程管理、成绩管理,还有成绩导入导出和可视化统计。这里面最值得看的是成绩导入导出和统计图表。

成绩导入通常支持 Excel 文件。代码里大概率用了 Apache POI 或者 jxl。如果是 POI,你会看到WorkbookFactory.create(inputStream)这样的调用。导入时最容易出问题的是列顺序和数据类型——比如成绩列里混了空行或者文本,getNumericCellValue()就会抛异常。稳妥的做法是逐行读、逐列判类型:

// 成绩导入的核心逻辑,基于 Apache POI Workbook workbook = WorkbookFactory.create(file.getInputStream()); Sheet sheet = workbook.getSheetAt(0); for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) continue; // 学号列按字符串读,避免数字被科学计数法截断 String studentNo = row.getCell(0).getStringCellValue().trim(); // 成绩列可能是数字也可能是文本,做兼容 Cell scoreCell = row.getCell(2); double score; if (scoreCell.getCellType() == CellType.NUMERIC) { score = scoreCell.getNumericCellValue(); } else { score = Double.parseDouble(scoreCell.getStringCellValue().trim()); } scoreDao.insertOrUpdate(studentNo, courseId, score); }

这段代码的关键点有两个:学号必须按字符串读,否则 Excel 里长数字会被转成科学计数法;成绩列要做类型兼容,因为老师填表时可能把成绩写成文本。参数上,getLastRowNum()返回的是最后一行的索引,循环从 1 开始是跳过表头。

可视化统计一般是把成绩按分数段分组,然后用 ECharts 或 Chart.js 在前端画柱状图。后端 SQL 类似SELECT COUNT(*) FROM score WHERE score BETWEEN 60 AND 70,前端拿到数据后渲染。如果图表不显示,先看浏览器控制台有没有 JS 报错,再看后端返回的 JSON 格式对不对。

3.3 管理员端:教师管理与权限控制

管理员端比教师端多一个教师管理,本质上是多了一张教师表的增删改查。权限控制这块,这套代码用的是 session 里存角色字段,然后在 JSP 或 Servlet 里判断。比如:

// 权限拦截的常见写法 Object role = session.getAttribute("role"); if (role == null || !"admin".equals(role.toString())) { response.sendRedirect("login.jsp"); return; }

这种写法简单直接,但每个需要权限的 Servlet 都要写一遍,容易漏。如果你要二次开发,建议抽一个BaseServlet或者用 Filter 统一拦截。参数上,role的值一般是student、teacher、admin三种,和登录时存入 session 的值保持一致。

3.4 MD5 加密在登录与注册中的位置

摘要里特意提到「加入了 MD5 的加密算法」,这在老课程设计里很常见。MD5 是不可逆摘要,登录时把用户输入的明文转成密文,再和数据库里的密文比对。代码里一般有一个MD5Util工具类:

// MD5 工具类,通常位于 src/com/xxx/util/MD5Util.java public static String md5(String plainText) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(plainText.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : digest) { // 转成两位十六进制,不足补零 sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } }

注意%02x和b & 0xff这两个细节。b & 0xff是把 byte 转成无符号整数,否则负数会出问题;%02x保证每个字节输出两位十六进制。如果你自己写注册功能,存库前必须调一次md5(),否则登录永远对不上。另外,MD5 现在已经被认为不安全,但作为课程设计了解原理没问题,生产环境应该用 BCrypt 或 SHA-256 加盐。

4. 避坑与排查:这套老代码最容易翻车的五个地方

4.1 中文乱码:从 JSP 到数据库的全链路

现象:页面输入中文,存进数据库变成???,或者查询出来是乱码。

原因:乱码可能出现在三个环节——JSP 页面编码、Servlet 请求编码、数据库连接编码。这套代码里 JSP 一般有<%@ page contentType="text/html;charset=UTF-8" %>,但 Servlet 的request.setCharacterEncoding("UTF-8")容易漏。

解决:先确认数据库和表的字符集是utf8,再检查 JDBC URL 里有没有characterEncoding=utf-8,最后在 Servlet 取参数前加request.setCharacterEncoding("UTF-8")。如果是 POST 请求,这三步基本能解决;GET 请求还要看 Tomcat 的server.xml里Connector有没有URIEncoding="UTF-8"。

4.2 驱动包版本与 MySQL 8.0 不兼容

现象:启动时报com.mysql.jdbc.Driver找不到,或者连接时报Unable to load authentication plugin 'caching_sha2_password'。

原因:老项目用的是mysql-connector-java-5.1.x,驱动类名是com.mysql.jdbc.Driver;MySQL 8.0 默认认证插件变了,老驱动不认。

解决:要么把 MySQL 降到 5.7,要么换mysql-connector-java-8.0.x驱动包,并把驱动类名改成com.mysql.cj.jdbc.Driver,URL 里加上serverTimezone=Asia/Shanghai。改完记得把WEB-INF/lib下的旧驱动删掉,避免冲突。

4.3 成绩导入时 Excel 数字被截断

现象:导入成绩后,学号变成1.20201E+11这种科学计数法,或者成绩小数位丢失。

原因:POI 默认按数字类型读单元格,长数字和学号会被转成 double。

解决:读学号时用getStringCellValue(),或者用DataFormatter统一转字符串。如果单元格本身是数字格式,可以先cell.setCellType(CellType.STRING)再读。成绩列如果允许小数,用getNumericCellValue()没问题,但要注意精度。

4.4 部署后 404:路径与项目名不匹配

现象:Tomcat 启动正常,但访问http://localhost:8080/报 404。

原因:项目名和访问路径不一致,或者web.xml里welcome-file配置的首页不存在。

解决:在 Eclipse 里右键项目Properties → Web Project Settings,看Context root是什么,访问时就要用那个名字。比如 Context root 是ssms,访问地址就是http://localhost:8080/ssms/。如果还 404,检查WebContent下有没有index.jsp或login.jsp,以及web.xml里的欢迎页配置。

4.5 登录成功但功能页空白

现象:登录后跳转到主页,但菜单点进去是空白页或者报 500。

原因:常见的是 session 过期、数据库查询异常被吞掉、或者 JSP 里引用的 JavaBean 属性名写错。

解决:先看 Tomcat 控制台有没有异常堆栈,如果有NullPointerException,顺着堆栈找到具体行。如果是 JSP 空白,右键页面查看源代码,看是不是forward或redirect的路径写错了。还有一种情况是数据库里对应角色的表没有数据,查询返回空列表,页面没做空判断就直接渲染了。

5. 二次开发与验证:从跑通到改出自己的一套

5.1 先做一次完整的功能回归

拿到这套代码,别急着改。先按角色把主流程走一遍:学生登录→选课→查成绩→请假;教师登录→审核请假→录入成绩→导出成绩→看统计图;管理员登录→增删教师→改密码。每走一步,打开浏览器 F12 看 Network 里请求的 URL 和返回状态码,同时看 Tomcat 控制台有没有异常。这一步的目的是建立「正常状态」的基线,后面改出问题才知道是哪里引入的。

我一般会准备一个checklist.md,把每个角色的核心功能列出来,跑通一个勾一个。这套系统功能点不少,光教师端就有七八个模块,不列清单很容易漏测。

5.2 把 JDBC 工具类改成连接池

原生 JDBC 每次请求都新建连接,性能差,而且连接数一多就报Too many connections。如果你要把这套代码用到稍微正式一点的场景,建议把DBUtil改成 Druid 或 HikariCP 连接池。以 Druid 为例,加一个druid.properties:

# druid.properties 放在 src 根目录 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/ssms?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=你的密码 initialSize=5 maxActive=20 maxWait=3000

然后把DBUtil.getConnection()改成从DruidDataSource里取:

// 改造后的 DBUtil,使用 Druid 连接池 public class DBUtil { private static DruidDataSource dataSource; static { try { Properties props = new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

改完之后,所有 DAO 里的DBUtil.getConnection()不用动,但连接的创建和回收交给连接池管理。参数上,initialSize是初始连接数,maxActive是最大连接数,maxWait是获取连接的超时时间(毫秒)。学生做课程设计,initialSize=5、maxActive=20足够了。

5.3 用 Filter 统一处理编码和权限

前面提到权限判断散落在各个 Servlet 里,容易漏。可以加一个 Filter,在web.xml或注解里配置拦截路径:

// 编码与权限统一过滤器 @WebFilter("/*") public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 统一编码 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String uri = request.getRequestURI(); // 放行登录页、静态资源和登录接口 if (uri.endsWith("login.jsp") || uri.endsWith("LoginServlet") || uri.contains("/static/")) { chain.doFilter(req, resp); return; } // 未登录一律跳登录页 if (request.getSession().getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }

这个 Filter 做了两件事:统一设置请求和响应编码,以及拦截未登录请求。参数上,@WebFilter("/*")表示拦截所有路径,放行规则按你的项目结构调整。注意登录接口的路径要和实际 Servlet 映射一致,否则会把登录请求也拦掉,导致死循环。

5.4 验证改动是否生效的三个手段

改完代码怎么确认没改坏?我一般用三个手段交叉验证。第一,看 Tomcat 启动日志有没有SEVERE级别的报错;第二,用浏览器开发者工具的 Network 面板看关键请求的响应码和返回内容;第三,直接连数据库查数据,确认增删改查真的落库了。比如你改了成绩导入逻辑,就导入一个包含边界数据的 Excel——空行、文本成绩、超长学号——看数据库里存进去的是不是符合预期。

还有一个习惯:每次改完一个模块,就把这个模块的入口 URL 记在一个urls.md里,下次回归测试直接点,不用再翻菜单。这套系统角色多、菜单深,不记 URL 很容易迷路。

5.5 从这套代码里能带走什么

这套10072-javaEE学生成绩管理系统的价值不在于它有多先进,而在于它完整。JSP + Servlet + JDBC 这套技术栈虽然老,但它是理解 Java Web 请求流转的最好样本——从浏览器发请求,到 Servlet 处理,到 DAO 查库,再到 JSP 渲染,整条链路没有框架帮你藏起来。你把它跑通、改一遍,再去看 Spring MVC 的 DispatcherServlet,会发现很多概念是相通的。

我自己的习惯是,每拆完一套老代码,就把它的分层结构画在一张纸上,标出哪些是控制层、哪些是业务层、哪些是数据层。这套代码画完你会发现,它的 Servlet 既当控制器又当业务逻辑,DAO 只负责 SQL,典型的 Model1 向 Model2 过渡的写法。知道它「不完美」在哪,比知道它「能跑」更有用。从那以后我每次拿到这种课程设计源码,都强制自己先跑通主流程、再画分层图、最后挑一个模块重写,希望这套方法也能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询