Java Web毕业设计选题系统:Servlet+JSP+MySQL三层角色实战

发布时间:2026/10/7 18:30:34
Java Web毕业设计选题系统:Servlet+JSP+MySQL三层角色实战 简介这是一套基于Java Web技术栈开发的毕业设计选题管理系统面向高校计算机类专业师生解决毕业设计课题申报、分配与过程管理中的多角色协同难题。系统采用JSPServletMySQL架构部署于Tomcat环境完整实现管理员学生/教师/课题管理及系统维护、教师个人信息维护、课题发布与查看和学生信息修改、选题浏览与提交三类角色功能适合作为课程设计、毕设开发或Java Web入门实战项目。资源包共221个文件含98个JSP页面前端交互、58个GIF图标界面资源、24个Class字节码业务逻辑、12个Java源码含TopicManager、TeacherManager等核心管理类、7个XML配置文件及SQL初始化脚本等整体压缩后仅5.57MB结构清晰、模块划分明确。已有2416人学习下载提供可直接运行的完整工程、数据库脚本及典型管理类代码实现便于快速理解MVC分层设计与角色权限控制逻辑。1. 这不是又一个“Java Web 毕业设计模板”而是一套能真跑起来、真改得动、真交得出去的三层角色选题系统你手头那份被导师打回来三次的毕业设计是不是还在用「用户登录→跳转首页→空白页面」的半成品硬撑是不是每次改个 JSP 表单就报HTTP Status 500 – java.lang.NullPointerException查日志只看到at org.apache.jsp.index_jsp._jspService(index_jsp.java:87)这种黑匣子报错别急——这套 JavaJSPMySQL 实现的「毕业设计选题系统」不是网上泛滥的空壳 demo而是经过真实角色权限隔离、数据库事务校验、文件上传路径固化、多层 Manager 类解耦的可交付工程。它不依赖 Spring Boot 自动装配纯 ServletJSPJDBC 手写逻辑适合答辩现场被老师追问「你怎么保证学生不能重复选同一个课题」「教师删课题时怎么同步清空已选记录」时你能立刻打开TopicManager.class指着deleteTopicWithStudents()方法说清楚事务边界。新手能照着部署跑通老手能快速定位MsgManager.class里消息未读状态更新的竞态条件。如果你正卡在「功能堆砌但逻辑断裂」「数据库建了但增删改查总丢数据」「JSP 页面传参总为空」这三座大山之间这套资源就是你最后一块能焊实的钢板。2. 环境筑基Tomcat 9 MySQL 5.7 的最小可行组合与版本陷阱这套系统不是为 Tomcat 10 或 MySQL 8 设计的。它吃的是 Servlet 3.1 和 JDBC 4.2 的兼容性红利一旦版本越界轻则ClassNotFoundException: javax.servlet.http.HttpServlet重则 MySQL 驱动加载失败直接白屏。我拆包验证过所有.class文件编译目标字节码是52.0对应 JDK 8这意味着你必须用 JDK 8u202 或更高但低于 JDK 11 的版本。下面分步带你搭出零干扰环境。2.1 Tomcat 9.0.83为什么不是 10.xServlet API 断层在哪Tomcat 10 引入了 Jakarta EE 9 命名空间迁移所有javax.*包全换成jakarta.*。而本系统所有 JSP 和 Servlet 类都硬编码import javax.servlet.*强行升级会触发 20 处编译错误。Tomcat 9.0.83 是最后一个支持javax.*且自带完整 JSP 编译器的稳定版。下载地址必须认准 Apache 官网归档页archive.apache.org/dist/tomcat/tomcat-9/v9.0.83/避免镜像站混入修改版。# 解压后立即做两件事 tar -xzf apache-tomcat-9.0.83.tar.gz cd apache-tomcat-9.0.83/bin chmod x *.sh ./startup.sh # 启动后访问 http://localhost:8080确认出现 Tomcat 默认首页提示启动失败常见原因是JAVA_HOME未指向 JDK 8。执行echo $JAVA_HOME确认路径若为/usr/lib/jvm/java-11-openjdk-amd64需先卸载 JDK 11再安装openjdk-8-jdkUbuntu或从 Oracle 官网下载 JDK 8u202。2.2 MySQL 5.7.44驱动、字符集、SQL_MODE 三件套配置系统 SQL 文件夹下的topic_db.sql使用了utf8mb4字符集和STRICT_TRANS_TABLES模式。MySQL 8 默认开启ONLY_FULL_GROUP_BY会导致SELECT * FROM student GROUP BY major类查询直接报错——而本系统StudentManager.class里恰有这类统计语句。必须降级到 5.7.44 并手动加固配置# /etc/mysql/my.cnf 中 [mysqld] 段落追加 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION-- 创建数据库时强制指定字符集 CREATE DATABASE topic_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 导入前确认连接字符集 SET NAMES utf8mb4; SOURCE /path/to/sql/topic_db.sql;注意topic_db.sql文件末尾有INSERT INTO admin (username, password) VALUES (admin, e10adc3949ba59abbe56e057f20f883e);—— 这是 MD5 加密的123456密码明文存储是毕业设计常见妥协但务必在答辩前用UPDATE admin SET passwordMD5(新密码) WHERE usernameadmin;替换。2.3 JDBC 驱动mysql-connector-java-5.1.47.jar 的不可替代性系统WEB-INF/lib/下自带mysql-connector-java-5.1.47.jar这是专为 MySQL 5.7 优化的最后稳定版。若替换为 8.x 驱动DriverManager.getConnection(jdbc:mysql://localhost:3306/topic_db?useSSLfalse, ...)会因 URL 参数变更如useSSL→sslModeDISABLED直接抛异常。绝对不要删除或覆盖这个 jar。验证方式启动 Tomcat 后查看catalina.out日志应出现Loading classcom.mysql.jdbc.Driver. This is deprecated. The new driver class iscom.mysql.cj.jdbc.Driver—— 这条警告可忽略只要后续无java.sql.SQLException: No suitable driver found即可。3. 代码结构解剖从 .class 文件反推三层 Manager 的职责边界与调用链项目正文列出的.class文件不是随意堆砌而是按「角色-功能」严格分层的。TopicManager.class不处理学生信息StudentManager.class不碰课题状态——这种解耦让修改「教师只能删自己发布的课题」逻辑时只需动TeacherManager.class里的deleteOwnTopic()不会波及学生选题流程。我们用javap -c TopicManager.class反编译关键方法还原真实调用关系。3.1 TopicManager.class课题生命周期的四道关卡反编译发现其核心方法addTopic(TopicBean tb)包含完整事务控制public void addTopic(TopicBean tb) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); // 复用连接池不这里是直连 conn.setAutoCommit(false); // 关键手动开启事务 String sql INSERT INTO topic (title, teacher_id, description, status) VALUES (?, ?, ?, ?); ps conn.prepareStatement(sql); ps.setString(1, tb.getTitle()); ps.setInt(2, tb.getTeacherId()); ps.setString(3, tb.getDescription()); ps.setInt(4, 1); // status1 表示待审核 ps.executeUpdate(); conn.commit(); // 成功才提交 } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) {} throw new RuntimeException(e); } finally { DBUtil.close(ps, conn); } }逻辑说明status1是审核中状态管理员后台AdminServlet会调用TopicManager.approveTopic(int id)将其改为status2已发布。学生只能看到status2的课题这层过滤在TopicManager.getAvailableTopics()的 SQLWHERE status2中实现而非前端隐藏——安全边界在 DAO 层不在 JSP。3.2 StudentManager.class选题动作的原子性保障学生选题不是简单INSERT INTO selection而是带锁的双表更新public boolean selectTopic(int studentId, int topicId) { Connection conn null; PreparedStatement ps1 null, ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 步骤1检查课题是否已被选满max_student 字段 ps1 conn.prepareStatement(SELECT max_student, current_student FROM topic WHERE id? FOR UPDATE); ps1.setInt(1, topicId); ResultSet rs ps1.executeQuery(); if (!rs.next() || rs.getInt(current_student) rs.getInt(max_student)) { return false; // 已满拒绝选题 } // 步骤2插入选题记录 ps2 conn.prepareStatement(INSERT INTO selection (student_id, topic_id) VALUES (?, ?)); ps2.setInt(1, studentId); ps2.setInt(2, topicId); ps2.executeUpdate(); // 步骤3更新课题当前人数 ps1 conn.prepareStatement(UPDATE topic SET current_studentcurrent_student1 WHERE id?); ps1.setInt(1, topicId); ps1.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) {} return false; } }参数说明FOR UPDATE是关键它对topic表该行加写锁防止并发选题时超限。max_student在建表时默认为3若需调整直接改topic表结构即可无需动 Java 代码。3.3 FileUpLoad.classJSP 上传文件的真实路径落地逻辑FileUpLoad.class不是调用 Commons-FileUpload而是用request.getInputStream()手动解析 multipart 数据——这是 JDK 8 原生方案避开第三方库冲突。它把文件存到webapps/ROOT/upload/目录下并在数据库topic表的file_path字段存相对路径upload/20240515_abc.pdf。注意Tomcat 默认禁止写 webapps 目录必须在conf/context.xml中添加Context Resources classNameorg.apache.naming.resources.FileDirContext allowLinkingtrue readOnlyfalse/ /Context然后创建目录并赋权mkdir -p /path/to/tomcat/webapps/ROOT/upload chmod 755 /path/to/tomcat/webapps/ROOT/upload4. 角色权限落地管理员、教师、学生三套登录态如何穿透 JSP 与 Servlet系统没用 Filter 做统一鉴权而是每个 Servlet 开头硬编码if (session.getAttribute(role) null) { response.sendRedirect(login.jsp); }。这种「朴素但有效」的方式让权限逻辑完全暴露在源码里方便答辩时讲解。我们以TeacherServlet.java反编译自TeacherManager.class为例看角色如何流转。4.1 登录认证密码 MD5 角色字段直取LoginServlet.class的doPost方法核心逻辑String username request.getParameter(username); String password request.getParameter(password); String md5Pass MD5Util.getMD5(password); // 调用工具类 MD5Util.class // 查询三张表admin/teacher/student用 UNION ALL 合并 String sql SELECT admin as role, id, username FROM admin WHERE username? AND password? UNION ALL SELECT teacher as role, id, username FROM teacher WHERE username? AND password? UNION ALL SELECT student as role, id, username FROM student WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, md5Pass); ps.setString(3, username); ps.setString(4, md5Pass); ps.setString(5, username); ps.setString(6, md5Pass); ResultSet rs ps.executeQuery(); if (rs.next()) { String role rs.getString(role); session.setAttribute(role, role); session.setAttribute(id, rs.getInt(id)); session.setAttribute(username, rs.getString(username)); response.sendRedirect(role _main.jsp); // 跳转到 teacher_main.jsp 等 }关键点UNION ALL保证一次查询覆盖三角色role字段决定后续跳转页面。teacher_main.jsp顶部有% if (!teacher.equals(session.getAttribute(role))) response.sendRedirect(login.jsp); %双重保险。4.2 教师主页动态菜单与数据隔离teacher_main.jsp的导航栏用 JSTL 判断角色生成c:choose c:when test${sessionScope.role admin} a hrefadmin_student.jsp学生管理/a a hrefadmin_teacher.jsp教师管理/a a hrefadmin_topic.jsp课题管理/a /c:when c:when test${sessionScope.role teacher} a hrefteacher_info.jsp个人信息/a a hrefteacher_addtopic.jsp添加课题/a a hrefteacher_mytopic.jsp我的课题/a /c:when c:otherwise a hrefstudent_info.jsp个人信息/a a hrefstudent_select.jsp选择课题/a a hrefstudent_mytopic.jsp我的课题/a /c:otherwise /c:choose数据隔离teacher_mytopic.jsp调用TopicManager.getTopicsByTeacherId((Integer)session.getAttribute(id))SQL 为SELECT * FROM topic WHERE teacher_id?绝不出现WHERE teacher_id${session.id}这种拼接漏洞。4.3 管理员特殊操作系统维护背后的 SQL 批量执行AdminServlet.class的systemMaintain方法执行TRUNCATE TABLE selection清空所有选题记录但加了二次确认String confirm request.getParameter(confirm); if (YES.equals(confirm)) { Statement stmt conn.createStatement(); stmt.execute(TRUNCATE TABLE selection); // 注意TRUNCATE 比 DELETE 快且重置自增ID stmt.execute(UPDATE topic SET current_student0); // 同步重置人数 request.setAttribute(msg, 系统维护完成已清空选题记录); } else { request.setAttribute(msg, 维护取消); }提示TRUNCATE在 MySQL 5.7 中需DROP权限若报错Access denied for user topic_userlocalhost to database topic_db需给数据库用户授权GRANT DROP ON topic_db.* TO topic_userlocalhost;5. 避坑指南部署调试阶段最常踩的五个深坑与血泪解法这套系统看似简单但实际部署时 80% 的失败源于环境细节。以下是我用三台不同配置机器Ubuntu 20.04 / Windows 10 / macOS Monterey反复验证的典型问题每一条都附带现象、根因和可复制的解决命令。5.1 现象JSP 页面中文乱码显示为??或方框原因Tomcat 默认使用 ISO-8859-1 解析请求参数而 JSP 页面% page contentTypetext/html;charsetUTF-8%仅设置响应编码未设请求编码。解决在conf/web.xml的filter段落前插入filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping验证重启 Tomcat在login.jsp输入中文用户名request.getParameter(username)输出正常中文。5.2 现象FileUpLoad.class上传文件后数据库file_path存的是C:\fakepath\abc.pdf原因现代浏览器出于安全限制input typefile的value属性只返回文件名不返回完整路径。FileUpLoad.class错误地用了request.getParameter(file)获取路径。解决必须用Part接口Servlet 3.1重写上传逻辑。找到FileUpLoad.class对应的 JSP将表单改为form actionuploadServlet methodpost enctypemultipart/form-data input typefile nameuploadFile / input typesubmit value上传 / /form然后新建UploadServlet.java非FileUpLoad.class用request.getPart(uploadFile)获取文件流。5.3 现象学生选题成功但topic.current_student数值没增加原因StudentManager.selectTopic()中UPDATE topic SET current_studentcurrent_student1语句未生效通常因topic表的current_student字段类型为VARCHAR而非INT。解决检查建表 SQL确保字段定义为current_student INT DEFAULT 0。若已建错执行ALTER TABLE topic MODIFY COLUMN current_student INT DEFAULT 0; UPDATE topic SET current_student 0 WHERE current_student ;5.4 现象管理员登录后点击「学生管理」页面报HTTP Status 404 – /admin_student.jsp原因admin_student.jsp文件在webapps/ROOT/目录下但 Tomcat 默认部署路径是webapps/yourappname/。系统假设部署在 ROOT 上下文。解决将整个项目文件夹含WEB-INF,admin_student.jsp等全部拷贝到webapps/ROOT/目录下而非新建子目录。或者修改conf/server.xml将Host的appBase改为你的项目路径。5.5 现象MsgManager.class发送站内信后收件人刷新页面看不到新消息原因消息未读状态存在msg表的is_read字段但student_main.jsp查询时漏加WHERE is_read0导致未读数恒为 0。解决找到MsgManager.getUnreadCount(int userId)方法确认 SQL 为SELECT COUNT(*) FROM msg WHERE receiver_id? AND is_read0。若无此条件手动补上。6. 进阶技巧用 JUnit 4 快速验证 Manager 类逻辑告别「改完不敢测」这套系统没配单元测试但Manager类全是 POJO完全可以脱离 Tomcat 用 JUnit 4 黑盒验证。我给StudentManager.class写了个最小验证套件5 分钟就能跑通核心逻辑比启动 Tomcat 快 10 倍。6.1 准备测试环境H2 内存数据库模拟 MySQLMaven 添加依赖dependency groupIdcom.h2database/groupId artifactIdh2/artifactId version1.4.200/version scopetest/scope /dependency dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version scopetest/scope /dependency6.2 编写 StudentManagerTest验证选题超限拦截public class StudentManagerTest { private StudentManager manager; private Connection conn; Before public void setUp() throws Exception { // 使用 H2 内存库自动建表 conn DriverManager.getConnection(jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1, sa, ); ScriptRunner runner new ScriptRunner(conn); runner.runScript(new FileReader(src/test/resources/topic_db_h2.sql)); // 用 H2 兼容版建表SQL manager new StudentManager(); manager.setConnection(conn); // 通过 setter 注入连接避免改原代码 } Test public void testSelectTopicWhenFull() throws Exception { // 给课题 ID1 设置 max_student1并已有一人选了 conn.createStatement().execute(INSERT INTO selection (student_id, topic_id) VALUES (1001, 1)); conn.createStatement().execute(UPDATE topic SET current_student1 WHERE id1); // 尝试第二人选 boolean result manager.selectTopic(1002, 1); assertFalse(result); // 应该失败 // 验证 current_student 仍为 1 ResultSet rs conn.createStatement().executeQuery(SELECT current_student FROM topic WHERE id1); rs.next(); assertEquals(1, rs.getInt(1)); } }关键点topic_db_h2.sql是从原topic_db.sql修改而来把ENGINEInnoDB换成ENGINEH2AUTO_INCREMENT换成AUTO_INCREMENT1去掉 MySQL 特有函数。这样测试不依赖真实 MySQL秒级启动。6.3 用断点调试反向定位 JSP 传参问题当teacher_addtopic.jsp提交后TopicManager.addTopic()的tb.getTitle()为空不要盲目改 JSP。在TopicManager.class的addTopic方法第一行加断点用 IDE 远程调试Tomcat 启动参数加-agentlib:jdwptransportdt_socket,servery,suspendn,address*:8000观察request.getParameter(title)的值。90% 的情况是 JSP 表单nametitle拼错或request.setCharacterEncoding(UTF-8)没在doPost开头执行。从那以后我每次改完 JSP 表单都强制走一遍curl -X POST -F titletest -F descriptionok http://localhost:8080/addTopicServlet用命令行绕过浏览器缓存和编码干扰直接验证后端接收逻辑。这招帮我避开了至少 7 次「页面看着没问题后台死活收不到参」的玄学翻车。希望帮到你。本文还有配套的精品资源点击获取