尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于Servlet+JSP+MySQL的学生成绩管理系统:从架构到避坑全解析

发布时间:2026/9/24 18:13:41

资讯中心
01
ARTICLE

基于Servlet+JSP+MySQL的学生成绩管理系统:从架构到避坑全解析

基于Servlet+JSP+MySQL的学生成绩管理系统:从架构到避坑全解析
简介这是一套基于ServletJspMySQL实现的学生成绩管理系统项目完整附带源码与数据库脚本面向计算机相关专业正在做毕业设计的学生也适合需要Java Web项目实战训练的初中级学习者可协助解决项目代码、数据库设计及部署运行等核心问题。压缩包共含166个文件以69个JSP页面、25个Java源文件、25个Class文件为主体并附带SQL脚本、XML配置、CSS样式和项目依赖包整体体积仅1.53MB可借助Eclipse或IDEA在Tomcat7及以上版本和MySQL5及以上环境直接导入运行。系统采用分层设计思路按管理员、教师、学生三类用户划分权限管理员负责学生、教师和课程信息管理教师能够开设课程、录入成绩和查询信息学生可完成选课与成绩查看数据库包含用户、学生、教师、课程及对应关联表业务模块完整闭环。项目结构清晰、注释详细适合作为课程设计或毕业设计的基础再开发当前已有537人学习下载能够帮助开发者快速理解ServletJspMySQL的整合方式并在此基础上根据实际需求进行功能扩展。1. 基于ServletJSPMySQL的学生成绩管理系统课设常青树也是Java Web入门的完整闭环本科最后一年几乎所有计算机专业的同学都会碰到同一个题目基于ServletMySQLJSP实现的学生成绩管理系统。它不是什么花哨项目却是Java Web课程设计里十几年没倒的常青树——把HTTP请求、会话保持、JDBC读写、SQL增删改查这几块最基础的能力完整串了一遍。这套以“源码数据库”形式交付的系统适合正在赶课程设计和毕业设计的同学也适合想用一个最小闭环快速复习Java Web的老手。看完这篇文章你能独立把数据库建起来、把代码分层跑通并且知道答辩时老师最可能追问哪几个点。2. 架构与选型为什么ServletJSPMySQL这个组合还能打很多同学上来就问“为什么不直接上Spring Boot”问得好但这个问题恰好说明还没看清这套系统的定位。学生成绩管理系统的核心目标是让初学者理解一次HTTP请求怎么变成数据库里的一行记录再变回页面上的一个表格。Servlet负责接收请求和响应JSP负责渲染视图MySQL负责存储。三者各管一段边界清晰没有任何框架替你把这些细节藏起来。2.1 从浏览器到数据库的完整链路Servlet 接管了哪一段先看一次“查询成绩列表”的请求在系统里怎么走浏览器向Tomcat发起GET /score/list请求。Tomcat根据注解或web.xml配置找到ScoreListServlet。ScoreListServlet调用service层查询MySQL拿到结果集并转成List 。Servlet把列表放进request.setAttribute转发到scoreList.jsp。JSP渲染成HTML返回浏览器。这条链路里Servlet是唯一的控制入口。它的生命周期由Tomcat管理init在首次访问时执行一次doGet/doPost每次请求执行destroy在应用卸载时执行。所以不要在doGet里做耗时初始化也不要让Servlet直接写SQL——那是DAO层的活。除了注解方式旧项目里更常见的是在web.xml中配置Servlet映射。两种方式不能混用否则会踩后面避坑章节里的404坑。web.xml里的标准写法是这样servlet servlet-nameScoreListServlet/servlet-name servlet-classservlet.ScoreListServlet/servlet-class /servlet servlet-mapping servlet-nameScoreListServlet/servlet-name url-pattern/score/list/url-pattern /servlet-mapping一个最小Servlet的骨架如下WebServlet(/score/list) public class ScoreListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 设置编码防止中文乱码 req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); // 2. 调用业务层查询成绩列表 ListScore list new ScoreService().listScores(); // 3. 把数据放进 request 域转发给 JSP 渲染 req.setAttribute(scoreList, list); req.getRequestDispatcher(/jsp/scoreList.jsp).forward(req, resp); } }这段代码的逻辑顺序固定先处理编码再做业务最后转发。参数上WebServlet(/score/list)里的路径要和前端表单的action或超链接地址完全一致大小写、斜杠位置都不能差。转发路径以工程根目录为起点写/jsp/scoreList.jsp而不是jsp/scoreList.jsp。2.2 JSP 的分工页面层只做渲染不写业务逻辑JSP本质上也是一个Servlet第一次被访问时会编译成Java类。它和Servlet的区别在分工JSP擅长写HTMLServlet擅长写逻辑。很多课设源码把Java代码直接塞进% %里页面能跑但业务逻辑散落各处想改个排序规则得改两处还容易改岔。JSP里允许出现的Java代码应该只限这几类遍历集合的for循环、根据条件控制某行是否显示、把request里的对象取出来展示。其他一切——数据库查询、成绩计算、登录校验——都应该在Servlet或Service层完成。Spring Boot 2集成JSP时还要专门调整目录结构到src/main/webapp这个细节也从侧面说明JSP是跟着传统Servlet容器走的别指望框架帮你消化掉分层纪律。实际渲染成绩表格时优先用JSTL标签配合EL表达式能让JSP页面干净很多c:forEach items${scoreList} varscore tr td${score.studentNo}/td td${score.studentName}/td td${score.courseName}/td td${score.score}/td /tr /c:forEach这段代码里没出现一行Java。items属性从request域中取出之前Servlet放进去的scoreListvarscore定义循环变量名后面用${score.属性名}取值。属性名必须和ScoreVO类里的getter对应例如getStudentNo()对应${score.studentNo}。2.3 JDBC 与 MySQL 对接DriverManager 直连与连接池的取舍成绩管理系统这种课设规模同时在线人数通常不超过几十个用DriverManager直连完全够用而且代码最直观。Druid或C3P0连接池在真实项目里是标配但课设里引入连接池答辩老师一句“连接池参数为什么要这么调”就可能把你问住。我的建议是先实现DriverManager直连理解getConnection的每个参数时间充裕再升级连接池答辩内容反而更丰富。对比项DriverManager 直连Druid 连接池代码量少一个工具类搞定需要额外配置连接池参数连接复用每次请求新建连接连接复用性能更好学习成本低能讲清原理需要理解初始化、回收机制课设答辩风险低参数配置讲不清容易扣分先理解直连方式也就理解了JDBC的本质。后面要升级连接池时核心的SQL代码一行都不用动只需要把getConnection()的实现从DriverManager换成dataSource.getConnection()。3. 把数据库立起来建表脚本与连接参数的落地细节不管源码里写了多少类数据库结构不对整个系统就废了。这套学生成绩管理系统最少需要学生、课程、成绩三张业务表外加一张登录账号表。表设计的原则是字段能少不多外键能不加就不加但给查询热点的字段配上索引。3.1 最少的表结构student、course、score、user 四张表先看学生表的建表脚本字段和注释都写进去了CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4; USE student_system; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, student_name VARCHAR(50) NOT NULL COMMENT 姓名, class_name VARCHAR(50) COMMENT 班级, gender CHAR(1) DEFAULT 男 COMMENT 性别, enroll_year YEAR COMMENT 入学年份, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表;course表结构类似核心字段是course_no唯一、course_name、credit学分、teacher_name。score表是关联表承担成绩本体CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, student_id INT NOT NULL COMMENT 学生ID关联student.id, course_id INT NOT NULL COMMENT 课程ID关联course.id, score DECIMAL(5,1) COMMENT 成绩保留一位小数, exam_time DATE COMMENT 考试时间, UNIQUE KEY uk_stu_course (student_id, course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT成绩表;登录账号表单独建别把密码字段塞进student表CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码, role VARCHAR(20) DEFAULT admin COMMENT 角色 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT登录账号表;几个细节值得记。student_no加UNIQUE是因为学号天然唯一别让重复数据进来。score表加uk_stu_course联合唯一索引保证同一个学生同一门课只有一条成绩这是业务规则在数据库层的兜底比在Java代码里先查再插可靠得多。DECIMAL(5,1)能存0.0到999.9足够大学成绩使用别用FLOAT浮点比较会产生精度问题。MySQL的排序、update语法在这个系统里都有用武之地。列表页按成绩从高到低排就写ORDER BY score DESC修改成绩就写UPDATE score SET score ? WHERE id ?。这些SQL在第四章里都会落地到代码中。3.2 JDBC 连接参数driver、url、字符集的正确写法建好库之后写一个工具类统一管理连接。太多源码把连接代码复制进每一个DAO里改一次密码要全局搜索替换。工具类的好处是连接参数只维护一份package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/student_system ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败检查jar包); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL里的三个参数是血泪经验换来的。useSSLfalse避免MySQL 8.0对SSL握手的时间消耗和安全警告serverTimezoneAsia/Shanghai解决驱动默认读取UTC导致时间差8小时的问题characterEncodingutf8配合数据库的utf8mb4保证中文从请求到存储再到页面全链路不乱码。注意USER和PASSWORD要和你本机MySQL实际账号一致。如果你安装MySQL 8.0时设了强密码策略这里写123456会直接Access denied。3.3 用 MySQL Workbench 导入数据库文件与初始化数据安装MySQL 8.0之后Workbench是官方标配。打开Workbench连接本地实例用File Open SQL Script打开源码包里的student_system.sql然后点击闪电图标执行。如果提示权限不足检查当前账号是否有CREATE DATABASE权限。执行完成后左侧Schema树里能看到四张表。右键表名选Select Rows可以快速验证数据是否导入成功。这里有两个细节常被忽略一是SQL脚本里已有USE student_system导入时不需要手动先建库二是SQL文件编码必须是UTF-8Windows记事本另存时要选UTF-8而不是ANSI否则中文字段注释会变成乱码。为了演示效果可以手动插入几条测试数据INSERT INTO student (student_no, student_name, class_name) VALUES (2021001, 张三, 计科2101), (2021002, 李四, 计科2101); INSERT INTO course (course_no, course_name, credit) VALUES (CS101, Java程序设计, 4.0), (CS102, 数据库原理, 3.5); INSERT INTO score (student_id, course_id, score, exam_time) VALUES (1, 1, 88.5, 2024-06-20), (1, 2, 76.0, 2024-06-22), (2, 1, 91.0, 2024-06-20);这三条INSERT分别验证了单行插入和多行插入的写法。成绩表插入时依赖student和course的主键ID所以必须先插入学生和课程再插入成绩这个顺序反了会报外键约束错误。4. 核心代码怎么组织从登录校验到成绩增删改查数据库就绪之后就要面对源码的组织方式。课设源码最容易让老师扣分的地方不是功能缺了而是“所有Java代码堆在Servlet里一个类上千行”。这一章给出一个课设阶段既不过度设计、又能把职责讲清楚的分层方案。4.1 工程目录分层entity/dao/service/servlet/jsp 各归其位一个顺手的目录结构长这样src/main/java ├─ entity // 实体类Student, Course, Score, User ├─ dao // 数据访问BaseDao, ScoreDao, UserDao ├─ service // 业务逻辑ScoreService, UserService ├─ servlet // 控制器LoginServlet, ScoreListServlet └─ util // 工具DBUtil src/main/webapp ├─ jsp // 视图login.jsp, scoreList.jsp, scoreAdd.jsp └─ WEB-INF/web.xmlentity是数据库表的Java映射字段名和列名保持一致生成getter/setter即可。dao只做SQL执行和结果集映射不写if判断业务逻辑。service封装“查询成绩列表并统计平均分”这类多步操作。servlet只做三件事接收参数、调用service、转发页面。这个四层结构是Java Web课设最稳妥的模板答辩时每一个职责都能一句话说清。4.2 登录与 Session 会话密码校验和注销的最小实现登录功能每个答辩老师都会问。核心逻辑是表单提交用户名密码到LoginServletservlet调用UserDao查库比对比对通过把用户信息放进session失败则带回错误提示。放session的目的是让其他页面在渲染前检查用户是否登录没登录就踢回login.jsp。WebServlet(/login) public class LoginServlet extends HttpServlet { Override 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) { // 登录成功用户信息放 session跳转成绩列表 req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /score/list); } else { // 登录失败带回错误消息到登录页 req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/jsp/login.jsp).forward(req, resp); } } }这里故意做了对比登录成功用sendRedirect浏览器地址栏变成/score/list刷新不会重复提交表单登录失败用forward请求还在错误信息能通过request域带回登录页。如果两者用反会出现重复登录或看不到错误提示的问题。getParameter拿到的是表单控件name属性对应的值前端input的nameusername要和这里精确匹配。UserDao里的查询方法要防SQL注入写法如下public User findByUsernameAndPassword(String username, String password) { String sql SELECT * FROM user WHERE username ? AND password ?; // PreparedStatement 预编译参数不走SQL拼接 return queryOne(sql, username, password); }用?占位符替代字符串拼接是Java Web里最基本的安全习惯。如果写成SELECT * FROM user WHERE username username 输入 OR 11就能绕过密码校验整个系统如同虚设。4.3 成绩列表查询与分页mysql 排序与 limit 参数列表页是系统的门面。成绩列表要同时关联学生表拿姓名、课程表拿课程名SQL层面用JOIN一次取回不要在Java代码里循环查库public ListScoreVO listScores(int page, int pageSize) { String sql SELECT s.student_no, s.student_name, c.course_name, sc.score, sc.exam_time FROM score sc JOIN student s ON sc.student_id s.id JOIN course c ON sc.course_id c.id ORDER BY score DESC LIMIT ?, ?; int offset (page - 1) * pageSize; return queryList(sql, offset, pageSize); }LIMIT后面的两个参数含义不同第一个是偏移量offset第二个是每页条数pageSize。第1页从0开始第2页从pageSize开始。MySQL排序ORDER BY score DESC必须写在LIMIT之前这个顺序是MySQL固定的语法规则。JOIN的类型默认是INNER JOIN只会返回两边都匹配的行如果某条成绩记录的学生被删了这条成绩就不会出现在列表里。分页还需要知道总条数单独一次COUNT查询public int countScores() { String sql SELECT COUNT(*) FROM score; // 返回总记录数总页数 ceil(total / pageSize) return queryCount(sql); }答辩老师追问分页时你能说出“COUNT查总数LIMIT配合offset拿当前页总页数用ceil(total/pageSize)向上取整”这个问题就过关了。注意COUNT(*)返回的是当前表的总行数如果后续加了WHERE条件过滤班级COUNT查询也要同步加WHERE。4.4 添加、修改、删除成绩mysql update/delete 语法在业务层的使用成绩的增删改是管理员的日常操作。三个功能对应三条SQLINSERT、UPDATE、DELETE。这里以修改成绩为例public int updateScore(int scoreId, double newScore) { String sql UPDATE score SET score ? WHERE id ?; // executeUpdate 返回受影响行数1 表示更新成功0 表示 id 不存在 return executeUpdate(sql, newScore, scoreId); }executeUpdate返回int这是判断操作是否成功的依据。更新0行不代表报错而是WHERE条件没匹配到记录业务层要能区分这两种情况。INSERT和DELETE同理只是SQL模板不同。删除成绩的SQL是public int deleteScore(int scoreId) { String sql DELETE FROM score WHERE id ?; return executeUpdate(sql, scoreId); }在设计score表结构时就要想清楚粒度如果成绩表包含平时分、期中分、期末分多个字段DELETE整行会把所有细分数据一起带走。课设阶段通常只有最终成绩一个字段整行删除没问题。真正要注意的是页面传参类型scoreId从request.getParameter拿到的是String要先用Integer.parseInt转成int再传给DAO传参类型不匹配会抛NumberFormatException。5. 避坑排查跑通这套系统最常见的翻车现场以下问题按“现象→原因→解决”的顺序记录每一个都让不止一个人卡过一两个小时。这些问题没有先后之分都是实际运行中最高频的报错。5.1 启动后 404Servlet 映射与 web.xml 撞车现象Tomcat正常启动访问http://localhost:8080/student_system/score/list却报404。原因旧版代码在web.xml里配置了servlet-mapping新版用WebServlet注解两者并存时容器没有按你预期的路径去解析。还有一种情况是工程发布名是student_system访问URL却漏了这一段context-path。解决二选一别混用。用注解就在所有Servlet类上统一使用WebServlet(/xxx)删除web.xml里的servlet和servlet-mapping整段用web.xml就统一在xml里配置。访问路径先在Tomcat的Manager应用里确认工程发布名再拼上注解里的url-pattern。5.2 中文乱码五处编码不配对现象页面标题正常从MySQL查出来的学生姓名显示为问号或者表单提交的中文写进数据库变成乱码。原因编码链路是request → servlet → 数据库存储 → response → 浏览器任何一环不是UTF-8就会乱。最常见的漏项是MySQL连接URL忘记加characterEncodingutf8或者JSP页面顶部的pageEncoding写成了ISO-8859-1。解决按五处逐一核对。JSP页面顶部pageEncodingUTF-8Servlet的doGet/doPost开头写req.setCharacterEncoding(UTF-8)、resp.setContentType(text/html;charsetUTF-8)MySQL连接URL加characterEncodingutf8建库时DEFAULT CHARSETutf8mb4。五处全部一致中文乱码才算根治。5.3 error 2002 (HY000)MySQL 服务没起来或 socket 找不到现象启动系统后控制台抛CommunicationsException原因链里能看到error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因本地MySQL服务没有启动或者客户端尝试连接socket文件路径而MySQL实际把socket放在别处。最常见的情况是安装MySQL 8.0之后从未启动过服务很多人以为安装完成就自动运行了。解决Linux执行systemctl start mysqldmacOS执行brew services start mysqlWindows在服务管理器里找到MySQL80服务手动启动。如果服务已启动仍报socket错误在连接URL里改成jdbc:mysql://127.0.0.1:3306强制走TCP绕过对socket路径的依赖。5.4 ClassNotFoundException: com.mysql.jdbc.Driverjar 包放错位置现象Tomcat启动时或首次访问页面时抛ClassNotFoundException提示找不到com.mysql.cj.jdbc.Driver。原因驱动jar包没有打进WEB-INF/lib目录。很多初学者把mysql-connector-java.jar放在JDK的lib目录或者放在项目根目录Tomcat的类加载器根本看不到这些位置。解决把jar包复制到src/main/webapp/WEB-INF/lib下在IDE里右键执行Rebuild重新部署。命令行部署的话解压war包确认WEB-INF/lib/里有mysql-connector-java-8.x.x.jar。同时注意驱动类名版本差异8.x用com.mysql.cj.jdbc.Driver5.x用com.mysql.jdbc.Driver写反一样报ClassNotFoundException。5.5 页面能展示但数据不刷新JSP 编译缓存与浏览器缓存现象在Workbench里改了成绩数据刷新页面看到的还是旧分数重启Tomcat也没用。原因Tomcat对JSP有编译缓存。JSP文件的时间戳没变或者IDE没有把改动同步到部署目录就会出现“旧页面”。另外浏览器缓存也会让你误以为数据没更新。解决清理工作目录。删除Tomcat的work/Catalina/localhost下对应工程的子目录重启后JSP强制重新编译。浏览器侧打开开发者工具勾选Disable cache或按CtrlF5强制刷新。开发阶段还可以在web.xml里加入以下配置让JSP每次检查是否有更新servlet servlet-namejsp/servlet-name servlet-classorg.apache.jasper.servlet.JspServlet/servlet-class init-param param-namedevelopment/param-name param-valuetrue/param-value /init-param /servletdevelopmenttrue会让JSP引擎每次都检查文件修改时间适合开发阶段。部署到生产环境时记得移除或改为false减少文件系统检查的开销。6. 进阶与验证让这套源码从“能跑”到“能演示”6.1 用 PreparedStatement 挡住 SQL 注入改造后的DAO用?占位符传参PreparedStatement先预编译SQL再把参数作为数据处理特殊字符不会被拼进SQL语句。同时它能缓存执行计划同一SQL循环执行多次时性能优于Statement。改造成本就是替换三个方法里的Statement收益是答辩时能主动说出“我防止了SQL注入”这一个加分点抵得上十个页面动画。6.2 给成绩表加一个视图或存储过程答辩加分的另一个技巧是写一个统计学生平均分的存储过程。MySQL声明存储过程的写法如下DELIMITER // CREATE PROCEDURE avgScoreOfStudent(IN stuId INT, OUT avgScore DECIMAL(5,1)) BEGIN SELECT AVG(score) INTO avgScore FROM score WHERE student_id stuId; END// DELIMITER ;存储过程封装了“计算某学生平均成绩”的逻辑调用时只需传学生ID输出参数带回平均值。它对性能提升有限但展示了存储过程、IN/OUT参数、流程控制三类知识点是课设里性价比最高的扩展方向。6.3 导出 war 包并验证功能清单部署到演示环境前按这个清单过一遍登录成功跳转、登录失败提示正确、未登录直接访问列表页被拦截、成绩列表分页正常、新增成绩后列表刷新、修改成绩后分数更新、删除成绩后记录消失、中文字段显示无乱码。全部通过再导出war包放到Tomcat的webapps目录启动后访问http://localhost:8080/student_system/做冒烟测试。我自己的习惯是先完整跑一遍再改代码改一处就回归一遍这条流程别等到答辩前一晚才第一次启动。这套ServletJSPMySQL的成绩管理系统虽然技术栈偏老但正好能把Java Web的地基夯实把请求链路和SQL手感练出来。希望这篇笔记能帮你少熬几个夜也希望你跑通的这套系统能真正帮到你日后的每一个项目。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。