Java+MySQL学生信息管理系统课设:Servlet+JSP+DAO分层源码全解析
简介基于JavaMySQL实现的Web学生信息管理系统课程设计项目源码包面向高校计算机专业学生及Java Web初学者适合作为课程设计、毕业设计参考或自学练手项目。系统使用IntelliJ IDEA开发按ServletJavaBeanDAO分层架构搭建Servlet负责业务处理并配置过滤器校验用户权限功能分为学生、教师、系统管理员三类角色学生可进行个人成绩、课程平均成绩、班级开课及教学计划查询教师可录入与查询成绩、查看任课情况管理员则统筹管理学生、教师、课程、成绩、学院、专业及班级信息模块划分完整。压缩包共551个文件涵盖Java源码、JSP页面、XML配置、HTML/CSS/JS前端样式、图片素材及运行依赖jar包整体大小约17.88MB目录结构清晰便于按功能模块查阅。已有568人学习/下载。通过该项目可系统性理解Web开发流程掌握分层架构设计、MySQL数据库操作、会话与权限过滤等关键技能是课程设计或入门实战的高性价比参考。1. 学生信息管理系统一套能直接跑通的 JavaMySQL Web 课设源码期末拿到“学生信息管理系统”这个课设题目要求 JavaMySQLWeb 三件套两周时间从零到验收。大多数人的第一反应是直接上 Spring Boot 一把梭但课程大纲往往卡死了必须用 JSP/Servlet就算不卡答辩被问一句“Servlet 生命周期是什么”也容易当场卡壳。这套编号 100010724 的课程设计资源给的就是一个不依赖重量级框架的完整实现IDEA 2020.3.3 开发Servlet 负责请求处理和页面转发DAO 层封装全部 JDBC 操作JavaBean 承载表单数据MySQL 存七张业务表再叠一个过滤器做角色权限拦截。功能上学生能查成绩、课程平均分、开课计划和教学计划教师能录入与查询成绩、看任课情况管理员能管理学生、教师、课程、成绩以及学院、专业、班级三类基础数据。适合刚学完 JavaWeb、需要一份能讲清楚原理的课设源码的在校生也适合接单做同类型小管理系统的开发者。2. 分层选型逻辑为什么用 ServletJSPDAO 而不是一上来就 Spring Boot2.1 先搞懂这套架构在讲什么这个项目用的是标准的 MVC 分层JSP 是 View只负责展示数据Servlet 是 Controller接收请求、解析参数、调用业务方法、决定跳转到哪个页面Model 层拆成两块——JavaBean 做数据载体DAO 做数据库访问。整个链路是“浏览器发请求 → Tomcat 找到对应 Servlet → Servlet 调 DAO → DAO 执行 SQL 返回 JavaBean 集合 → Servlet 把集合塞进 request → forward 给 JSP 渲染”。选型逻辑很直接课设场景下ServletJSP 是 Java Web 官方入门方案能让你清晰看到一次 HTTP 请求从 Tomcat 到 Servlet 再到 JSP 的完整路径。Spring Boot 确实开发效率更高但它把请求分发、参数绑定、事务控制全部封装成了黑匣子答辩时老师问“请求是怎么被 DispatcherServlet 转发的”没底层功底的人当场就露馅。这套项目的价值恰恰在于把每一层都摊开了Servlet 里的 doGet/doPost 是你自己写的DAO 里的 Connection 是你自己拿的SQL 是你自己拼的。另外别觉得这套东西过时。把它映射到 Spring Boot 的体系里看Controller 对应 ServletServiceMapper 对应 DAOEntity 对应 JavaBean。理解了这套课设再看 SSM 项目你只是把“手写 JDBC”换成了“MyBatis 帮你做”请求链路和分层思想完全一致。2.2 JavaBean、Servlet、DAO 在系统里各负责什么我以成绩模块举例看三个角色怎么配合。先是 JavaBean用来承载一条成绩记录在页面上要展示的所有字段package com.sims.bean; public class Grade { private int id; private String studentNo; private String studentName; private String courseName; private String teacherName; private double score; private String term; // 省略 getter/setter }注意这个类没有 score 对应的课程 ID而是直接放了 courseName 和 teacherName因为成绩列表页要展示的是“课程名教师名分数”不是一堆外键 ID。JavaBean 的字段设计可以跟着页面视图走不必和表结构一一对应这是新手最容易搞反的地方——有人把数据库七个字段原封不动搬进类里页面要显示教师姓名还得再查一次表纯属给自己加活。DAO 层负责和数据库打交道。比如成绩查询DAO 里写一个方法接收 studentId返回 ListGradeSQL 里把课程表和教师表 JOIN 出来一次性把页面要的数据全查齐public ListGrade findGradesByStudent(int studentId) { String sql SELECT g.score, g.term, c.course_name, t.teacher_name FROM grade g JOIN course c ON g.course_id c.id JOIN teacher t ON c.teacher_id t.id WHERE g.student_id ? ORDER BY g.term DESC; // 执行 JDBC 查询封装成 Grade 对象加入 List }Servlet 层做的事情就三件从 request 里拿参数、调用 DAO、把结果放到 request 域里转发给 JSP。我见过不少课设把所有代码堆在 Servlet 里一个方法五六百行SQL 散落各处。这个项目把增删改查收拢到 DAO好处是成绩查询、平均分查询多个 Servlet 能复用同一套数据库操作改表结构时只动 DAO 一处不用满项目找 SQL。2.3 过滤器与 Session权限控制是怎么做成闭环的权限设计是这个项目值得看的一个点。系统里学生、教师、管理员三类角色学生不能访问教师的管理页面教师不能动学生信息靠的是 Session 加过滤器双层控制。为什么不每个 Servlet 里都写一段 if 判断因为权限校验是横切逻辑你希望它在请求到达 Servlet 之前统一拦截否则十个 Servlet 复制十份 if漏一个就是一个权限漏洞。过滤器的核心逻辑是这样public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); // 登录页、登录接口、静态资源直接放行否则会死循环 if (uri.endsWith(login.jsp) || uri.endsWith(LoginServlet) || uri.contains(/static/)) { chain.doFilter(request, response); return; } HttpSession session req.getSession(false); Object loginUser session null ? null : session.getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } String role (String) session.getAttribute(role); if (uri.contains(/admin/) !admin.equals(role)) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, 无权限访问); return; } chain.doFilter(request, response); } }这里有两个细节值得琢磨。第一req.getSession(false) 传 false 是为了不主动创建 Session——用户没登录时服务器不该白开一个会话对象第二未登录跳登录页已登录但角色不匹配返回 403两个分支区分开避免权限不足的用户被来回重定向刷不出页面。为什么角色信息放 Session 而不放 CookieSession 存在服务端用户改不了里面的值Cookie 是明文存在浏览器里的改成 roleadmin 就能越权这也是面试里“Session 和 Cookie 区别”的实战对应。2.4 包结构与 IDEA 运行方式项目源码包结构是这个样子拿到手先按这个脉络去读别上来就翻 JSPsrc/main/java/com/sims │── bean/ # JavaBean 实体类 │── dao/ # JDBC 数据访问层 │── filter/ # 权限过滤器 │── servlet/ # 控制器 src/main/webapp │── admin/ # 管理员功能页面 │── student/ # 学生功能页面 │── teacher/ # 教师功能页面 │── login.jsp │── index.jsp web.xml这种按角色分包的方式是课设里比较清晰的做法页面目录跟着角色走过滤器按 URL 前缀判断权限一眼能看出哪个页面属于哪类用户。在 IDEA 里跑起来的关键点有两个一是 Project Structure 里把 Web 模块的 Artifacts 配好二是 Run/Debug Configurations 里 Deployment 标签页一定要把 war exploded 添加进去并写对 Application context否则启动不报错但浏览器永远 404。Tomcat 8.5 或 9 都可以JDK 1.8 起步就行。3. 建库建表七张表把三个角色的数据视图落到 MySQL3.1 从需求倒推数据模型这个项目最值得先做的不是写代码而是把需求翻译成表结构。三个角色的数据视图完全不同学生要查成绩、查课程平均分、查班级开课、查教学计划那学生相关的数据至少要覆盖“谁在哪个班、学了哪些课、考了多少分”教师要录成绩、查成绩、看任课情况那课程表里必须能定位到“这门课是哪个老师教的”管理员要维护六大基础信息自然引出学院、专业、班级三张字典表。梳理下来落到七张表college学院、major专业、class_info班级、student学生、teacher教师、course课程、grade成绩。课程和学生的关系通过 grade 表关联课程和教师通过 course 表里的 teacher_id 关联。至于“教学计划”和“班级开课”这两个查询课设里常见做法是给 course 表加一个 term 字段表示开课学期班级通过 class_info 里存的 major_id 关联到专业再关联培养计划不额外建复杂的关系表够用且好讲。3.2 核心建表 SQL外键和唯一约束一处都不能少建表语句我按这个模板走字符集统一用 utf8mb4避免后面录中文成绩单出现乱码CREATE DATABASE IF NOT EXISTS sims_db DEFAULT CHARSET utf8mb4; USE sims_db; CREATE TABLE college ( id INT PRIMARY KEY AUTO_INCREMENT, college_name VARCHAR(50) NOT NULL ); CREATE TABLE major ( id INT PRIMARY KEY AUTO_INCREMENT, major_name VARCHAR(50) NOT NULL, college_id INT, FOREIGN KEY (college_id) REFERENCES college(id) ); CREATE TABLE class_info ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL, major_id INT, grade_year VARCHAR(10), FOREIGN KEY (major_id) REFERENCES major(id) ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL, student_name VARCHAR(30) NOT NULL, password VARCHAR(64) NOT NULL, class_id INT, FOREIGN KEY (class_id) REFERENCES class_info(id) ); CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) UNIQUE NOT NULL, teacher_name VARCHAR(30) NOT NULL, password VARCHAR(64) NOT NULL, college_id INT, FOREIGN KEY (college_id) REFERENCES college(id) ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) NOT NULL, teacher_id INT, credits DECIMAL(3,1), term VARCHAR(20), FOREIGN KEY (teacher_id) REFERENCES teacher(id) ); CREATE TABLE grade ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT, course_id INT, score DECIMAL(5,2), term VARCHAR(20), FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id), UNIQUE KEY uk_stu_course (student_id, course_id) );几个设计决策说下理由。grade 表加了 (student_id, course_id) 联合唯一约束这是为了防止同一个学生同一门课录两条成绩——没有这个约束成绩录入页面连续提交两次数据就花了。score 用 DECIMAL(5,2) 而不是 DOUBLE因为浮点算平均分会出 89.999999 这种血泪结果。password 字段留 VARCHAR(64)是因为常见做法存 MD5 摘要32 位 hex留足长度以后想升级成加盐哈希也不用改表。外键这里要提醒一句课设里我建议保留物理外键因为要的就是数据库层面约束数据完整性。但别加 ON DELETE CASCADE 级联删除管理员删错一个学院连带学生成绩全没了这操作没有后悔药。业务上线项目里很多团队会刻意去掉物理外键改由应用层保证那是另一套取舍课设阶段用物理外键反而好解释。3.3 初始化数据三个顺序问题导致的外键报错建完表第一件事是造测试数据。外键在 MySQL 里默认是强校验的插入顺序错了直接报 “Cannot add or update a child row: a foreign key constraint fails”。常见做法是严格按父表到子表的顺序来INSERT INTO college (college_name) VALUES (计算机学院); INSERT INTO major (major_name, college_id) VALUES (软件工程, 1); INSERT INTO class_info (class_name, major_id, grade_year) VALUES (软工2401, 1, 2024); INSERT INTO teacher (teacher_no, teacher_name, password, college_id) VALUES (T001, 王老师, MD5(123456), 1); INSERT INTO student (student_no, student_name, password, class_id) VALUES (S001, 张三, MD5(123456), 1); INSERT INTO course (course_name, teacher_id, credits, term) VALUES (Java程序设计, 1, 3.5, 2024-2025-1); INSERT INTO grade (student_id, course_id, score, term) VALUES (1, 1, 88.5, 2024-2025-1);有个坑是命令行客户端里插入中文姓名变成乱码。这通常不是表结构的问题而是客户端连接字符集没对齐执行一句 SET NAMES utf8mb4; 再插入基本能解决。涉及 MySQL 安装和字符集配置的细节网上教程很多这里只说结论数据库、表、客户端连接三个层面的字符集必须统一缺一个都可能翻车。最后说密码。课设阶段对安全要求不高但明文存密码在答辩时是明显减分项。常见折中方案是存 MD5 摘要插入时用 SQL 里的 MD5() 函数登录时也对用户输入做一次 MD5 再比对。如果你能在答辩时补一句“生产环境一般用 BCrypt 加盐MD5 已经不够安全”老师会觉得你不仅会做还知道边界在哪。4. 核心链路实现登录、权限过滤、成绩查询与录入的完整代码4.1 DBUtil所有 DAO 的地基这个项目所有 DAO 都依赖一个 JDBC 工具类把获取连接和释放资源统一收口。代码不长但每个参数都有讲究package com.sims.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; 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/sims_db ?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; 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); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }静态块里用 Class.forName 注册驱动保证整个应用生命周期里驱动只加载一次。URL 里的参数逐个说useUnicodetrue 和 characterEncodingutf8 是让 JDBC 连接以 UTF-8 传输中文少了它页面就是一堆问号useSSLfalse 是本地开发关掉 SSL 握手不然 MySQL 8 会多一段警告日志serverTimezoneAsia/Shanghai 是 MySQL 8 的强制要求不加会报时区错误。为什么强制用 PreparedStatement 而不是 Statement两个原因一是防 SQL 注入用户输入的学号如果带单引号Statement 拼 SQL 直接语法错误甚至被注入二是参数用 ? 占位代码可读性好不需要手动处理字符串转义。close 方法里每个资源单独判空避免一个关闭失败导致后面的资源泄漏连接泄漏这个问题在课设里不常见但工作后迟早会碰到。4.2 登录、会话与过滤器把「我只能看我能看的」写死登录是三个角色进入系统的唯一入口流程不复杂接收用户名密码LoginDao 查库校验成功就把用户对象和角色写进 Session失败回到登录页提示。核心代码如下WebServlet(/LoginServlet) public class LoginServlet extends HttpServlet { private LoginDao loginDao new LoginDao(); 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); User user loginDao.findUser(username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); req.getSession().setAttribute(role, user.getRole()); resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }登录成功后用 sendRedirect 做重定向而不是 forward这是为了避免用户按 F5 刷新时重复提交登录表单——重定向之后浏览器地址栏变成了 index.jsp刷新不会重新触发 doPost。失败场景用 forward因为需要把 msg 属性带给登录页展示而 redirect 是二次请求request 域里的数据全丢了。配合第 2 章的 AuthFilter权限就闭环了未登录访问任何业务页面过滤器直接踢回 login.jsp登录了但角色不匹配返回 403。这里有一个实际项目里常见的坑——Session 里存用户对象的 key 必须全局统一。有人登录时 setAttribute(loginUser)过滤器里却 getAttribute(user)永远取到 null怎么都跳不出登录循环。这种问题排查起来特别费时间属于典型的“代码没错但就是不对”的玄学现场。web.xml 里过滤器映射要配对所有路径但放行登录相关filter filter-nameAuthFilter/filter-name filter-classcom.sims.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern 用 /* 而不是 /login.jsp 或 /index.jsp意义在于所有请求统一过闸机再在过滤器内部细化放行规则。多个过滤器同时存在时执行顺序按 filter-mapping 在 web.xml 里的声明顺序从前往后这个顺序问题在日志里经常表现为“权限过滤器先执行了导致后面的过滤器还没机会处理”排查时先看声明顺序。4.3 成绩查询链路QueryGradeServlet 到 StudentDao 到 JSP成绩查询是学生角色的核心功能链路完整走一遍能理解 MVC 的数据流动。先是 DAO 层public ListGrade findGradesByStudent(int studentId) { ListGrade list new ArrayList(); String sql SELECT g.score, g.term, c.course_name, t.teacher_name FROM grade g JOIN course c ON g.course_id c.id JOIN teacher t ON c.teacher_id t.id WHERE g.student_id ? ORDER BY g.term DESC, c.course_name ASC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Grade g new Grade(); g.setCourseName(rs.getString(course_name)); g.setTeacherName(rs.getString(teacher_name)); g.setScore(rs.getDouble(score)); g.setTerm(rs.getString(term)); list.add(g); } } } catch (SQLException e) { System.err.println(查询成绩失败: e.getMessage()); } return list; }SQL 里 JOIN 了两张表一次性查出课程名和教师名页面直接 EL 表达式就能渲染不用再发起第二次查询。ORDER BY term DESC 让最近的学期排在最前面这是成绩单页面最自然的展示顺序。Java 7 的 try-with-resources 语法会在代码块结束后自动关闭 PreparedStatement 和 ResultSet但 Connection 仍然显式交给 DBUtil.close 处理保持统一。Servlet 层做的只是搭桥WebServlet(/student/QueryGradeServlet) public class QueryGradeServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User loginUser (User) req.getSession().getAttribute(loginUser); int studentId loginUser.getId(); ListGrade gradeList studentDao.findGradesByStudent(studentId); req.setAttribute(gradeList, gradeList); req.getRequestDispatcher(/student/gradeList.jsp).forward(req, resp); } }学生 ID 不从请求参数里拿而是从 Session 里取当前登录用户这是权限设计的关键——如果从 URL 参数接收 studentId学生把参数改成别人的 ID 就能查别人的成绩典型的越权漏洞IDOR。课设里能主动意识到这一点是明显的加分表现。forward 到 JSP 后页面上用 JSTL 标签循环渲染 gradeList 即可。4.4 教师录成绩UPDATE 必须配事务教师角色录成绩的本质是批量 UPDATE score 字段但这里有个隐藏问题如果一次录入十门成绩执行到第五句时数据库连接断开了前面四条已经提交后面五条全丢数据就处于半更新状态。解决方式就是事务public boolean updateScore(int studentId, int courseId, double score) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启手动事务 String sql UPDATE grade SET score ? WHERE student_id ? AND course_id ?; ps conn.prepareStatement(sql); ps.setDouble(1, score); ps.setInt(2, studentId); ps.setInt(3, courseId); int rows ps.executeUpdate(); conn.commit(); return rows 0; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { System.err.println(回滚失败: ex.getMessage()); } } System.err.println(更新成绩失败: e.getMessage()); return false; } finally { DBUtil.close(conn, ps, null); } }setAutoCommit(false) 之后所有 SQL 都在同一个事务里只有 commit 才真正落库任何一步抛异常就 rollback 全部撤销。这段代码对应面试里常问的“事务的 ACID 特性”和“JDBC 事务怎么控制”能把这两句话讲清楚比背八股文强得多。录入前还要做参数校验比如分数范围 0-100学号课程号必须大于 0。校验放 Servlet 层做一次DAO 层只负责执行这样非法请求在进入数据库之前就被拦住了。如果课程已经录过成绩想更新而不是重录SQL 可以换成 INSERT ... ON DUPLICATE KEY UPDATE配合表上的联合唯一索引一条语句同时处理插入和更新两种场景这也是实际项目常见的 UPSERT 写法。5. 避坑指南IDEA 部署、乱码、驱动与 Session 的五次翻车经历5.1 现象Tomcat 启动正常浏览器访问始终 404原因几乎每次都出在 IDEA 的 Deployment 配置上Artifacts 没有添加到 Tomcat 的 Deploy at the server startup或者 Application context 和实际访问路径不一致。新手最容易遇到的是前一种——在 IDEA 里点运行Tomcat 日志打完 Started但访问 http://localhost:8080/ 返回 404因为服务器上根本没有部署任何工程。解决方式Run → Edit Configurations → 找到 Tomcat Server → Deployment 标签页 → 点 号 → Artifact → 选择项目对应的 war exploded → Application context 核对为 /sims或你期望的上下文路径。改完重启 Tomcat访问 http://localhost:8080/sims/login.jsp。war exploded 和 war 的区别也要知道前者是解压目录IDEA 里改 JSP 能热更新后者是压缩包每次改代码都要重新打包开发阶段一律用 exploded。5.2 现象登录后页面中文全是问号或乱码排查乱码要按三段来看请求编码、响应编码、数据库连接编码。最隐蔽的是数据库连接 URL 少了 characterEncodingutf8JSP 页面和过滤器都设了 UTF-8但数据写进 MySQL 还是乱码因为 JDBC 连接本身没告诉 MySQL 用 UTF-8 传输。解决按顺序检查JSP 头部确认 % page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%所有 doPost 第一行加 req.setCharacterEncoding(UTF-8)JDBC URL 带上 useUnicodetruecharacterEncodingutf8数据库建库时指定 DEFAULT CHARSET utf8mb4如果数据已经乱码写进去了把连接参数改对后重新插入才能恢复已经乱码的那批数据基本救不回来所以这个坑要在一开始就堵住。Tomcat 8.5 及以上 GET 请求的 URI 编码默认就是 UTF-8不用再改 server.xml。5.3 现象启动时 ClassNotFoundException: com.mysql.cj.jdbc.DriverIDE 里代码能编译但运行报找不到驱动类九成原因是 JDBC 驱动 jar 只加到了 Module 的 classpath没有打进最终的 WEB-INF/lib。IDEA 里有个很迷惑人的设计——你在 Project Structure 里添加了库编译期能过但 Tomcat 运行时用的是部署目录WEB-INF/lib 里没有这个 jar 就加载不到。解决方式Project Structure → Artifacts → 选中部署的 exploded 目录 → 右侧 Available Elements 里找到 mysql-connector-j 的 jar → 双击添加到 WEB-INF/lib。更稳妥的做法是把 jar 直接复制到项目 webapp/WEB-INF/lib 物理目录下这样不管怎么重新打包都不会丢。判断问题最直接的办法打开部署目录看 WEB-INF/lib 里到底有没有这个 jar。5.4 现象MySQL 8 连接报 Access denied 或 Communications link failureMySQL 5.x 和 8.x 在 JDBC 连接上有两个不兼容点驱动类名从 com.mysql.jdbc.Driver 换成了 com.mysql.cj.jdbc.Driver连接 URL 强制要求 serverTimezone 参数。如果你本机装的是 8.0 以上版本驱动 jar 还用的老版本 5.1.x大概率报 SSL 握手失败或 Access denied。另外 MySQL 8 默认认证插件是 caching_sha2_password老驱动根本协商不通。解决方式驱动包换成 mysql-connector-j 8.0.x驱动类名写 com.mysql.cj.jdbc.DriverURL 里加 serverTimezoneAsia/Shanghai显式声明 useSSLfalse。还有个小坑是首次连接时报 “Public Key Retrieval is not allowed”URL 里加 allowPublicKeyRetrievaltrue 即可这是本地开发的安全妥协生产环境不要这么干。如果你装了 MySQL 却不知道版本命令行敲 mysql --version 看一眼别在这个问题上凭感觉猜。5.5 现象登录成功却被过滤器弹回登录页循环跳转这问题我在帮人看课设时遇到至少三次起。网上找资料一句话能概括登录跳转地址没带上下文路径。直接写 resp.sendRedirect(/index.jsp) 在部署路径不是根路径时浏览器会跳到 localhost:8080/index.jsp绕过项目和过滤器匹配规则导致看起来像“登录没生效”。解决方式所有重定向统一用 resp.sendRedirect(req.getContextPath() /index.jsp)getContextPath() 会返回 /sims 这样的上下文路径保证不管部署名是什么都能跳对。另外也要回头检查过滤器里放行规则如果 AuthFilter 连 login.jsp 和 LoginServlet 也拦登录请求还没到 DAO 就被踢回登录页一样是循环。过滤器里放行条件要放在最前面而且要用 endsWith 或精确匹配来做别用 contains(login) 这种模糊判断不然 LoginServlet 以外的、路径里恰好带 login 四个字母的资源也会被放行。6. 三个改造让这套课设从「交差」变成「作品」6.1 把 DBUtil 从 DriverManager 换成 Druid 连接池课设项目里每次查询都新建连接、用完关闭功能没问题但效率很低——连接创建的代价远高于 SQL 执行本身。面试官问“连接池解决什么问题”时你需要能说出复用连接、控制并发、避免频繁握手。改造很简单引入 Druid 后初始化一个数据源DAO 里从数据源拿连接DruidDataSource ds new DruidDataSource(); ds.setUrl(jdbc:mysql://localhost:3306/sims_db?serverTimezoneAsia/Shanghai); ds.setUsername(root); ds.setPassword(123456); ds.setInitialSize(5); ds.setMaxActive(20); ds.setMaxWait(60000);DAO 里把 DBUtil.getConnection() 换成 ds.getConnection()其余代码一行不用改。答辩时能讲出 initialSize 和 maxActive 的区别——最小空闲连接数和最大活跃连接数说明你对资源管理有意识。6.2 加一张「挂科统计」报表JOINGROUP BYHAVING 一次讲透课程设计里最常见的加分项是给管理员加一个统计报表。比如“哪些学生有挂科记录挂了几门”一条 SQL 就能撑起整个功能SELECT s.student_no, s.student_name, COUNT(g.course_id) AS fail_count FROM student s LEFT JOIN grade g ON s.id g.student_id AND g.score 60 GROUP BY s.id, s.student_no, s.student_name HAVING fail_count 0 ORDER BY fail_count DESC;这个查询能串起好几个知识点LEFT JOIN 保证没选课的学生也出现在结果集里只是挂科数为 0用 GROUP BY 按学生分组HAVING 是对分组后的结果做筛选——这也是面试里“HAVING 和 WHERE 区别”的最佳例子WHERE 在分组前过滤行HAVING 在分组后过滤组。你把这条 SQL 的每部分拆给老师听比背十道 mysql 基础面试题都有说服力。6.3 让 DAO 层接口化代码规范是课设和工作的分水岭最后一个小改造把 StudentDao、TeacherDao 定义成接口实现类放 dao.impl 包Servlet 里只依赖接口。这是前面所有改动的收尾——连接池解决性能接口化解决的是维护性。接口的意义在于把“调用方”和“实现方”解耦今天用 JDBC 写实现类明天换成 MyBatis 的实现类Servlet 的代码完全不用动。答辩时说出“面向接口编程”和“开闭原则”两个词配合前面几章的实战代码整份课设的完成度立刻上一个档次。这套跑通的项目我前后带过几个学弟复现最大的感受是网上 Java Web 课设源码不难找但绝大多数人拿到手只会改个数据库密码就交差被老师一个问题问倒就露馅。我以前第一次做类似管理系统时也不懂分层全堆在 Servlet 里答辩被问“两个人同时录成绩会不会覆盖”直接愣住。从那以后我每次做 JavaMySQL 的 Web 项目都强制自己走一遍“先拆 DAO、再定权限、最后写业务”的顺序装完环境先跑通再改代码。这套编号 100010724 的资源把源码、SQL 脚本和部署说明都配齐了你照着第 4 章的链路走一遍再挑第 6 章任意一个改造点动手课设答辩基本稳。希望帮到你。本文还有配套的精品资源点击获取