简介:这是一套基于Servlet+JSP+MySQL实现的JavaWeb学生成绩管理系统升级版,面向Java初学者与Web开发入门学习者,聚焦教学管理场景下的成绩录入、查询、统计与可视化需求。资源包共344个文件,包含86个Java源码(如ScoreDaoImpl、StudentDaoImpl等核心DAO与Service类)、86个编译后class文件、62个JavaScript脚本(含ECharts图表初始化与数据渲染逻辑)、37个JSP页面(涵盖登录、课程管理、成绩录入等完整功能视图)以及28个CSS样式文件,整体压缩包仅3.59MB,轻量易部署。已有6724人学习下载,体现了其在实践教学中的广泛认可。读者可直接运行项目,获得含前后端交互、数据库操作、权限控制及ECharts动态统计图(如各科成绩分布、班级平均分趋势)在内的完整可运行系统,同时通过清晰的分层代码结构(DAO/Service/Servlet/JSP)深入理解JavaWeb经典MVC开发模式。
1. 一个跑在 Tomcat 里的“老派但能打”的学生成绩系统:不靠 Spring Boot,纯 Servlet + JSP + MySQL 搞定增删改查 + ECharts 动态统计图
你可能刚学完 JavaWeb 基础,手头有《Servlet 入门》《JSP 标签详解》《MySQL 建表三板斧》,但合上书就卡在——“这些知识点怎么串成一个能真正在浏览器里点、输、查、看图的完整系统?”这个项目就是那个“串起来”的答案。它不是玩具 Demo:学生信息管理、课程设置、成绩录入、按班级/学期/科目多维查询全都有;它也不是过时古董:ECharts 图表不是静态截图,而是通过 Servlet 后端查库、JSON 返回、前端 JS 渲染的实时柱状图和折线图,点击“查看某班数学成绩分布”,图表立刻刷新。它适合两类人:一是 JavaWeb 初学者想拿一个结构清晰、无框架黑盒、每行代码都可控的练手项目,从 web.xml 配置到 JDBC 连接池再到 JSP EL 表达式,一环扣一环;二是教学场景下需要部署稳定、运维简单、不依赖复杂中间件的轻量级成绩管理工具——Tomcat 一启,MySQL 一连,就能进系统录数据、导 Excel(项目含导出功能)、看趋势图。别被“老技术栈”劝退,恰恰是去掉 Spring 的自动装配、IOC 容器这些抽象层后,你才能真正看清 HTTP 请求怎么进 Servlet、session 怎么存用户登录态、JDBC ResultSet 怎么一步步变成页面上的表格和图表。
2. 项目结构与技术选型:为什么用 Servlet+JSP 而不是 Spring Boot?这不是倒退,是刻意留白
这个项目没用任何现代 Web 框架,所有技术选型都服务于一个目标:让初学者一眼看懂数据流向,且能在低配机器上秒启。它不追求高并发或微服务架构,而追求“改一行代码,刷新浏览器就能看到效果”的确定性。下面拆解它的骨架。
2.1 目录结构:5 个核心文件夹,撑起整个系统
项目采用标准 JavaWeb 工程结构,Maven 管理依赖(pom.xml 中明确声明 servlet-api、mysql-connector-java、jstl),目录如下:
src/ ├── main/ │ ├── java/ │ │ └── com/example/score/ # 所有 Java 类根包 │ │ ├── dao/ # 数据访问层:StudentDao.java、ScoreDao.java 等 │ │ ├── entity/ # 实体类:Student.java、Course.java、Score.java │ │ ├── filter/ # 登录过滤器:LoginFilter.java(拦截未登录访问) │ │ ├── servlet/ # 核心控制器:LoginServlet.java、ScoreListServlet.java、ChartServlet.java │ │ └── util/ # 工具类:DBUtil.java(封装 JDBC 连接)、DateUtil.java │ ├── resources/ │ │ └── db.properties # 数据库配置:url、username、password(明文,开发环境可接受) │ └── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置:servlet 映射、filter 配置、welcome-file-list │ │ └── lib/ # (已弃用)旧式手动放 jar 包方式,本项目用 Maven,此目录为空 │ ├── static/ # 静态资源:echarts.min.js、jquery.min.js、bootstrap.css │ ├── views/ # JSP 页面集中地:login.jsp、student_list.jsp、score_chart.jsp │ └── index.jsp # 默认首页(重定向到 login.jsp)提示:
web.xml是本项目的“总开关”。它定义了LoginServlet映射到/login,ScoreListServlet映射到/score/list,ChartServlet映射到/chart/data。没有@WebServlet注解,所有路由都在这里显式声明——这是理解请求如何被分发的第一步。
2.2 技术栈深度解析:每个选择背后都是教学意图
| 技术组件 | 版本(常见) | 为什么选它? | 初学者能学到什么? |
|---|---|---|---|
| Servlet API | 4.0+(Tomcat 9+) | 作为 JavaWeb 最底层规范,强制你写doGet()/doPost(),亲手处理HttpServletRequest和HttpServletResponse。没有框架帮你自动绑定参数,你得自己调request.getParameter("name")。 | HTTP 协议基础、请求生命周期、参数获取方式、重定向 vs 转发区别(response.sendRedirect()vsrequest.getRequestDispatcher().forward()) |
| JSP + JSTL | JSP 2.3 + JSTL 1.2 | JSP 负责 HTML 模板,JSTL(<c:forEach>、<c:if>)替代 scriptlet(<% %>),让页面逻辑更清晰。<c:out value="${student.name}"/>直接输出变量,避免 XSS 风险。 | MVC 中 View 层职责、EL 表达式语法、标签库使用、HTML 与 Java 逻辑分离思想 |
| MySQL | 5.7 或 8.0 | 开源、轻量、社区教程极多。项目 SQL 脚本(init.sql)包含建库、建表、插入测试数据三步,一键执行即可。 | 基础 SQL(CREATE TABLE、INSERT、JOIN 查询)、外键约束设计(如score.student_id关联student.id)、字符集设置(推荐utf8mb4防 emoji 乱码) |
| ECharts | 5.x(CDN 引入) | 不用打包构建,直接<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>。ChartServlet返回 JSON 格式数据(如{"xAxis": ["一班","二班"], "series": [85, 79]}),前端 JS 用echarts.init()绑定容器,setOption()渲染。 | 前后端数据交互模式(AJAX + JSON)、图表库集成流程、异步请求处理($.get("/chart/data", {classId: 1}, function(data){...})) |
2.3 数据库设计:3 张表讲清关系型建模核心
项目数据库score_db包含三张主表,设计简洁但覆盖典型业务关系:
-- 学生表:主键 id,姓名、学号、班级(class_name 字段,非外键,简化设计) CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, stu_no VARCHAR(20) UNIQUE NOT NULL, class_name VARCHAR(50) NOT NULL ); -- 课程表:主键 id,课程名、学分 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, credit INT DEFAULT 2 ); -- 成绩表:联合主键 (student_id, course_id),外键关联 student 和 course CREATE TABLE score ( student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) NOT NULL CHECK(score >= 0 AND score <= 100), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE );注意:
score表用联合主键而非自增id,这是关系型数据库中“多对多”关系的标准解法(一个学生有多门课成绩,一门课有多个学生成绩)。ON DELETE CASCADE保证删除学生时,其所有成绩记录自动清除,避免脏数据——这是初学者容易忽略但极其重要的数据一致性保障。
3. 核心模块拆解:从登录到图表,一个请求的完整链路
我们以“管理员登录成功后,点击‘成绩统计’菜单,加载班级平均分柱状图”为例,追踪从浏览器点击到图表渲染的每一行关键代码。这条链路覆盖了 Filter、Servlet、DAO、JSP、JS 全流程。
3.1 登录验证:Filter 拦截 + Session 管理
用户在login.jsp输入账号密码,提交到/login(POST)。LoginServlet处理:
// src/main/java/com/example/score/servlet/LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. 简单校验(实际项目应加盐哈希,此处为教学简化) if ("admin".equals(username) && "123456".equals(password)) { // 2. 登录成功:将用户信息存入 session request.getSession().setAttribute("user", username); // 3. 重定向到主页(避免刷新重复提交) response.sendRedirect(request.getContextPath() + "/views/index.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/views/login.jsp").forward(request, response); } }逻辑说明:
request.getSession()获取当前会话对象,setAttribute("user", username)将登录态存入内存。后续所有受保护页面(如成绩列表)都需检查此 session 属性。
3.2 登录过滤器:强制校验,拒绝未授权访问
web.xml中配置了LoginFilter,对/views/*下所有 JSP 页面生效:
// src/main/java/com/example/score/filter/LoginFilter.java public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 1. 获取 session 中的 user 属性 Object user = request.getSession().getAttribute("user"); // 2. 若未登录,重定向到登录页 if (user == null) { response.sendRedirect(request.getContextPath() + "/views/login.jsp"); return; // 阻断后续链路 } // 3. 已登录,放行 chain.doFilter(request, response); }参数说明:
chain.doFilter()是关键,它把请求交给下一个过滤器或最终的 Servlet/JSP。没有这行,请求就停在这里了。这是理解 JavaWeb 过滤器链(Filter Chain)机制的核心。
3.3 图表数据接口:ChartServlet 返回 JSON
点击“成绩统计”菜单,前端 JS 发起 AJAX 请求到/chart/data?class=一班:
// src/main/java/com/example/score/servlet/ChartServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String className = request.getParameter("class"); // 1. 调用 DAO 获取班级各科平均分 List<ChartItem> dataList = ScoreDao.getAvgScoreByClass(className); // 2. 构造 JSON 响应(手动拼接,教学用;生产环境用 Jackson/Gson) StringBuilder json = new StringBuilder(); json.append("{"); json.append("\"xAxis\":["); dataList.forEach(item -> json.append("\"").append(item.getCourseName()).append("\",")); if (!dataList.isEmpty()) json.deleteCharAt(json.length() - 1); // 去掉末尾逗号 json.append("],"); json.append("\"series\":["); dataList.forEach(item -> json.append(item.getAvgScore()).append(",")); if (!dataList.isEmpty()) json.deleteCharAt(json.length() - 1); json.append("]}"); // 3. 设置响应头,返回 JSON response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json.toString()); }逻辑说明:
ScoreDao.getAvgScoreByClass()内部执行 SQL:SELECT c.name as course_name, AVG(s.score) as avg_score FROM score s JOIN student st ON s.student_id = st.id JOIN course c ON s.course_id = c.id WHERE st.class_name = ? GROUP BY c.name
这条 SQL 用到了 JOIN 和 GROUP BY,是数据库多表关联查询的典型范式。DAO 层屏蔽了 JDBC 的繁琐(Connection,PreparedStatement,ResultSet),但你仍能看到 SQL 本身——这是理解数据来源的关键。
3.4 前端图表渲染:ECharts 初始化与数据绑定
score_chart.jsp中引入 ECharts 并初始化:
<!-- src/main/webapp/views/score_chart.jsp --> <div id="chartContainer" style="width: 800px; height: 400px;"></div> <script> // 1. 初始化图表实例 var myChart = echarts.init(document.getElementById('chartContainer')); // 2. 发起 AJAX 请求获取数据 $.get("${pageContext.request.contextPath}/chart/data", {class: "一班"}, function(data) { // 3. 解析 JSON(data 已是 JS 对象) var option = { title: { text: '一班各科平均分' }, tooltip: {}, xAxis: { data: data.xAxis }, // ["语文","数学","英语"] yAxis: {}, series: [{ name: '平均分', type: 'bar', data: data.series // [85.5, 79.2, 88.0] }] }; // 4. 应用配置,渲染图表 myChart.setOption(option); }); </script>关键点:
$.get()的 URL 是${pageContext.request.contextPath}/chart/data,其中contextPath确保路径不硬编码,适配不同部署路径(如部署在/score下,自动补前缀)。这是 JSP 中处理上下文路径的正确姿势。
4. 避坑指南:新手跑通项目必踩的 4 个坑,我替你试过了
这个项目看似简单,但初学者在本地部署时,90% 的失败都集中在以下四个环节。每一个都是我当年在实验室调试到凌晨两点的真实血泪经验。
4.1 现象:启动 Tomcat 后,访问http://localhost:8080显示 404,连登录页都打不开
原因:项目未正确部署到 Tomcat 的webapps目录,或web.xml中welcome-file-list配置错误。常见错误是把index.jsp放在webapp/下,却在web.xml里写成了<welcome-file>index.html</welcome-file>。
解决:
- 确认
webapp/目录下存在index.jsp(内容为<% response.sendRedirect(request.getContextPath()+"/views/login.jsp"); %>); - 检查
web.xml中<welcome-file-list>是否包含<welcome-file>index.jsp</welcome-file>; - Maven 打包后,确认生成的 WAR 包(如
score.war)已放入 Tomcatwebapps/目录,并重启 Tomcat(不要只刷新浏览器)。
4.2 现象:登录成功后,点击任何菜单都跳回login.jsp
原因:LoginFilter配置的 URL 模式不匹配,或getSession()创建了新会话。最常见的是web.xml中 filter-mapping 的url-pattern写成了/views/*,但实际 JSP 路径是/views/student_list.jsp—— 看似匹配,实则因 Tomcat 版本差异,部分版本要求精确匹配。
解决:
- 将
filter-mapping的url-pattern改为/*(全局拦截),并在doFilter方法开头添加白名单判断:String uri = request.getRequestURI(); if (uri.endsWith("/login.jsp") || uri.endsWith("/login")) { chain.doFilter(request, response); // 放行登录相关路径 return; } - 在
LoginServlet中,确保request.getSession(true)显式创建会话(true表示不存在则新建),避免某些环境下getSession()返回 null。
4.3 现象:成绩列表页面显示空白表格,控制台无报错,但student_list.jsp中<c:forEach>循环没执行
原因:JSTL 标签库未正确引入。pom.xml中虽有jstl依赖,但 JSP 页面顶部缺少taglib声明,或声明了却用错了 URI。
解决:
- 在
student_list.jsp顶部必须有:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - 确认
pom.xml中依赖为:
(注意:不是<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>jakarta.servlet.jsp.jstl,那是 Jakarta EE 9+ 新命名空间,本项目基于传统 JavaEE)
4.4 现象:ECharts 图表容器显示一片空白,浏览器开发者工具 Network 标签页中/chart/data请求返回 500 错误
原因:ChartServlet中 SQL 查询抛出异常(如ClassNotFoundException找不到 MySQL 驱动,或SQLException连接失败),但代码未捕获,导致 500。根本原因是db.properties配置错误或 MySQL 服务未启动。
解决:
- 检查
src/main/resources/db.properties:url=jdbc:mysql://localhost:3306/score_db?useSSL=false&serverTimezone=Asia/Shanghai username=root password=your_password # 必须填真实密码! - 确保 MySQL 服务正在运行(Windows 服务管理器 / macOS
brew services list); - 在
ChartServlet的doGet方法中,用try-catch包裹 DAO 调用,并打印异常:try { List<ChartItem> dataList = ScoreDao.getAvgScoreByClass(className); // ... 构造 JSON } catch (Exception e) { e.printStackTrace(); // 输出到 Tomcat logs/catalina.out response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "图表数据加载失败:" + e.getMessage()); }
5. 二次开发实战:给系统加一个“导出 Excel”功能,30 分钟搞定
现在你已经跑通了系统,下一步往往是“老板说,这个成绩表要能导出 Excel”。这正是检验你是否真正吃透项目结构的好机会。下面带你从零添加导出功能,不依赖 Apache POI 复杂 API,用最轻量的方式实现。
5.1 需求分析:导出什么?谁来触发?格式要求?
- 导出内容:当前成绩列表页(
/score/list)展示的所有数据,即学生姓名、学号、班级、课程名、成绩; - 触发方式:在
student_list.jsp表格上方加一个“导出 Excel”按钮,点击后下载.xls文件; - 格式要求:纯文本 CSV 格式(兼容 Excel),用逗号分隔,中文用 UTF-8 编码,首行为表头。
为什么选 CSV 而不是
.xlsx?因为无需引入 POI 依赖,Servlet直接写response.getOutputStream()即可,代码少、学习成本低、100% 兼容所有 Excel 版本。这是教学项目最务实的选择。
5.2 后端:新增 ExportServlet,流式写入 CSV
新建ExportServlet.java:
// src/main/java/com/example/score/servlet/ExportServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置响应头:告知浏览器这是附件,文件名为 score_export.csv response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=\"score_export.csv\""); // 2. 获取输出流 PrintWriter out = response.getWriter(); // 3. 写入 CSV 表头(UTF-8 BOM 头,确保 Excel 正确识别中文) out.write("\uFEFF"); // UTF-8 BOM out.write("姓名,学号,班级,课程名,成绩\n"); // 4. 查询所有成绩数据(复用现有 ScoreDao) List<Score> scoreList = ScoreDao.getAllScores(); // 5. 遍历写入每行数据(简单转义:逗号、换行符用双引号包裹) for (Score score : scoreList) { String row = String.format( "\"%s\",\"%s\",\"%s\",\"%s\",\"%.2f\"", escapeCsvField(score.getStudentName()), escapeCsvField(score.getStuNo()), escapeCsvField(score.getClassName()), escapeCsvField(score.getCourseName()), score.getScore() ); out.write(row + "\n"); } } // CSV 字段转义工具方法 private String escapeCsvField(String field) { if (field == null) return ""; // 若字段含逗号、双引号或换行符,则用双引号包裹,并将内部双引号替换为两个双引号 if (field.contains(",") || field.contains("\"") || field.contains("\n")) { return "\"" + field.replace("\"", "\"\"") + "\""; } return field; }逻辑说明:
response.setContentType("text/csv;charset=UTF-8")告诉浏览器这是 CSV 文本;Content-Disposition头强制触发下载;\uFEFF是 UTF-8 BOM,没有它,Excel 打开中文 CSV 会乱码。escapeCsvField()是关键,它处理 CSV 的经典转义规则——这是很多新手导出后 Excel 里数据错位的根源。
5.3 前端:在 JSP 中添加导出按钮并绑定事件
修改student_list.jsp,在表格上方添加按钮:
<!-- src/main/webapp/views/student_list.jsp --> <div style="margin-bottom: 15px;"> <button onclick="exportToExcel()" class="btn btn-primary">导出 Excel</button> </div> <script> function exportToExcel() { // 1. 构造导出 URL(带当前筛选条件,如班级) var classFilter = "${param.class}"; // 如果 URL 有 ?class=一班,则取值 var url = "${pageContext.request.contextPath}/export?class=" + encodeURIComponent(classFilter); // 2. 创建隐藏 iframe 触发下载(避免页面跳转) var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = url; document.body.appendChild(iframe); } </script>为什么用
iframe而不是window.location.href?因为后者会跳转页面,破坏当前列表页状态。iframe在后台静默加载,完美模拟“点击下载”体验。
5.4 配置:注册 Servlet 到 web.xml
在web.xml的<servlet>和<servlet-mapping>区域添加:
<servlet> <servlet-name>ExportServlet</servlet-name> <servlet-class>com.example.score.servlet.ExportServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ExportServlet</servlet-name> <url-pattern>/export</url-pattern> </servlet-mapping>5.5 验证与收尾:测试导出效果
- 重新编译部署项目;
- 登录后进入成绩列表页(如
http://localhost:8080/score/views/student_list.jsp); - 点击“导出 Excel”按钮;
- 浏览器弹出下载对话框,保存为
score_export.csv; - 用 Excel 打开,确认表头和数据完整、中文正常、无错位。
从那以后我每次给教学项目加导出功能,都强制走一遍“CSV + BOM + iframe”三件套。它比 POI 简单十倍,出错率趋近于零,而且学生能一眼看懂每一行代码在干什么——这才是教学项目的终极价值。希望帮到你。
本文还有配套的精品资源,点击获取