
简介面向高校计算机相关专业毕业设计场景这是一套基于 Java MySQL 的大学生学籍管理系统完整项目配套毕业设计报告。系统覆盖学生、班级、院系、专业、课程、成绩、奖惩等核心管理模块并包含视图查询、存储过程生成成绩单、触发器自动维护班级人数等数据库进阶应用同时设置了性别字段的输入规则与表间参照完整性约束适合需要完成学籍/教务类课题或想熟悉 JDBC 与 MySQL 编程的开发者参考。压缩包共 203 个文件以 Java 源码、编译后的 class 文件、数据库建表 SQL、项目配置及文档为主另有 jar 包依赖与说明书整体约 1.91MB结构清晰便于直接导入开发环境学习。已有 2434 人学习下载。资源内提供完整代码和报告各功能模块按 MVC 思路拆分包括选课、退课、成绩增删改查、奖惩信息维护等可帮助读者理解从表结构设计到触发器、存储过程落地的全流程。1. 大学生学籍管理系统到底在做什么用 Java MySQL 做毕业设计的三个现实理由如果你的毕业设计选题落在“信息管理系统”这一大类学籍管理系统几乎是复现率最高、也最不容易翻车的一个方向。它不吃硬件不需要单独装 Redis 或消息队列一台能跑 Eclipse 的电脑就能把整条链路跑通评审老师看重的分层、表结构、登录会话、增删改查、报表导出它全都覆盖。系统看起来功能不大实际业务链却不短用户登录、学生档案维护、成绩录入、学籍异动申请与审批任何一个环节都要把 Java 后端和 MySQL 数据库打通这恰恰是 JavaWeb 课程里最核心的知识点。适合两类人一是时间紧、想用一个完整可运行的项目去复现课程设计的在校生二是想快速看明白一个 JavaWeb 工程到底怎么分包、怎么传参、怎么处理乱码的初学者。这套代码和配套报告给到的就是一个能直接跑起来、又能讲清楚每一行逻辑的范本。2. 核心技术选型与数据库设计从 ER 图到五张核心表的落位2.1 为什么选 Servlet JSP MySQL 而不是一上来就上 Spring Boot很多同学一开口就问“能不能直接用 Spring Boot 写”我的看法是能但没必要。毕业设计评审老师关心的是你懂不懂请求怎么进来、数据怎么存储、页面怎么回显而不是你背了多少注解。Servlet JSP 是 JavaWeb 最底层的形态它把 HTTP 请求如何处理、Session 怎么管理、SQL 怎么拼接、结果集怎么遍历这些最原始的操作全部摊在明面上。你用 Spring Boot 写一个登录可能只需要几行代码但问到你“Session 到底存在哪里”“Filter 和 Interceptor 有什么区别”的时候答不上来反而扣分。我一般建议选 Servlet JSP MySQL 的组合还有一个现实原因这套方案对 IDE 和运行环境的要求极低。Eclipse Tomcat 8.5 MySQL 5.7 或 8.0全部免费任何一台实验室电脑都能装。代码量虽然比 Spring Boot 多但每一个类都有明确的归属答辩时被追问代码细节你能指着某一行说清楚它做了什么。MySQL 选用 5.7 或 8.0 均可考虑到新装环境基本都是 8.0本项目的 JDBC 驱动和 URL 参数按 8.0 来配置这点在后面避坑章节会专门强调。2.2 数据库建表五张表的设计与外键安排学籍管理系统不是一张 student 表就能搞定的。从业务上看至少需要用户表存登录账号和角色学生表存基本信息课程表存课程学分成绩表存选课和分数学籍异动表存休学、复学、转专业的申请记录。这五张表各司其职外键关系清晰也方便在报告里画 ER 图。下面给出建表 SQL数据库名用student_manager。CREATE DATABASE IF NOT EXISTS student_manager DEFAULT CHARSET utf8mb4; USE student_manager; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码建议MD5存储, role TINYINT NOT NULL DEFAULT 2 COMMENT 1管理员 2教师 3学生, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( student_no VARCHAR(20) PRIMARY KEY COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 1 COMMENT 1男 2女, birth_date DATE, nation VARCHAR(20) DEFAULT 汉族, political_status VARCHAR(20) COMMENT 政治面貌, major VARCHAR(50) NOT NULL COMMENT 专业, class_name VARCHAR(50) NOT NULL COMMENT 班级, enroll_date DATE COMMENT 入学日期, status TINYINT DEFAULT 1 COMMENT 1在读 2休学 3毕业 4退学 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY COMMENT 课程编号, course_name VARCHAR(50) NOT NULL, credit DECIMAL(3,1) NOT NULL COMMENT 学分 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, course_id VARCHAR(20) NOT NULL, score DECIMAL(5,1) COMMENT 成绩, semester VARCHAR(20) COMMENT 学期如2024-2025-1, UNIQUE KEY uk_stu_course (student_no, course_id, semester), CONSTRAINT fk_score_student FOREIGN KEY (student_no) REFERENCES student(student_no), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE change_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, change_type TINYINT COMMENT 1休学 2复学 3转专业 4退学, reason VARCHAR(255), apply_date DATE, approve_status TINYINT DEFAULT 0 COMMENT 0待审批 1通过 2驳回, remark VARCHAR(255), CONSTRAINT fk_change_student FOREIGN KEY (student_no) REFERENCES student(student_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个设计细节值得在报告里展开。sys_user的密码字段宽度留了 64是给 MD5 或 SHA-256 的十六进制字符串预留的不建议明文存储哪怕项目很简单至少用MD5(密码固定盐)做一次加密。score表加了唯一键uk_stu_course防止同一个学生在同一学期修同一门课程出现两条成绩记录这是很多人忽略的点也是答辩时一个不错的提问点。student表主键直接用学号而不是自增 id是因为学号在院校内部本身就是唯一业务标识减少一次关联查询。外键的数量需要克制。五张表里我只在score和change_record上建了外键student与sys_user之间反而不建外键。原因是学生账号通常由管理员事后批量创建学号还没录入时就要先建账号建外键会让初始化数据变得很痛苦。这个取舍可以在报告的数据设计章节写明属于正常的解耦设计不是偷懒。2.3 JDBC 连接的三个参数细节不论用不用框架JDBC 连接 MySQL 的方式都要写在 DAO 层的最底层。项目里我习惯写一个独立的DBUtil工具类所有 DAO 都通过它拿连接。这个类虽然短但三个参数直接决定你后面会不会被乱码和启动失败折腾。package com.student.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/student_manager?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) {} } if (conn ! null) { try { conn.close(); } catch (SQLException e) {} } } }逻辑说明静态代码块在类加载时执行一次Class.forName把 MySQL 驱动注册到 DriverManager。getConnection()每次从连接池不存在的最朴素方式直接创建连接项目数据量小性能瓶颈不明显但要注意用完后必须关闭否则 Tomcat 运行一段时间后会报连接耗尽。参数说明DRIVER用的是com.mysql.cj.jdbc.Driver这是 MySQL 8.0 之后的驱动类名8.0 之前是com.mysql.jdbc.Driver。URL 里三个参数一个都不能少useUnicodetrue和characterEncodingutf8保证中文正常读写serverTimezoneAsia/Shanghai解决 MySQL 8.0 时区报错问题。PASSWORD换成你自己数据库的密码即可。3. 登录权限与学生管理的完整实现控制层、DAO 与页面的三步打通3.1 登录校验用 Filter 统一拦截未登录请求登录功能不能只在 Servlet 里判断用户名密码还要考虑一个问题如果用户没登录直接浏览器输入/student/list.jsp能不能访问答案是不能。最干净的做法是写一个LoginFilter在请求到达 Servlet 或 JSP 之前统一做会话检查。下面这个过滤器的写法是这个系统里的标准配置。package com.student.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(LoginServlet)) { chain.doFilter(request, response); return; } if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明req.getSession(false)的关键在于参数false如果当前没有创建过 Session它不会主动新建一个而是返回null。用getSession(true)的话未登录用户也会被分配一个空 Session过滤器里就区分不出登录状态了。放行条件里只放行了登录页和登录接口其余请求一律检查loginUser这个 Session 属性不存在就跳回登录页。配置说明Filter 需要在web.xml里注册同时设置拦截路径。我一般写成url-pattern/*/url-pattern把所有请求都纳入过滤但要注意放行静态资源否则 CSS 和 JS 会被一起拦截。对应配置是再增加一个判断uri.contains(/static/)时直接放行。这个过滤器是整个系统权限控制的基石答辩时可以主动提“Session 失效后刷新页面会被踢回登录页”这个细节。3.2 学生信息增删改查DAO 层到 Servlet 的完整链路学生模块是学籍管理系统的核心代码结构上我按“实体类 - DAO - Servlet - JSP”四层来组织。这里以分页查询为例展示完整链路因为分页是几乎所有信息管理系统的必考点。package com.student.servlet; import com.student.dao.StudentDao; import com.student.entity.Student; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/StudentServlet) public class StudentServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (delete.equals(action)) { String studentNo req.getParameter(studentNo); studentDao.delete(studentNo); resp.sendRedirect(req.getContextPath() /StudentServlet); return; } int page 1; int pageSize 10; String pageStr req.getParameter(page); if (pageStr ! null !.equals(pageStr)) { page Integer.parseInt(pageStr); } String keyword req.getParameter(keyword); if (keyword null) { keyword ; } ListStudent list studentDao.findByPage(page, pageSize, keyword); int totalCount studentDao.count(keyword); int totalPages (int) Math.ceil(totalCount * 1.0 / pageSize); req.setAttribute(list, list); req.setAttribute(totalPages, totalPages); req.setAttribute(currentPage, page); req.setAttribute(keyword, keyword); req.getRequestDispatcher(/student/list.jsp).forward(req, resp); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (add.equals(action)) { Student student new Student(); student.setStudentNo(req.getParameter(studentNo)); student.setName(req.getParameter(name)); student.setGender(Byte.parseByte(req.getParameter(gender))); student.setMajor(req.getParameter(major)); student.setClassName(req.getParameter(className)); studentDao.add(student); resp.sendRedirect(req.getContextPath() /StudentServlet); } else if (update.equals(action)) { // 更新逻辑与新增类似赋值后调用 dao.update(student) } } }逻辑说明doGet处理查询和删除doPost处理新增和更新这是最常见的 Servlet 分工方式。分页参数page从请求字符串中获取默认值设为 1避免空指针。Math.ceil(totalCount * 1.0 / pageSize)的作用是计算总页数这里必须把totalCount先转成浮点数再除否则两个整数相除结果永远是整数最后一页会被吞掉。参数说明pageSize设为 10一页显示 10 条可以按需求调整。keyword是模糊搜索关键字对应 SQL 里的WHERE name LIKE ?。这里有个容易被忽略的点Integer.parseInt(pageStr)在用户手动输入非数字参数时会抛异常代码里应该用 try-catch 包一层出现异常时回退到第 1 页属于健壮性细节。DAO 层对应的方法签名一般是ListStudent findByPage(int page, int pageSize, String keyword)SQL 使用LIMIT ?, ?注意起始行是(page - 1) * pageSize这部分容易算错建议在报告里写清计算过程。JSP 页面的循环部分比较固定核心是用 JSTL 的c:forEach遍历列表再用c:if控制操作按钮的显示。需要特别注意表单提交时的action参数它决定了后端走新增还是更新分支一旦忘记在表单里写input typehidden nameaction valueadd后端就会因为action为 null 直接不执行任何操作页面却没有任何报错这类问题排查起来很费时间。3.3 成绩录入与跨表查询一次 JOIN 把页面显示难题解决成绩模块比学生模块多一个难点成绩表里存的是student_no和course_id但页面上要显示学生的姓名和课程名称用户不可能去看编码。最省事的方案是直接写一条带 JOIN 的查询把关联表的名目一次性查出来。SELECT s.student_no, s.name, c.course_name, sc.score, sc.semester FROM score sc INNER JOIN student s ON sc.student_no s.student_no INNER JOIN course c ON sc.course_id c.course_id WHERE sc.semester ? ORDER BY sc.student_no;逻辑说明INNER JOIN比子查询更直观成绩记录只要存在就必然能关联到对应的学生和课程不存在左连接的必要。执行结果直接作为ListMapString, Object返回每一行同时包含编码和名称JSP 页面上直接输出name和course_name即可省去在 Java 层二次查表的麻烦。参数说明?是学期条件属于必填参数前端用下拉框限定学期格式避免用户自由输入导致查不到数据。如果你希望列表页同时显示总人数和平均分可以用COUNT(DISTINCT s.student_no)和AVG(sc.score)组合这在成绩统计的扩展功能里很常见。4. 部署运行时的常见问题排查五个必踩的坑与修复实录4.1 现象数据库里的中文全是问号页面显示也是乱码这是学籍管理系统出现频率最高的问题几乎每个复现的人都会遇到。原因不只有一个点而是三处编码不一致JSP 页面本身的字符集、请求参数传输的字符集、JDBC URL 里声明的字符集任何一处是默认的 ISO-8859-1中文就会乱。解决方法是三处统一用 UTF-8。JSP 第一行写成% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%doPost里第一行加req.setCharacterEncoding(UTF-8)JDBC URL 里带上useUnicodetruecharacterEncodingutf8。这样三层都对齐后乱码问题基本消失。如果还有问题检查 MySQL 表本身的字符集建表时统一用utf8mb4。4.2 现象Tomcat 启动时直接报 ClassNotFoundException: com.mysql.jdbc.Driver这个报错常见于 MySQL 8.0 环境。原因很直接MySQL 8.0 之后驱动类名已经改为com.mysql.cj.jdbc.Driver旧的com.mysql.jdbc.Driver只是兼容包里的遗留名称有些 8.x 版本的连接器里已经移除。解决方法是两步第一步把mysql-connector-java的 jar 包换成 8.0 以上版本放到项目的WEB-INF/lib目录下第二步把DBUtil里的DRIVER常量改成com.mysql.cj.jdbc.Driver。换完后如果报时区错误The server time zone value Öйú再在 URL 后面追加serverTimezoneAsia/Shanghai即可。4.3 现象用 Navicat 导入 SQL 脚本时提示 Cannot add or update a child row导入数据库脚本报警外键约束失败是因为脚本里的数据插入顺序和外键依赖关系不一致。常见做法是把所有表的CREATE TABLE语句放在最前面然后按“学生表 → 课程表 → 成绩表”的顺序插入数据。但很多同学从项目里拷贝的 SQL 脚本是按业务顺序导出的先插了成绩表而学生表还没数据外键自然不满足。解决方式有两种一是严格按照依赖顺序手工执行插入语句二是在脚本最前面加一行SET FOREIGN_KEY_CHECKS0;导入完成后执行SET FOREIGN_KEY_CHECKS1;恢复检查。后一种方式更省事但要在报告里注明这只是初始化数据时使用的临时开关不是正式运行时的配置。4.4 现象部署后访问 http://localhost:8080 显示 404或者页面一直跳回登录页这个问题的原因要分两种来看。第一种项目不是放在webapps根目录下访问地址应该是http://localhost:8080/student_manager/login.jsp其中student_manager是项目上下文路径直接访问 8080 根端口自然找不到资源。第二种项目能打开登录页但登录后访问列表页又被踢回登录页这通常是 Filter 把所有请求都拦截了没有放行静态资源或登录接口。解决办法是检查web.xml里 Filter 的url-pattern是否写了/*以及LoginFilter中是否对login.jsp和LoginServlet做了放行。顺手把uri.contains(/static/)加进放行条件CSS 样式丢失的问题也会一并解决。4.5 现象代码在别的电脑上编译报错找不到 HttpServletRequest 类这是环境版本不匹配造成的。Tomcat 9 对应的是javax.servlet包而 Tomcat 10 开始把包名改成了jakarta.servlet两者不兼容。如果在 Tomcat 10 上跑老项目所有import javax.servlet都会报红。解决方法是不要用 Tomcat 10 运行这个学籍管理系统统一使用 Tomcat 8.5 或 9.0。这类问题在图省事直接下载最新版 Tomcat 时最容易出现属于跨界知识盲区写进报告的错误分析与解决方案一节非常加分。5. 从 Eclipse 导出到 Tomcat 上线替换配置与数据初始化的完整流程5.1 导出 War 包之前的三种配置清理很多同学在 Eclipse 里能跑通但一部署到独立的 Tomcat 就各种报错问题几乎都出在配置没清理干净。第一步打开DBUtil.java把USER和PASSWORD改成目标机器上实际的 MySQL 账号密码千万别带着本机的测试账号打包出去。第二步确认web.xml里没有写死绝对路径比如某些项目会把图片上传路径写成D:/upload/这种路径在别人电脑上不存在就会报错调整为相对webapp目录的路径更稳妥。第三步检查WEB-INF/lib目录下是否包含mysql-connector-java的 jar 包Eclipse 的构建路径里有这个 jar 不代表部署时会自动带上必须确认它在WebContent/WEB-INF/lib下。清理完成后在 Eclipse 里右键项目选择 Export WAR file目标位置随意。导出的 War 包只是一个压缩文件不建议手工解压后零散拷贝到webapps目录直接把 War 包放进去让 Tomcat 自己解压更规范。5.2 用命令行或 Navicat 初始化数据库脚本拿到项目源码后第一步永远是先建库再跑程序不要跳过去直接启动 Tomcat。如果目标机器装的是 MySQL 8.0建议直接使用项目自带的 SQL 脚本因为它已经兼容 8.0 的字符集和驱动要求。命令行导入方式如下mysql -u root -p student_manager.sql执行后会要求输入 root 密码然后自动创建student_manager数据库及全部表结构。使用 Navicat 的话新建连接后右键数据库选择“运行 SQL 文件”选中脚本执行即可。导入完成后手动查一下表数量正常是五张表少了任何一张都说明脚本执行不完整需要查看 Navicat 的日志定位具体报错。5.3 启动 Tomcat 后的验证清单部署完成后按照下面六步验证系统是否真正能用。第一步启动 Tomcat访问http://localhost:8080/student_manager/login.jsp页面能显示出来且无乱码。第二步用管理员账号登录看是否能跳转到首页。第三步进入学生管理页测试新增一条学生记录保存后刷新列表能查看到。第四步测试编辑和删除操作确认数据在 MySQL 中同步变化。第五步进入成绩模块录一条成绩后查看列表确认学生姓名和课程名称能正常显示而不是只显示编码。第六步退出登录直接访问列表页确认被过滤器拦截跳回登录页。这六步走完系统才能算真正交付而不是“代码能编译”就算完成。6. 答辩前的加分改动给学籍管理系统加一个成绩导出 Excel 的技巧毕业设计到了收尾阶段最加分的改动不是加页面而是加一个“导出成绩 Excel”的按钮。评审老师看惯了表格页面能看到数据一键导出为 xls 文件会直接认定你考虑到了实际办公场景。实现上用 Apache POI 的 HSSFWorkbook生成旧版 xls 格式兼容性最好不需要额外装插件。public void exportScore(HttpServletRequest req, HttpServletResponse resp) throws IOException { ListScoreVO list scoreDao.findAllScore(); resp.setContentType(application/vnd.ms-excel;charsetutf-8); String fileName URLEncoder.encode(成绩表.xls, UTF-8); resp.setHeader(Content-Disposition, attachment;filename fileName); HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(成绩); HSSFRow header sheet.createRow(0); String[] titles {学号, 姓名, 课程, 成绩, 学期}; for (int i 0; i titles.length; i) { header.createCell(i).setCellValue(titles[i]); } for (int i 0; i list.size(); i) { HSSFRow row sheet.createRow(i 1); row.createCell(0).setCellValue(list.get(i).getStudentNo()); row.createCell(1).setCellValue(list.get(i).getName()); row.createCell(2).setCellValue(list.get(i).getCourseName()); row.createCell(3).setCellValue(list.get(i).getScore()); row.createCell(4).setCellValue(list.get(i).getSemester()); } workbook.write(resp.getOutputStream()); workbook.close(); }这里的核心有两个。一是响应头Content-Disposition的attachment它告诉浏览器不把内容当页面展示而是弹出一个下载窗口文件名经过URLEncoder.encode处理避免中文文件名在部分浏览器里变成乱码。二是 POI 的行列定位逻辑createRow(0)是表头数据从第二行开始索引加 1这个偏移量极其容易写错写错的结果是表头被覆盖或者第一行数据丢失。我自己的习惯是把这个导出方法放在ScoreServlet里actionexport时调用前端在列表页放一个a hrefScoreServlet?actionexport导出成绩/a链接不用提交表单不用传参代码改动量很小但功能演示效果非常好。从那以后我每次给别人优化这个项目都会先检查过滤器放行路径再确认 JDBC 驱动版本最后看一眼导出功能的响应头这三处是最能暴露一个 JavaWeb 初学者功底深浅的地方。希望这份拆解能帮你少踩几个坑把更多精力放到真正该研究的业务逻辑上。本文还有配套的精品资源点击获取