JSP+Servlet+MySQL教务管理系统毕设全攻略:从源码到答辩
2026/9/23 22:55:47 网站建设 项目流程

简介:这是一份面向计算机专业毕业生及Java Web学习者的教务管理系统完整项目,基于JSP+Servlet+MySQL实现,覆盖学生、教师、课程、成绩等核心教务模块,可直接运行,也适合作为毕业设计或课程设计的参考蓝本,内容涵盖前端展示、后端业务逻辑与数据库设计三个层面。rar压缩包共535个文件,压缩后约9.87MB,主要包含24个Java源码与24个class文件、10个JSP页面、9个jar库文件、4个db数据库文件及1个sql初始化脚本,同时附有大量gif/jpg操作示意图和CSS/JS样式交互文件,便于对照效果演示与二次开发。工程内部封装了登录、用户管理、文件上传、数据增删改查等典型功能,通过配置文件可快速部署到Web容器,数据库脚本可快速创建数据表并写入基础数据,整体结构清晰,有助于理解MVC分层思想。该资源已在CSDN获得1130人学习,完整性与可运行性经过验证,适合毕业设计选题参考、JSP/Servlet开发实战练习以及教学演示场景。

1. 教务管理系统(JSP+Servlet+MySQL)毕业设计:这顶“老技术”的帽子,恰恰是最好答辩的项目

如果你正在为毕设选题发愁,大概率会在选题库里看到“教务管理系统(JSP+Servlet+MySQL)”这种标题,后面还跟着“完整源码可运行”。第一反应往往是:这技术栈是不是太老了?2025年了谁还用JSP?但我的真实看法是——对毕业设计来说,JSP+Servlet+MySQL恰恰是容错率最高、答辩时最好讲、最容易让老师认可你“真的懂”的一套组合。理由很朴素:Servlet控制请求流转、JSP渲染页面、JDBC读写MySQL,每一环都是JavaWeb课程的教科书内容,老师一问一个准,你也答得出底层原理。换成SpringBoot,类加载怎么走、Bean怎么注入你自己都说不清,反而容易被问住。这个项目能帮你解决的核心问题就三件:把环境跑通、把代码读懂、把答辩串起来。它适合两类人:一是JavaWeb刚学完、想做一个“能运行且看得懂”的综合项目的人;二是拿人家源码做底子、准备改成自己课题的人。接下来我按自己带学生做这套东西的路径,把“怎么读、怎么跑、怎么改、怎么避坑”一次讲完。

2. 读懂项目骨架:MVC分层、目录结构与一次请求的完整流转

2.1 选型理由:为什么这套组合在毕设场景里比 SpringBoot 还稳

教务管理系统这类业务,本质是“课程、学生、教师、成绩”四张主表的增删改查加权限判断。它不需要高并发、分布式、消息队列,最需要的是“逻辑清晰、数据不出错”。JSP+Servlet+MySQL在这类业务上有一个无法替代的优势:每一层职责在代码层面是肉眼可见的。JSP负责显示,Servlet负责接收请求和跳转,JDBC负责拼SQL,DAO负责把数据封装成对象。老师在答辩时问“登录是怎么校验的”,你可以从登录页的form一路讲到Servlet里的rs.next(),链路没有黑匣子。

如果是SpringBoot,MVC、IOC、自动配置这三级抽象会把“请求分成几段”这件事遮住,新手被问到“Servlet在哪里”往往答不上。这不是说SpringBoot不好,而是毕业设计答辩的重点不是“用了多新的框架”,而是“你自己做的系统是否自洽”。另外,JSP+Servlet项目对电脑配置几乎没要求,Tomcat一个绿色版解压就能用,MySQL装好导入SQL就能连,环境搭建半小时之内可以完成,这对时间紧张的毕业生是实打实的优势。

2.2 拿到源码先看这些文件:目录结构、web.xml 与建表脚本

很多同学解压完一个.rar源码包,双击一个start.html发现打不开,又不知道从哪入口,于是开始慌。我的习惯是:拿到任何JavaWeb毕设源码,不做任何操作,先按下面这个顺序读三个地方

# 典型 JSP+Servlet 项目目录结构(动态Web项目,非Maven) 教务管理系统/ ├── src/ # Java 源代码 │ ├── com/xxx/entity/ # 实体类:Student.java, Course.java ... │ ├── com/xxx/dao/ # 数据访问层:StudentDao.java, ScoreDao.java ... │ ├── com/xxx/servlet/ # 控制层:LoginServlet.java, StudentServlet.java ... │ ├── com/xxx/util/ # 工具类:DBUtil.java, MD5Util.java ... │ └── com/xxx/filter/ # 过滤器:EncodingFilter.java, LoginFilter.java ... ├── WebContent/ # Web 根目录(也有叫 webapp/ 的) │ ├── index.jsp # 入口页面 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符:Servlet 映射、过滤器、欢迎页 │ │ └── lib/ # 存放 mysql-connector-java.jar 等依赖 │ ├── css/ js/ images/ # 静态资源 │ └── jsp/ # 业务页面:login.jsp, student_list.jsp ... └── 教务管理系统.sql # 建库建表 + 初始数据脚本

先看web.xml里配置了几个 Servlet、每个 Servlet 映射到什么 URL。比如<servlet-mapping>里把LoginServlet映射到/login,这就是“登录页面的请求交给哪个类处理”的索引。再看src下有没有DBUtil.java,它是整个项目与 MySQL 打交道的总入口,能不能跑起来,一半的变量由这个文件的数据库连接信息决定。最后打开.sql文件,拖到文件末尾看注释,确认它创建了哪个数据库,库名和DBUtil.java里的 URL 是否能对上。大部分“连接不上数据库”的报错,根源就在这两处不一致。

2.3 一次带参数请求的流转:从登录页到数据库的完整路径

这里我用“教师登录”这个最常见的教务功能,把一整条链路拆开讲。登录页login.jsp里有一个<form action="login" method="post">,用户输入工号和密码,点击提交后,请求体是username=1001&password=123456。这个action提交的login就是web.xml里给LoginServlet配的 URL 映射,于是请求进LoginServletdoPost方法。

doPost第一步做两件事:调用request.getParameter("username")取出表单值;再调用UserDao.findByUsername(username)去数据库查这条记录。UserDao内部通过DBUtil拿到Connection,执行一条预编译 SQL:SELECT * FROM t_user WHERE username=?,把查询结果封装成User对象返回。回到LoginServlet,拿页面提交的密码与查出来的密码比对:一致则request.getSession().setAttribute("currentUser", user)response.sendRedirect("index.jsp");不一致则request.setAttribute("error", "账号或密码错误"),再forwardlogin.jsp,把错误信息显示在页面上。

这套流转有两个细节值得注意。第一,过滤器的执行顺序早于 ServletEncodingFilter里设置了request.setCharacterEncoding("UTF-8"),你的中文参数才不会乱码,如果项目里没有这个过滤器,POST 提交的中文必花。第二,DAO 层里用的PreparedStatement而不是Statement,前者对单引号有转义处理,能从根上挡掉一大部分 SQL 注入案例,答辩时这是一句很加分的话:预编译的好处是“SQL 骨架只编译一次,参数通过占位符传入”。

3. 把项目跑起来:JDK、Tomcat、MySQL 的版本对照与导入步骤

3.1 环境安装:版本选不对,后面全是白忙

拿到源码的第一步是准备运行环境。我的建议组合是:JDK 1.8(或 8 )、Tomcat 8.5、MySQL 5.7。这套组合是绝大多数 JavaWeb 毕设源码的默认版本基线,兼容性最好。去 MySQL 官网下载安装包时,大概率会看到 8.0 和 8.4 版本,能装 5.7 就装 5.7——很多老源码里的mysql-connector-java-5.1.x.jar连接 8.0 数据库会报Public Key Retrieval is not allowed,虽然能通过加参数解决,但没必要在毕设阶段给自己添麻烦。如果你已经装了 8.0,后面第 5 章我会给对应的解决参数。

JAVA_HOME环境变量配置是一个高频翻车点。JDK 装完,去系统环境变量里新增JAVA_HOME,值填 JDK 安装目录,比如C:\Program Files\Java\jdk1.8.0_202,然后编辑Path,新增%JAVA_HOME%\bin。配置完在命令行敲java -version,能输出版本号就说明 Java 环境通了。Tomcat 只需要下载apache-tomcat-8.5.x的压缩包,解压到D:\tomcat8,不需要安装,这个目录在后续 IDE 里配置运行环境时会用到。

MySQL 安装完,运行mysql -uroot -p能进mysql>命令行就说明服务正常。如果你是直接导入别人的源码,建议先创建一个专用账号来处理这个项目,而不是直接用 root 裸奔到代码里,这样万一DBUtil.java里的密码泄露,影响范围也有限。

3.2 导入 Eclipse 或 IDEA:把 .rar 里的项目变成可运行项目

标题里的.rar不是部署包,它只是一个压缩包。解压后,区分它是不是标准动态 Web 项目,看两个标志:有没有.project文件,有没有WebContentwebapp目录。有,直接作为项目导入即可。下面以 IDEA 为例,Eclipse 的操作逻辑完全一致。

# IDEA 导入 JavaWeb 项目的操作路径(菜单点击,非命令行) # 1. File -> New -> Project from Existing Sources # 2. 选择解压后的项目根目录,点 OK # 3. 选择 Import project from external model -> Eclipse,一路 Next # 4. 导入完成后,右键项目 -> Open Module Settings # 5. 在 Project 里设置 SDK 为 1.8,Language level 为 8 # 6. 在 Libraries 里点击 +,添加 WebContent/WEB-INF/lib 下的 mysql-connector 驱动包 # 7. 在 Facets 里勾选 Web,设置 Web Resource Directory 为 WebContent 目录 # 8. 配置完成后,下拉框选择 Tomcat Server,添加 Artifact,点 Run

逻辑说明:第 3 步选 Eclipse 是因为绝大多数毕设源码是从 Eclipse 导出,保留着.project文件;第 6 步添加驱动包是最容易漏的,漏了会报ClassNotFoundException: com.mysql.jdbc.Driver;第 8 步把项目打包成war exploded部署到 Tomcat,之后每次修改 JSP 或 Java 代码保存即热更新,不用反复重启。如果你习惯用 VSCode 写 Servlet,配置逻辑也类似:装好 Java Extension Pack 和 Tomcat for Java 插件,强调server.tomcat.basedir指向你解压的 Tomcat 目录,把项目作为文件夹打开即可,但调试体验不如 IDEA 顺手,毕设阶段我更推荐用 IDEA 做主场。

3.3 初始化数据库:建库、建表、导入初始数据

数据库是整个项目的数据底座。源码包里一般带一个.sql脚本,用 Navicat 或命令行执行它。如果脚本缺失,你可以手动建一个最小的教务库,只要表名和字段名与源码里的 SQL 语句一致,就能正常跑起来。下面是一个最小核心表结构。

-- 创建数据库:字符集必须用 utf8mb4,不然中文姓名和课程名会变问号 CREATE DATABASE IF NOT EXISTS edu_system DEFAULT CHARACTER SET utf8mb4; USE edu_system; -- 学生表:表名和字段名要与 StudentDao.java 里的 SQL 完全一致 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL, student_name VARCHAR(50) NOT NULL, gender VARCHAR(10), class_name VARCHAR(50), created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 课程表 CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL, course_name VARCHAR(50) NOT NULL, credit DOUBLE DEFAULT 2.0 ); -- 成绩表:student_id 和 course_id 是外键逻辑关联,不强制建物理外键 CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DOUBLE, UNIQUE KEY uk_stu_course (student_id, course_id) );

参数说明:UNIQUE KEY uk_stu_course是个很实用的细节,它保证同一个学生同一门课只能录一次成绩,二次录入会被数据库拦住,比在 Servlet 里做判断更可靠。AUTO_INCREMENT让主键自增,批量插入学生数据时不用手动拼 id。如果你导入的源码在登录时一直报“账号或密码错误”,很可能是初始数据里的密码是明文,而代码里做了 MD5 加密存储,所以直接用 SQL 塞一条INSERT INTO t_user (username, password) VALUES ('admin', MD5('123456'));能绕过这个坑。

3.4 启动与访问:看到 404 和 500 时先查这三处

Tomcat 点启动,控制台出现Server startup in [xxx] milliseconds,浏览器访问http://localhost:8080,能看到 Tomcat 默认首页,然后http://localhost:8080/你的项目名/能打开系统首页,项目就算跑通了。如果这一步没到,按顺序排查。第一,项目名对不上:IDEA 里 Artifact 的名称决定了访问路径,如果你把项目配置成 ROOT,访问路径就是http://localhost:8080/直接到项目;第二,web.xml里的<welcome-file>没有配置index.jsp,会导致访问根路径时 404;第三,控制台报java.sql.SQLException: Access denied for user 'root'@'localhost',直接定位到DBUtil.java里的url、username、password三个变量值,改成你自己数据库的真实账号。这三个问题占到毕设无法启动案例的七成。

4. 改造这 5 处代码,答辩时能多讲十分钟

4.1 数据库连接工具类:把硬编码配置提取到 db.properties

绝大部分毕设源码的数据库连接写死在DBUtil.java里,每次换环境都要改 Java 代码再重新编译。我习惯的改进方式是把连接参数抽到配置文件。第一步,在src目录下新建一个db.properties,内容如下:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/edu_system?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

第二步,改造DBUtil.java的静态块,让驱动初始化时自动读取这个文件:

public class DBUtil { private static String url; private static String username; private static String password; static { try { // 通过类加载器读取 resources 目录下的配置文件 InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); Properties props = new Properties(); props.load(in); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); } catch (Exception e) { throw new ExceptionInInitializerError("数据库配置加载失败"); } } public static Connection getConnection() throws SQLException { // 每次调用都创建新连接,毕设规模下够用;追求性能才考虑连接池 return DriverManager.getConnection(url, username, password); } }

逻辑说明:ClassLoader.getResourceAsStream是读取类路径下资源文件的通用做法,配置文件跟着编译后的 class 一起进 target 目录。Class.forName是显式地注册 JDBC 驱动,MySQL 8 以下版本必须写,8.0 以上虽然 SPI 机制会自动加载,但写了也不报错。改造后的好处是,换数据库环境时只需改 db.properties,Java 代码一行不动。这一句话,在答辩现场就是“你做过工程化配置管理”的证据。

4.2 Servlet 层:用 BaseServlet 消除重复的 if-else 分支

原始的StudentServlet往往是这种结构:doGet里先取request.getParameter("method"),然后if ("add".equals(method)) { ... } else if ("update".equals(method)) { ... }。每加一个功能就加一个分支,类越来越肥。我通常用一个反射调用的BaseServlet来收口。改造后,逻辑路由不再需要手动判断。

public abstract class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 先设置编码,避免中文乱码 req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); String methodName = req.getParameter("method"); if (methodName == null || methodName.isEmpty()) { throw new ServletException("缺少 method 参数"); } try { // 通过反射调用同名方法,方法签名固定为 (HttpServletRequest, HttpServletResponse) Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); String result = (String) method.invoke(this, req, resp); // 约定:返回 "redirect:xxx" 做重定向,其余按请求转发处理 if (result != null && result.startsWith("redirect:")) { resp.sendRedirect(req.getContextPath() + result.substring(9)); } else if (result != null && !result.isEmpty()) { req.getRequestDispatcher(result).forward(req, resp); } } catch (NoSuchMethodException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, "功能不存在"); } catch (Exception e) { throw new ServletException("处理请求失败", e); } } }

参数说明:methodName由 URL 参数决定,比如studentServlet?method=add就调用add方法。反射的getMethod第三个参数是我们定死的入参类型,所以子类所有方法都必须长成public String add(HttpServletRequest req, HttpServletResponse resp)的样子。返回值约定为转发路径,redirect:前缀表示重定向,这样表单提交后刷新页面不会再次重复提交。这个改造有两个实际收益:新增一个功能只需要往子类加一个方法,不需要改路由代码;同时它用到了反射,这又是一个能拿来讲的概念点。

4.3 JSP 页面:用 JSTL 和 EL 替掉所有 <% %> 脚本片段

90% 的老项目 JSP 页面里写满了<% for (...) { %><%= stu.getName() %>。能运行,但页面代码和 Java 代码混在一起,可维护性几乎为零。答辩时老师翻到这种页面,第一印象就是你还没出新手村。建议用一下 JSTL 标签库。先在WEB-INF/lib下放入jstl-1.2.jar,然后改造列表页:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <table> <tr> <th>学号</th><th>姓名</th><th>班级</th><th>操作</th> </tr> <%-- studentList 是 Servlet 存到 request 里的 List<Student> --%> <c:forEach items="${studentList}" var="s"> <tr> <td>${s.studentNo}</td> <td>${s.studentName}</td> <td>${s.className}</td> <td> <a href="student?method=edit&id=${s.id}">编辑</a> <a href="student?method=delete&id=${s.id}" onclick="return confirm('确定删除该学生?')">删除</a> </td> </tr> </c:forEach> </table>

逻辑说明:${studentList}是 EL 表达式,从四个作用域里自动查找同名属性;<c:forEach>替代 for 循环,var="s"声明循环变量,${s.studentNo}会自动调用Student对象的getStudentNo()方法。改造之后,前端和后端边界变得清楚:页面里没有一行 Java 代码,业务逻辑全部回到 Servlet 层。而且到头来每张列表页的写法都长得一样,“看会的”迁移能力完全够用。

4.4 登录密码:从明文到 MD5 加盐存储

教务系统里教师账号、学生账号默认登录密码如果明文存数据库,老师只要看一眼t_user表,就大概率会质疑安全性。哪怕数据库被拖库,明文密码也是大事故。这个升级成本很低,值得做。改造成三步:在MD5Util里加盐做摘要(“盐”是一个固定字符串,防止两个相同密码产生相同哈希值,也可以再简单地拼上时间或用户名);在注册或初始化数据时,对密码做MD5Util.encrypt(plainPwd + salt)后再入库;在LoginServlet里,把用户输入的密码用同样的方式处理后比对哈希结果。

public class MD5Util { private static final String SALT = "edu@2025#"; public static String encrypt(String input) { try { // 每个用户的盐可以和用户名关联:md5(md5(pwd) + username + 固定盐) MessageDigest md = MessageDigest.getInstance("MD5"); byte[] bytes = md.digest((input + SALT).getBytes(StandardCharsets.UTF_8)); // 转成十六进制字符串,避免直接存二进制 StringBuilder sb = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(b & 0xff); if (hex.length() == 1) { sb.append("0"); } sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5 算法不可用", e); } } }

注意一:b & 0xff必须要做,否则负数会转出一长串无用的 hex。注意二:如果你在数据库初始化脚本里塞用户数据,得同步用UPDATE t_user SET password=MD5(CONCAT('123456', '固定盐')) WHERE username='admin'预先转好,不能让代码里加密、库里却是明文。注意三:MD5 不是安全界的顶配,但在毕设项目里比明文强了一个量级,跟老师说“本系统采用预编译 SQL 防注入 + MD5 加盐防脱库”这句,就能看到老师点头。

4.5 成绩排行与班级课程联动:三条 SQL 里的大学问

教务管理系统最常被问到的查询是“某课程成绩排名”和“某班学生选课成绩”。很多新手会把逻辑写成 Java 里先查所有成绩再排序,数据量一多页面就卡。正确做法是把排序和过滤下推到 MySQL 里。成绩排名的核心 SQL 如下:

-- 按课程分组,按分数降序排名;同分并列,用 @rank 变量实现 SELECT s.student_no, s.student_name, c.course_name, sc.score, @rank := IF(@prev_score = sc.score, @rank, @row_num) AS rank_no, @row_num := @row_num + 1 AS row_no, @prev_score := sc.score AS prev_score FROM t_score sc JOIN t_student s ON sc.student_id = s.id JOIN t_course c ON sc.course_id = c.id CROSS JOIN (SELECT @rank := 0, @row_num := 1, @prev_score := NULL) AS vars WHERE c.course_no = 'C001' ORDER BY sc.score DESC;

参数说明:@rank@row_num@prev_score是 MySQL 自定义变量。@prev_score记住前一行分数,如果和当前行相同,排名保持原样,实现“并列排名”;否则把排名赋为行号。CROSS JOIN子查询用于初始化变量,因为 MySQL 的ORDER BY执行顺序在SELECT之后,变量无法直接在子查询里初始化。如果是 MySQL 8.0,更优雅的写法是RANK() OVER (PARTITION BY course_no ORDER BY score DESC),但老项目跑在 5.7 上的多,所以自定义变量是一招“老环境也能用”的解法。

班级与课程联动查询则是JOIN的熟练度问题:t_score先关联t_student过滤班级,再关联t_course,最后WHERE sc.course_id = ? AND s.class_name = ?。顺序不能反,先过滤小表再连大表,MySQL 优化器虽然会替你调整,但语义上你先想清楚结果集,代码就不容易写乱。

5. 毕设项目常见问题避坑:环境、数据库与中文乱码排查

5.1 现象:启动后访问页面报 404 或 500,控制台没有任何有效报错

原因:多数是项目部署的 Artifact 没有把WebContent目录正确打包进 Tomcat,或者访问路径没带项目名。IDEA 里 Tomcat 配置的 Deploy 列表如果显示超链接指向的不是 Web 根目录,就会打包出半个项目。

解决:打开Run -> Edit Configurations -> Deployment,把 Artifact 删掉重新添加,选war exploded,确认 Application context 设置为/教务管理系统或你定义的路径。然后在浏览器地址栏手动输入http://localhost:8080/教务管理系统/,看能否访问。如果 500 且控制台有Servlet.init() threw exception之类的日志,把线索往下翻,大概率是数据库连接失败。

5.2 现象:控制台报ClassNotFoundException: com.mysql.jdbc.Driver

原因:MySQL 的 JDBC 驱动包没有进项目的 WEB-INF/lib,或没有添加到 Module 的 Libraries。老项目的驱动包经常在解压时被杀毒软件隔离,目录看起来在,但文件已损坏。

解决:去 MySQL 官网或 Maven 仓库重新下载mysql-connector-java-5.1.49.jar(对应 MySQL 5.7)或mysql-connector-j-8.0.x.jar(对应 MySQL 8.0),放进WebContent/WEB-INF/lib下,右键项目点 Refresh,再重启 Tomcat。如果这里用的是 MySQL 8,驱动类要改成com.mysql.cj.jdbc.Driver,URL 里还要加serverTimezone=Asia/Shanghai,否则报The server time zone value is unrecognized

5.3 现象:页面上的中文全是问号或乱码

原因:JSP 页面编码、Servlet 请求编码、数据库建表字符集、JDBC URL 四者没有统一到 UTF-8。

解决:按这个顺序逐层检查。第一,JSP页面头部必须有<%@ page contentType="text/html; charset=UTF-8" language="java" %>;第二,web.xml里加一个EncodingFilter,统一在过滤器里设置request.setCharacterEncoding("UTF-8");第三,建表 SQL 里DEFAULT CHARSET=utf8mb4(字符集务必要写);第四,JDBC URL 一定要带useUnicode=true&characterEncoding=utf8这两个参数。四层全查一遍,乱码基本绝迹。特定地——如果只有存进去的是问号而查询显示正常,那是数据库或建表字符集不对;如果存进去正常但页面显示乱码,是 JSP 或响应输出的编码不对。这一条能帮你在答辩现场直接回答老师“你这系统怎么处理中文编码”的问题。

5.4 现象:Tomcat 启动失败,端口被占用

原因:上一次 Tomcat 非正常退出,8080 端口还没释放;或者你开了多个 IDE 占用同一个端口。

解决:命令行执行netstat -ano | findstr 8080,把输出里的 PID 用taskkill /PID 进程号 /F杀掉,再重启 Tomcat。如果你更想彻底避开这个端口,可以改 Tomcat 的conf/server.xml,把<Connector port="8080"改成8081,但记住浏览器访问地址也跟着变。这个操作不涉及系统其他组件,改起来风险为零。

5.5 现象:Navicat 导入 .sql 文件报错,表结构不全

原因.sql文件里有USE xxx;语句,而 Navicat 当前连接选择的库不叫这个名字;或者 SQL 文件里带DROP TABLE IF EXISTS语句,把已有表先删了。

解决:第一,新建一个和 DBUtil 里 URL 同名的空数据库,字符集选utf8mb4;第二,在 Navicat 中“右键选中数据库 -> 运行 SQL 文件”,而不是“打开查询新建查询”去执行;第三,执行前看 SQL 第一行,确认没有和当前选中库冲突的CREATE DATABASE。如果文件里既有建表又有初始化数据,一气呵成执行完,再用SELECT * FROM t_user;验证数据是否真的导入进去。

6. 从“能运行”到“能答辩”:功能验证清单与三个进阶改法

项目跑通之后,不要急着写论文。我建议你先按下面这张验证清单完整走一遍流程,把自己当成用户,看看每一步是否真的符合预期。这张表同时也是你答辩 PPT 里的“系统功能演示目录”。

功能模块验证操作预期结果
登录与权限用管理员、教师、学生三账号分别登录跳转到不同首页,菜单项不同
学生管理新增一名学生、修改姓名、删除一条记录列表刷新后数据正确,删除有确认提示
课程管理新增课程并设置学分课程列表出现,学分保留两位小数
成绩录入录入学生成绩并提交同生同课重复提交会被拦截
查询统计按班级查成绩、按课程查排名排名与分数降序一致,同分并列
退出系统点击退出并直接访问受保护页面被拦截跳回登录页

验证过程中发现越权问题很正常,比如学生账号能访问教师管理页面。这题的原生解法是加一个LoginFilter,在doFilter里判断session.getAttribute("currentUser")的角色字段。这里我额外提两个不进代码也有效的小策略:视频录屏演示时,录一次完整流程就够了,不需要把每个按钮都点一遍。注意在演示“成绩排名”时,要特别展示第 2 名的分数与第 1 名相同、双方并列第 1 的场景,这能证明你的排名逻辑不是简单的行号。

如果还有余力,可以考虑三个写进论文的创新点。第一个,给成绩表加一条普通索引ALTER TABLE t_score ADD INDEX idx_student_course(student_id, course_id);,在论文里写“对查询频繁字段增加索引,数据量增大时查询性能不会线性下降”;第二个,把日记功能加上,登录成功、修改成绩等关键操作写进一张t_log表,这是大多数毕设都缺而老师们很在意的模块;第三个,把数据库从 5.7 迁移到 8.0 时会触发不少新特性,如果你在 MySQL 8 上跑,可以把成绩排名改写为DENSE_RANK() OVER (ORDER BY score DESC)一行代码,在论文里说一句“利用窗口函数简化排序逻辑”,离高分就更近了一步。

最后说我个人的一点教训。我带过的学生里,有一个人没看数据库里的初始密码,直接改 DBUtil 连自己的库连了三天,最后打开 SQL 脚本发现用户名不是 admin 而是 teacher,心态当场崩掉。所以拿到源码第一步,一定先翻 README 或 SQL 脚本里的初始账号,把这些信息贴在本子最显眼的位置,再谈二次开发和论文撰写。即使是老项目,版本的坑也比你想的多,但这个方向只要你把环境搞明白,后面的一切都会顺,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询