ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaWeb大作业98分方案:JSP+Servlet+JDBC+MySQL学生成绩管理系统

JavaWeb大作业98分方案:JSP+Servlet+JDBC+MySQL学生成绩管理系统 简介一套JavaWeb学生成绩管理系统源码与数据库工程是大三期末大作业项目经导师指导并认可评审分98分。面向计算机专业正在做课程设计或期末大作业的学生也适合需要项目实战练习的开发者能够覆盖教师、学生、考试、成绩等核心业务模块帮助理解管理系统的完整搭建过程。压缩包为zip格式大小约9.85MB共353个文件涉及53个Java源文件、24个JSP页面、40个CSS样式、24个jar依赖库、10个JavaScript脚本及SQL数据库脚本此外还包含png、jpg、gif等界面图片和字体、图标素材目录结构清晰便于按需查阅。已有810人学习/下载多用于期末大作业和课程设计场景。从代码组织看项目采用DAO、Service、Servlet分层设计业务逻辑与数据访问分离配合数据库表、前端样式和交互脚本可完整还原学生成绩管理系统的开发流程。读者既可整体研读架构也可针对登录、成绩录入、统计查询等模块改写复用用于自己项目的二次开发。1. 学生成绩管理系统拿了 98 分这个 JavaWeb 大作业赢在不是堆功能很多同学收到的 javaweb 期末大作业题目里“学生成绩管理系统”属于出现频率最高的一档交上去的版本通常页面很多、功能很碎最后分数却卡在七八十分。我倒着看这类项目时发现能拿到期末大作业 98 分的版本赢在一条主线上登录与角色权限清晰学生、课程、成绩三块数据正确关联增删改查完整统计排名用 SQL 而不是 Java 循环硬算。这篇文章就按“源码 数据库期末大作业 98 分”这个方向用 JSP Servlet JDBC MySQL 的技术栈拆一版可复现的方案从建表、核心代码到在 IDEA 里运行 JavaWeb 项目配置的踩坑点都覆盖到适合一周内要交付并且敢让老师现场提问的读者。2. 为什么用 JSPServletMySQL 而不是 Spring Boot期末答辩的技术选型依据技术选型这一步很多人的第一反应是“Spring Boot 写起来更快”。但期末大作业的评分逻辑和真实项目不一样课程大纲里讲的是 JSP、Servlet、JDBC老师验收时大概率只围绕这部分问。用 Spring Boot 交作业等于把答辩风险全押在自己对框架原理的临场发挥上。JSP Servlet JDBC MySQL Tomcat 这套组合才是 javaweb 项目完整案例 MySQL 场景里最常见的默认结构网上能搜到的参考资料多运行链路也短逻辑链路每一步都能拿出来讲清楚。2.1 技术栈选型贴着课程大纲写别拿 Spring Boot 给自己挖坑JSP Servlet 看起来“老”恰恰是课程设计最容易拿分的点。老师验收 JavaWeb 项目时默认会围绕这几个问题展开Servlet 生命周期是什么、doGet 和 doPost 什么区别、请求是怎么到 JSP 页面的、数据库连接放在哪一层。这些问题在 Servlet 技术栈里都是标准答案准备起来很直接但如果你用了 Spring Boot老师可能会顺势追问 DispatcherServlet 和 Servlet 容器的关系、Spring IOC 怎么管理对象答不上来反而把分丢了。另一个实际原因是运行环境。课程机房或老师验收用的机器大概率装的是 JDK 8 加 Tomcat而不是一套新的 Spring Boot 运行环境。JSP Servlet 项目直接打成 war 包丢进 Tomcat webapps 就能跑不需要 Maven 仓库下载几百个依赖也不依赖特定 Node 或 Gradle 版本。用“javaweb 连接 MySQL 数据库”这个关键词去检索主流结果也以 JDBC JSP Servlet 为主遇到问题容易找到对照案例。依赖管理上我一般建议大作业优先用普通 JavaWeb 工程而不是 Maven 工程。Maven 不是不能用但经常看到学生因为spring-webmvc、javax.servlet-api版本不匹配在 IDEA 里折腾一晚上。普通工程的做法简单直接下载一个 mysql-connector-java 驱动 jar放进 web/WEB-INF/lib 目录再把这个 jar 加到 Project Structure 的 Libraries 里。Tomcat 启动时就会把 lib 下的 jar 自动加载进 classpath不用处理传递依赖。常见运行组合可以参考这个表组件常用选择说明JDK1.8兼容 Tomcat 8.5 和旧课程环境Web 容器Tomcat 8.5 / 9.0IDEA 集成配置最稳定数据库MySQL 5.7 / 8.0驱动类名记得按版本选驱动mysql-connector-java 8.0.x驱动类为 com.mysql.cj.jdbc.Driver工程形式普通 JavaWeb 工程避免 Maven 依赖冲突2.2 项目骨架与三层架构一个可以照抄的包结构写 JavaWeb 大作业最容易乱的地方是“所有代码都堆在 Servlet 里”。老师一眼就能看出分层意识entity 只放数据字段dao 只做数据库操作servlet 只接收请求和跳转jsp 只负责展示。下面这个包结构是大作业里很稳的模板score_management ├── src/com/score/entity // Student.java、Course.java、Score.java、User.java ├── src/com/score/dao // UserDao、StudentDao、ScoreDao ├── src/com/score/servlet // LoginServlet、ScoreServlet、StudentServlet ├── src/com/score/filter // EncodingFilter、LoginFilter ├── src/com/score/util // DBUtil ├── web │ ├── WEB-INF/web.xml │ ├── login.jsp │ ├── admin/ │ │ ├── studentList.jsp │ │ ├── scoreList.jsp │ │ └── scoreEdit.jsp │ ├── student/ │ │ └── myScore.jsp │ ├── css/style.css │ └── WEB-INF/lib/mysql-connector-java-8.0.x.jarentity 下的 User 对应登录账号Student 对应学生档案Course 对应课程Score 对应成绩记录。dao 层只写 SQL 和 JDBC 操作返回实体或 Listservlet 层做参数接收、调用 dao、跳转页面jsp 不写 Java 业务逻辑顶多用 JSTL 或request.setAttribute拿到的值做展示。这样做的好处是答辩时老师指到任何一层你都能快速说出它负责什么。Servlet 映射建议用注解WebServlet(/xxx)加在类上web.xml 只保留欢迎页和 Filter 定义。用注解能少写很多 XML但要注意 URL 路径必须以/开头这是新手最容易翻车的地方。数据库工具类单独放 util 包里所有 dao 共用同一个getConnection()避免每个 dao 各写一份连接代码。3. 学生成绩管理的数据库设计四张核心表与初始化 SQL 的字段边界学生成绩管理系统看起来要管“学生、班级、课程、成绩、老师”很多对象但期末大作业真正要落地的核心就四张表用户表、学生表、课程表、成绩表。把四张表的字段边界定清楚后面所有代码都是围绕它们转。数据库设计这一步如果做扎实等于提前锁住了 30% 的分数因为老师最喜欢对着表结构提问。3.1 用户、学生、课程、成绩表字段设计与易被扣分的约束建库脚本我习惯直接在 Navicat 或 MySQL Workbench 里执行先建库再建表。下面这段是完整的初始化 SQL可以直接复制CREATE DATABASE IF NOT EXISTS score_db DEFAULT CHARSET utf8mb4; USE score_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0-管理员 1-学生 ); CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, student_name VARCHAR(50) NOT NULL, gender VARCHAR(4), class_name VARCHAR(50) ); CREATE TABLE t_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 2.0 ); CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,1) NOT NULL, exam_date DATE, UNIQUE KEY uk_stu_course (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES t_student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES t_course(id) );这段 SQL 里有两个容易被忽略的约束。第一个是t_score表上的唯一键uk_stu_course它保证同一门课程对一个学生只能有一条成绩记录。没有这个约束页面连续点击两次提交就会插入重复成绩答辩时老师很可能拿这个点来试。第二个是外键约束student_id和course_id必须真实存在防止录入成绩时随便填数字。字段类型上也有些讲究。score DECIMAL(5,1)能存十位整数外加一位小数对 0 到 100 的成绩范围完全够用不会像 FLOAT 那样出现 89.9999 的精度问题。t_user里的password用 VARCHAR(64) 是因为常见做法是存 MD5 字符串长度固定 32 位留出一定余量。t_student.student_no和t_course.course_no都设成 UNIQUE这是代码里按学号、课号查询的前提。四张表的关系用一句话就能说清楚登录的人记录在 t_user学生档案记录在 t_student课程信息记录在 t_courset_score 通过 student_id 和 course_id 把学生和课程关联起来。学生和课程是多对多关系成绩表就是中间表。老师问“为什么成绩表没有直接放学号和姓名”你就回答“规范化的设计应该通过外键关联姓名冗余存储会导致改学生姓名时成绩表数据不同步”。3.2 初始化 SQL造测试数据时容易忽略的外键顺序只建表没有数据项目里查不到效果。初始化脚本要顺带插入测试账号和一批成绩记录。这里有个容易踩的坑插入顺序必须按外键依赖来先插用户和学生再插课程最后插成绩。INSERT INTO t_user (username, password, role) VALUES (admin, 123456, 0), (2023001, 123456, 1), (2023002, 123456, 1); INSERT INTO t_student (student_no, student_name, gender, class_name) VALUES (2023001, 张明, 男, 计科2301), (2023002, 李雪, 女, 计科2301); INSERT INTO t_course (course_no, course_name, credit) VALUES (C001, Java程序设计, 3.0), (C002, 数据库原理, 3.5); INSERT INTO t_score (student_id, course_id, score, exam_date) VALUES (1, 1, 91.5, 2024-06-20), (1, 2, 88.0, 2024-06-21), (2, 1, 76.0, 2024-06-20);逻辑说明t_user里账号2023001的 role 是 1表示学生登录后通过用户名去 t_student 表里匹配student_no就能拿到学生档案。这就是为什么t_user.username和t_student.student_no的值要保持一致很多项目这两张表数据对不上登录后查成绩什么都查不到。删除数据时反过来要先删 t_score再删 t_student 或 t_course。否则直接删除学生外键约束会报错提示存在关联记录。很多同学做删除功能时第一次 500 就是这个问题。测试数据不要造太多每个学生两到三科成绩足够演示统计排名的效果。4. 从登录到成绩排名的核心实现Filter、PreparedStatement 和两条统计 SQL数据库就位后核心代码要先后完成三件事登录与角色分发、成绩增删改查、统计排名。这三部分分别对应 Filter Session、PreparedStatement、SQL 聚合查询也是答辩时老师最容易深挖的地方。这里不贴整包源码只把最关键、最容易讲错的代码段拆开讲。4.1 登录与角色分发Session 和 Filter 怎么配合第一步是让所有页面访问都要先经过登录检查。数据库连接先统一放 DBUtil 里避免每个 dao 重复写连接代码package com.score.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/score_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }关键在连接串参数useUnicodetruecharacterEncodingutf8让 JDBC 用 UTF-8 和中文字段交互serverTimezoneAsia/Shanghai是 MySQL 8 驱动的要求不写会报时间区错误。这里的用户名和密码要换成你自己 MySQL 的配置评分机器上跑时再改一次。登录拦截用 Filter 做最省事且能引出“面向切面”思想。下面的过滤器按路径放行登录页和静态资源package com.score.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); String ctx request.getContextPath(); if (uri.equals(ctx /login.jsp) || uri.endsWith(/LoginServlet) || uri.contains(/css/) || uri.equals(ctx /index.jsp)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(ctx /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明WebFilter(/*)表示拦截所有请求但必须手动放行登录页、登录接口、静态资源否则会出现“一直跳回登录页”的死循环。判断 session 时用的request.getSession(false)是拿已有 Session如果为 null 说明没有登录过重定向到登录页。写getSession(false)而不是getSession()是为了不主动创建无意义的 Session。这里要特别注意response.sendRedirect(ctx /login.jsp)里的ctx。项目部署后访问路径带工程名比如http://localhost:8080/score_war_exploded/login.jsp如果重定向写成/login.jsp浏览器会跳到根路径下Filter 再次拦截形成循环。很多项目运行时报“重定向次数过多”根因就在这里。4.2 成绩增删改查DAO 层用 PreparedStatement 绑定参数成绩模块的大作业要求就是数据库增删改查重点是新增和修改时不要用字符串拼接 SQL。下面这段是 ScoreDao 里的新增方法public int addScore(Score s) throws SQLException { String sql INSERT INTO t_score (student_id, course_id, score, exam_date) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, s.getStudentId()); ps.setInt(2, s.getCourseId()); ps.setDouble(3, s.getScore()); if (s.getExamDate() ! null) { ps.setDate(4, new java.sql.Date(s.getExamDate().getTime())); } else { ps.setDate(4, null); } return ps.executeUpdate(); } }逻辑说明PreparedStatement先用?占位再通过setInt、setDouble绑定参数SQL 结构在预编译时已经固定用户输入永远只能当值不能改变 SQL 语句结构。这是防 SQL 注入的正统做法。try-with-resources写法能自动关闭 Connection 和 PreparedStatement避免连接资源泄漏。使用setDate时java.util.Date 要转成 java.sql.Date否则会报类型不匹配。如果页面允许用户不选日期直接setDate(4, null)即可不要传一个空字符串进去。修改、删除的方法结构是一样的改 SQL 语句和参数即可。答辩时老师常问“为什么用 PreparedStatement 不用 Statement”标准回答就是“预编译、防注入、执行效率高”。4.3 成绩统计与排名一条 SQL 搞定平均分和名次成绩统计在大作业里属于锦上添花但很加分的功能。常见需求是显示每门课程的平均分、显示某个学生的总分和排名。这里的实现原则是能交给数据库的不要拿到 Java 里循环算。-- 每门课程的平均分与选课人数 SELECT c.course_name, ROUND(AVG(sc.score), 2) AS avg_score, COUNT(*) AS total FROM t_score sc JOIN t_course c ON sc.course_id c.id GROUP BY c.id, c.course_name ORDER BY avg_score DESC; -- 单科成绩排名兼容 MySQL 5.7 的写法 SELECT st.student_no, st.student_name, sc.score, (SELECT COUNT(*) 1 FROM t_score sc2 WHERE sc2.course_id sc.course_id AND sc2.score sc.score) AS rank_no FROM t_score sc JOIN t_student st ON sc.student_id st.id WHERE sc.course_id 1 ORDER BY rank_no ASC;逻辑说明第一个 SQL 用GROUP BY c.id, c.course_name分组ROUND(AVG(...), 2)把平均分保留两位小数。按course_id而不是course_name分组更安全因为课程名可能重复但 ID 不会。第二个 SQL 用相关子查询统计“比我分数高的人数 1”作为名次这是 MySQL 5.7 及以下版本通用的排名写法。如果评分环境是 MySQL 8可以用窗口函数RANK() OVER (PARTITION BY course_id ORDER BY score DESC)更简洁但为了兼容性我一般还是推荐相关子查询版本并且可以在答辩时补充一句“如果数据库是 8.0还可以用窗口函数”这本身就是展示你了解数据库差异的加分点。5. 在 IDEA 里把 javaweb 项目跑通的排查清单四个高频翻车点数据库脚本和代码都齐了最艰难的一步是把工程在 IDEA 里跑起来。这一章集中写 IDEA 运行 JavaWeb 项目配置里最常踩的四个坑。每一条按“现象 → 原因 → 解决”来写你可以直接对照排查。5.1 jar 包与数据库连接ClassNotFound 和 Communications link failure现象启动 Tomcat 后控制台报ClassNotFoundException: com.mysql.cj.jdbc.Driver或者点页面时报Communications link failure。原因前者是驱动 jar 没打进部署包。很多人只在 Project Structure 的 Libraries 里加了 jar但 IDEA 打包时没把它放进 web/WEB-INF/libTomcat 运行时就找不到类。后者是 MySQL 服务没启动或者连接串的serverTimezone参数缺失驱动不知道用哪个时区。解决打开 File → Project Structure → Artifacts选中当前工程的 Web exploded右键 Output Layout 里的 WEB-INF/lib添加 Library 里的 mysql 驱动。改完重新 Build Artifact再重启 Tomcat。如果报连接失败先在命令行执行mysql -uroot -p确认 MySQL 本身能连上再核对 DBUtil 里的密码、端口、数据库名。5.2 404 与 Filter 死循环URL 映射和放行规则现象浏览器访问页面显示 404或者登录成功后一直弹回登录页浏览器提示重定向次数过多。原因404 大多数是 Servlet 注解路径写错了。WebServlet(/score/list)必须以/开头写成score/list不合法还有一种情况是表单 action 里写了/LoginServlet但 Servlet 映射路径是loginServlet大小写不匹配。死循环则是登录 Filter 没放行登录接口或者重定向时没带request.getContextPath()跳转后的 URL 再次被 Filter 拦回。解决先看浏览器地址栏的路径和 Tomcat 日志。404 时核对 Servlet 的 WebServlet 值与所有访问路径是否一致死循环时把 Filter 里放行条件改成如下写法if (uri.equals(ctx /login.jsp) || uri.endsWith(/LoginServlet) || uri.contains(/css/)) { chain.doFilter(req, resp); return; }逻辑说明ctx /login.jsp和request.getRequestURI()是带完整工程名的路径而endsWith适合匹配 Servlet 接口。放行条件宁多勿少尤其是 css、js、图片等静态资源否则页面样式全部加载不出来。5.3 中文乱码页面、Servlet 和 MySQL 三层编码一起查现象页面提交中文姓名后数据库里显示???或者控制台输出乱码。原因三层编码链路断了。第一层是页面编码JSP 顶部必须声明contentTypetext/html; charsetUTF-8第二层是 Servlet 接收请求参数时Tomcat 对 POST 请求默认按 ISO-8859-1 解码必须手动设置请求编码第三层是数据库连接串和表的字符集不是 UTF-8。只改一层问题就还在。解决写一个 EncodingFilter统一设置请求和响应编码package com.score.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }逻辑说明这个 Filter 要在 LoginFilter 之前或外层执行保证请求参数在进入业务逻辑前已经是 UTF-8。数据库侧建库脚本已经用utf8mb4连接串里也带了characterEncodingutf8三层全部统一后中文乱码才能根治。这里有个血泪经验不要在 JSP 里反复写request.setCharacterEncoding放在 Filter 里一次解决全项目生效。5.4 端口占用与 Artifact 部署Tomcat 总启动不了的检查顺序现象启动 Tomcat 时提示Port 8080 was already in use或者 IDEA 运行按钮提示Artifact is not deployable。原因前一个 Tomcat 实例没关掉或者 IDEA 中 Deployment 选择的 Artifact 类型不对。默认配置下IDEA 要把项目部署成war exploded也就是解压目录形式而不是压缩 war 包。选成 warTomcat 启动时会尝试把 war 解压到 webapps 下容易和 IDEA 的部署机制冲突。解决Windows 上用netstat -ano | findstr 8080查端口占用进程再在任务管理器结束对应 PIDMac/Linux 用lsof -i:8080。IDEA 里打开 Run/Debug Configurations在 Deployment 标签下确保 Artifact 选的是xxx:war explodedApplication context 写成/score或空路径。改完清一次 Tomcat 的 work 目录避免旧 JSP 编译结果残留。6. 答辩稳住的三个细节数据校验、登录绕过和 SQL 可解释性代码能跑只是及格线98 分往往靠最后答辩那几分钟拉上去。我整理三个老师高频追问点提前准备好比临时翻书强得多。第一个是数据校验。老师经常会说你往录入成绩的页面里填 200 分试试。如果你只在 JSP 页面做了max100的 HTML 校验后端没有拦截这个操作会直接击穿防线。正确做法是后端再校验一次if (score 0 || score 100) { throw new IllegalArgumentException(成绩必须在0到100之间); }这是我个人养成的一个习惯前端校验只是用户体验后端校验才是安全底线DAO 层拿到数据之前Servlet 层必须先挡住非法值。第二个是登录绕过。老师会问“你把登录状态的 Filter 去掉能直接访问 studentList.jsp 吗”。要答出 Session 是存在服务器端的用户无法伪造只有拿到有效的 Session ID 才能通过 Filter同时还要说明 Session 有超时时间默认 30 分钟超时后要重新登录。能把这个链路说清楚说明你真的理解了登录拦截。第三个是 SQL 可解释性。答辩时老师会指着统计页面问“平均分是怎么算出来的”。不要只回答“点按钮就出来了”要指出前端调用哪个 Servlet、Servlet 调哪个 DAO 方法、方法里执行的是哪条 SQL、SQL 里 GROUP BY 和 AVG 各起什么作用。能把这一条链路完整讲完比任何功能截图都有说服力。我自己带这类项目时还留了个习惯交作业前一定把数据库从头删掉用初始化 SQL 重新执行一遍再跑项目验证。因为答辩机器大概率不是你配置好的那台电脑只有从零能复现才能证明你交付的是“源码 数据库”而不是运气。把这个细节做扎实老师现场验收的通过率会高很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表