JSP+Servlet学生成绩管理系统实战:从部署到优化

发布时间:2026/10/7 23:39:07
JSP+Servlet学生成绩管理系统实战:从部署到优化 简介这是一套面向计算机相关专业学生与Java Web初学者的学生成绩管理系统源码采用jspservlet技术栈搭配MySQL数据库实现适合用作毕业设计、课程设计或Web入门练手项目。压缩包共4个文件约6.33MB包含项目源码压缩包、数据库脚本、依赖库压缩包以及一份使用说明文档其中sql文件用于快速导入建表数据docx文档则交代了环境配置与运行要点源码经本地编译验证可直接运行。目前已有109人学习下载项目难度适中内容经过助教老师审定能够满足学习与使用需求。读者可借此掌握Servlet请求处理、JSP页面渲染、数据库连接与增删改查等核心技能并参考其分层目录结构理解典型MVC组织方式遇到问题还可私信作者获取解答。1. 从一份 jspservlet 学生成绩管理系统.zip 说起它到底能解决什么如果你手里正躺着一份基于jspservlet的学生成绩管理系统.zip大概率是三种情况之一课程设计要交、毕业设计要改、或者面试前想找个能跑起来的 Java Web 项目练手。这个标题背后其实是一套非常经典的组合——JSP 负责页面渲染Servlet 负责请求分发和业务调度JDBC 直连数据库整体跑在 Tomcat 上。它不新但它是理解 Java Web 请求生命周期最直白的一条路浏览器发请求Servlet 接住调 DAO 查库把结果塞进 request 域转发给 JSP 渲染成 HTML 回来。它适合谁适合刚学完 Servlet 生命周期、想找一个完整 CRUD 闭环的人适合需要一份能演示「登录、录入成绩、查询成绩、统计排名」的课程作业的人也适合想拿它当骨架后面自己换成 Spring Boot 的人。不适合谁不适合想直接拿它上生产的人——这套架构在并发、连接池、SQL 注入防护上都需要你自己补课。下面我按「先跑起来、再拆结构、再改功能、最后避坑」的顺序把这份 zip 拆开讲清楚。2. 把 zip 跑起来环境、建库、部署三步走2.1 先确认你的 JDK、Tomcat、MySQL 版本能对上这类项目最常见的翻车点不是代码是版本。JSPServlet 项目对 JDK 和 Tomcat 的匹配很敏感尤其是javax.servlet和jakarta.servlet这两个包名分水岭。老项目基本都用javax.*对应 Tomcat 8/9如果你直接扔进 Tomcat 10启动就报ClassNotFoundException: javax.servlet.http.HttpServlet这不是代码错是包名换了。我一般会先按这个组合配组件推荐版本说明JDK8 或 11JDK 8 最稳老项目兼容性最好Tomcat8.5 或 9.0用javax.servlet别上 Tomcat 10MySQL5.7 或 8.08.0 要改驱动类和 URL 参数驱动 jarmysql-connector-java 5.1.x / 8.0.x放WEB-INF/lib下IDEEclipse / IDEA能配 Tomcat 就行确认完版本先别急着导入项目把数据库建起来。项目里一般会有一个.sql文件或者 README 里写了建库语句。找不到就自己按实体建学生表、成绩表、用户表这三张是跑不掉的。-- 建库字符集用 utf8mb4避免中文姓名乱码 CREATE DATABASE student_score DEFAULT CHARACTER SET utf8mb4; USE student_score; -- 用户表登录用 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role VARCHAR(20) DEFAULT student -- admin / teacher / student ); -- 学生表 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE, -- 学号 name VARCHAR(50) NOT NULL, class_name VARCHAR(50) ); -- 成绩表一个学生多门课 CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL, course VARCHAR(50) NOT NULL, score DECIMAL(5,1), INDEX idx_sno (sno) ); -- 插一条管理员密码明文仅用于本地跑通后面必须改 INSERT INTO t_user (username, password, role) VALUES (admin, 123456, admin);建表时注意sno加了唯一索引成绩表用sno关联而不是学生表主键 id这是这类项目最常见的关联方式改的时候别改错字段。字符集一定要utf8mb4否则中文姓名和课程名会变问号这是血泪经验。2.2 改数据库连接一个 DBUtil 决定项目能不能连上库项目里连接数据库的代码通常集中在一个DBUtil或BaseDao类里。你要改的就三样URL、用户名、密码。MySQL 8.0 和 5.7 的 URL 写法不一样驱动类名也不一样这是新手最容易卡住的地方。// DBUtil.java —— 数据库连接工具类 public class DBUtil { // MySQL 5.7 写法 // private static final String URL jdbc:mysql://localhost:3306/student_score?useUnicodetruecharacterEncodingutf8; // private static final String DRIVER com.mysql.jdbc.Driver; // MySQL 8.0 写法多了时区参数驱动类带 cj private static final String URL jdbc:mysql://localhost:3306/student_score?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); // 加载驱动只执行一次 } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源顺序不能反ResultSet - Statement - Connection public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }serverTimezoneAsia/Shanghai不加会报时区错误useSSLfalse不加会有警告这两个参数是 MySQL 8.0 的标配。关闭顺序必须是 ResultSet 先关再 Statement最后 Connection反了会出问题。这个类是整个项目的黑匣子连不上库先看它。2.3 部署到 Tomcat导入、配 Artifact、启动看日志Eclipse 和 IDEA 的导入方式不同但核心就一件事让 Tomcat 认识这个 Web 项目。Eclipse 里用File - Import - Existing Projects into Workspace导入后右键项目Properties - Project Facets确认 Dynamic Web Module 勾上。IDEA 里用File - New - Project from Existing Sources然后Project Structure - Modules里把 Web Facet 加上Artifacts里建一个Web Application: Exploded。配好之后启动 Tomcat看控制台日志。启动成功的标志是看到Server startup in xxx ms。如果报ClassNotFoundException检查WEB-INF/lib下驱动 jar 在不在如果报 404检查访问路径和web.xml里的url-pattern对不对如果报 500 且堆栈指向数据库回到 2.2 检查连接参数。这一步没有捷径就是看日志、对配置。3. 拆开看结构Servlet 怎么接请求JSP 怎么渲染3.1 一个登录请求的完整链路从 login.jsp 到 main.jsp理解这个项目最好的切入点是登录。它把 Servlet 的请求处理、Session 管理、JSP 转发全串起来了。链路是这样的用户在login.jsp填账号密码表单提交到LoginServletServlet 调 DAO 查库比对成功就把用户信息放进 Session然后转发到主页面。// LoginServlet.java WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // 必须在取参数前设置否则中文乱码 String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(currentUser, user); // 登录态存 Session resp.sendRedirect(req.getContextPath() /main.jsp); // 重定向防止刷新重复提交 } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); // 转发保留提示信息 } } }这里有两个关键点。第一setCharacterEncoding必须在getParameter之前调用顺序反了中文就是乱码这是 JSP 项目最经典的坑。第二登录成功用sendRedirect失败用forward。重定向会改变浏览器地址栏刷新不会重复提交表单转发是服务器内部跳转能把msg带到页面上。很多人这里写反导致登录后刷新就重复登录。3.2 DAO 层怎么写PreparedStatement 和结果集映射Servlet 不直接写 SQL中间隔一层 DAO。这层的作用是把数据库操作封装起来Servlet 只管调方法。写 DAO 的核心是两件事用PreparedStatement防注入把ResultSet映射成对象。// UserDao.java public class UserDao { public User findByUsernameAndPassword(String username, String password) { String sql SELECT id, username, role FROM t_user WHERE username ? AND password ?; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); ps.setString(1, username); // 占位符从 1 开始不是 0 ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRole(rs.getString(role)); return u; } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return null; } }PreparedStatement的占位符索引从 1 开始这是新手常错的地方。查询时只SELECT需要的字段别用SELECT *字段顺序变了映射就错。finally里必须关资源否则连接池很快耗尽。这套写法虽然啰嗦但它是理解后面 Spring JdbcTemplate 的基础。3.3 JSP 页面渲染JSTL 循环输出成绩列表JSP 里最忌讳写 Java 脚本片段% %正确做法是用 JSTL 和 EL 表达式。Servlet 把ListScore放进 requestJSP 用c:forEach遍历输出。这样页面和逻辑分离改样式不用动 Java 代码。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth学号/thth课程/thth成绩/th/tr c:forEach items${scoreList} vars tr td${s.sno}/td td${s.course}/td td${s.score}/td /tr /c:forEach /table用 JSTL 要在WEB-INF/lib下放jstl.jar和standard.jar并在页面顶部声明 taglib。${scoreList}对应 Servlet 里req.setAttribute(scoreList, list)的 key名字必须一致。如果页面显示空白先检查 list 是不是 null再检查 key 拼写。这套 ELJSTL 的组合就是后来 Thymeleaf、Vue 模板语法的前身。4. 改功能成绩录入、查询、统计排名怎么落地4.1 成绩录入表单提交到 Servlet 再到 INSERT录入成绩的链路和登录类似但多了数据校验。表单提交学号、课程、分数Servlet 接住后先校验分数是不是数字、在不在 0 到 100 之间再调 DAO 插入。// ScoreServlet.java 的 doPost 片段 String sno req.getParameter(sno); String course req.getParameter(course); String scoreStr req.getParameter(score); // 服务端校验别只靠前端 if (sno null || sno.trim().isEmpty() || scoreStr null) { req.setAttribute(msg, 学号和成绩不能为空); req.getRequestDispatcher(/score_add.jsp).forward(req, resp); return; } double score; try { score Double.parseDouble(scoreStr); if (score 0 || score 100) throw new NumberFormatException(); } catch (NumberFormatException e) { req.setAttribute(msg, 成绩必须是 0-100 的数字); req.getRequestDispatcher(/score_add.jsp).forward(req, resp); return; } ScoreDao dao new ScoreDao(); dao.insert(new Score(sno, course, score)); resp.sendRedirect(req.getContextPath() /score?actionlist);前端校验能被绕过服务端必须再校验一遍这是安全底线。分数用double接但数据库存DECIMAL(5,1)插入时 JDBC 会自动转换。插入成功后重定向到列表页避免刷新重复插入。这个「校验-插入-重定向」的模式是所有写操作的通用套路。4.2 成绩查询多条件拼接 SQL 的正确姿势查询比录入复杂因为条件是可选的。学号、课程、分数区间用户可能只填一个。拼接 SQL 时不能直接字符串拼接要用PreparedStatement动态拼占位符。// ScoreDao.java 的条件查询 public ListScore query(String sno, String course) { StringBuilder sql new StringBuilder(SELECT sno, course, score FROM t_score WHERE 11); ListObject params new ArrayList(); if (sno ! null !sno.trim().isEmpty()) { sql.append( AND sno ?); params.add(sno); } if (course ! null !course.trim().isEmpty()) { sql.append( AND course LIKE ?); params.add(% course %); } ListScore list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); // 索引从 1 开始 } ResultSet rs ps.executeQuery(); while (rs.next()) { list.add(new Score(rs.getString(sno), rs.getString(course), rs.getDouble(score))); } } catch (SQLException e) { e.printStackTrace(); } return list; }WHERE 11是个小技巧让后面的AND拼接不用判断是不是第一个条件。参数用ListObject收集循环setObject时索引从 1 开始。模糊查询用LIKE加%注意%是拼在参数值里不是拼在 SQL 里。这套动态 SQL 写法就是 MyBatisif标签的手写版。4.3 统计排名用 SQL 聚合函数而不是在 Java 里循环统计平均分、排名这类需求很多人第一反应是在 Java 里遍历 List 算。正确做法是交给数据库用AVG、SUM、ORDER BY一次查出来。数据量大时Java 循环会拖垮内存。-- 每个学生的总分和平均分按总分降序排名 SELECT sno, SUM(score) AS total, AVG(score) AS avg_score, COUNT(*) AS course_count FROM t_score GROUP BY sno ORDER BY total DESC; -- 每门课的班级平均分 SELECT course, AVG(score) AS avg_score, MAX(score) AS max_score, MIN(score) AS min_score FROM t_score GROUP BY course;GROUP BY后面跟的字段必须是SELECT里非聚合的字段这是 SQL 标准。排名用ORDER BY total DESC如果要显示名次序号可以在 Java 里遍历时用下标加一或者用 MySQL 8.0 的RANK()窗口函数。统计结果同样用 JSTL 在 JSP 里循环输出。把计算下推到数据库是性能优化的第一步。5. 避坑与排查这类 JSPServlet 项目最容易翻车的 5 个点5.1 中文乱码现象是页面显示问号原因是编码没统一现象登录后欢迎语、学生姓名、课程名显示成???或乱码。原因请求编码、响应编码、数据库字符集、JSP 页面编码四处没统一。解决request.setCharacterEncoding(UTF-8)放在取参数前response.setContentType(text/html;charsetUTF-8)JSP 顶部% page contentTypetext/html;charsetUTF-8 %数据库和表用utf8mb4URL 里加useUnicodetruecharacterEncodingutf8。五处全对上乱码才彻底消失。5.2 404 错误现象是访问路径找不到原因是 url-pattern 和访问地址不匹配现象浏览器访问/login报 404。原因WebServlet(/login)或web.xml里的url-pattern和实际访问路径不一致或者项目没部署到 Tomcat 的 webapps 下。解决先看 Tomcat 控制台有没有部署成功日志再确认访问地址是http://localhost:8080/项目名/login项目名不能漏。用注解WebServlet时确认web.xml的metadata-complete不是true否则注解不生效。5.3 数据库连接失败现象是 500 且堆栈有 SQLException原因是驱动或参数错现象启动后第一次访问数据库就 500堆栈里是CommunicationsException或ClassNotFoundException。原因驱动 jar 没放WEB-INF/lib或者 MySQL 8.0 用了旧驱动类名或者时区参数没加。解决确认 jar 在WEB-INF/lib而不是项目根目录MySQL 8.0 用com.mysql.cj.jdbc.DriverURL 加serverTimezoneAsia/ShanghaiuseSSLfalse。改完重启 Tomcat别热部署。5.4 Session 失效现象是登录后跳回登录页原因是 Session 超时或没存对现象登录成功点几个页面后突然跳回登录页。原因Session 默认 30 分钟超时或者登录时session.setAttribute的 key 和拦截器里取的 key 不一致。解决检查登录 Servlet 和权限过滤器里用的 key 是不是同一个比如都用currentUser在web.xml里配session-configsession-timeout60/session-timeout/session-config延长超时。别把用户信息存 request跨请求会丢。5.5 SQL 注入现象是输入 or 11能绕过登录原因是字符串拼接 SQL现象登录框输入admin or 11直接进系统。原因DAO 里用字符串拼接 SQL没用PreparedStatement。解决所有带参数的 SQL 一律用?占位符加setXxx绝不拼接。检查项目里所有 DAO把WHERE username username 这种写法全部改掉。这是安全红线改起来不难但漏一处就全盘皆输。6. 进阶技巧把这份 zip 改造成能写进简历的项目跑通只是起点要让这份jspservlet项目在简历上有分量得做几处升级。第一把明文密码换成 MD5 加盐或 BCrypt登录时比对哈希值这一步能体现你有安全意识。第二把DBUtil每次新建连接改成用 Druid 或 C3P0 连接池配置initialSize、maxActive、maxWait三个参数性能立刻不一样。第三加一个CharacterEncodingFilter统一处理编码别在每个 Servlet 里重复写setCharacterEncoding。// CharacterEncodingFilter.java —— 统一编码过滤器 WebFilter(/*) public class CharacterEncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); // 放行别忘了这句 } }过滤器里chain.doFilter必须调用否则请求就断在这了页面一直转圈。第四把权限控制抽成一个AuthFilter判断 Session 里有没有currentUser没有就踢回登录页这样学生和老师看到的菜单可以不一样。第五如果时间够把 DAO 层换成 MyBatisServlet 基本不动只改 DAO 实现这是向主流框架过渡最自然的一步。验证改造是否成功我一般会做三件事用 or 11试登录进不去说明注入防住了开两个浏览器分别登学生和老师菜单不同说明权限生效用 JMeter 发 50 个并发请求连接池不报错说明配置合理。这套流程走完这份 zip 就不再是课程作业而是一个你能讲清楚每一层为什么这么设计的项目。我自己踩过最深的坑是当年为了省事把数据库密码写死在DBUtil里还提交到了公开仓库后来被扫到只能连夜改密码。从那以后配置一律抽到db.properties.gitignore里加上它。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询