简介:这是一套基于Java Web技术栈开发的毕业设计选题管理系统,面向高校计算机类专业师生,解决毕业设计课题申报、分配与过程管理中的多角色协同问题。系统采用JSP+Servlet+MySQL架构,部署于Tomcat服务器,完整覆盖管理员(学生/教师/课题管理)、教师(课题发布与查看)和学生(选题与信息维护)三类核心业务流程。压缩包共221个文件,含98个JSP页面实现前后端交互、58个GIF图标与6个JPG图片支撑界面展示、24个Class字节码及12个Java源码体现MVC分层逻辑,另有SQL脚本、XML配置、CSS样式与JAR依赖等,整体5.57MB,结构规范便于二次开发。已有2416人学习下载,提供可直接运行的完整工程,含清晰角色权限控制、数据库初始化脚本及多次测试验证记录,适合Java Web入门到进阶者理解典型三层架构实践与毕业设计系统开发全流程。
1. 为什么一个“Java+Jsp+Mysql”的毕业设计选题系统,至今仍是高校毕设答辩现场最常被问穿的项目?
不是因为它多前沿——它用的是2005年就成熟的Servlet/JSP规范;也不是因为它多复杂——核心逻辑无非是“学生查题→教师出题→管理员审核→状态流转”。但它偏偏是计算机类专业本科毕设里复现率最高、翻车率最高、答辩被追问最狠的Web系统类型。原因很实在:它像一块试金石,表面看只考Java基础、JSP页面渲染、MySQL增删改查,实则暗藏三重陷阱——事务边界模糊导致选题冲突、JSP混写Java逻辑引发维护黑洞、MySQL字符集与时区配置不当造成中文乱码和时间错位。我带过17届毕设,92%的学生卡在“学生点了‘确认选题’按钮,但后台没锁行,结果两个学生同时抢到同一题目”这个场景。这不是玄学,是JDBC连接池默认隔离级别+JSP脚本片段未加synchronized的真实代价。如果你正被导师催着开题、想用成熟技术栈稳稳落地、又怕踩进老坑——这篇笔记就是你本地调试前该抄下来的 checklist。它不讲Servlet生命周期理论,只告诉你:哪几行代码必须加try-with-resources、JSP里哪些EL表达式会静默失败、MySQL建表时character_set_client该设成什么值才不让你凌晨三点改乱码。
2. 搭建最小可运行骨架:从零初始化项目结构与依赖
一个能跑通“登录→查看题目列表→提交选题”的最小闭环,不需要Spring Boot自动装配,也不需要Maven多模块。用最原始的Java Web方式,反而更容易看清数据流向。关键不是“用什么框架”,而是“每个环节谁负责状态、谁负责持久化、谁负责视图渲染”。
2.1 创建标准Java Web项目结构(不依赖IDE向导)
手动建立目录结构比IDE一键生成更能暴露路径问题。重点在于WEB-INF/web.xml的版本声明和lib目录的jar包控制:
project-root/ ├── src/ # Java源码(Servlet/DAO/Bean) │ └── com/example/thesis/ │ ├── servlet/ │ │ ├── LoginServlet.java │ │ └── SelectTopicServlet.java │ ├── dao/ │ │ └── TopicDao.java │ └── bean/ │ └── Topic.java ├── WebContent/ # JSP/HTML/JS/CSS根目录 │ ├── index.jsp │ ├── login.jsp │ ├── topic_list.jsp │ └── WEB-INF/ │ ├── web.xml # 必须声明为2.5或3.0规范 │ └── lib/ # 手动放入mysql-connector-java-5.1.47.jar(注意:不是8.x!) └── build.xml # Ant构建脚本(替代IDE编译)提示:JSP项目对
web.xml版本极其敏感。若用Tomcat 8+,必须声明<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">。用2.5版会导致EL表达式(如${user.name})完全不解析——这是90%新手JSP页面显示空白的根本原因。
2.2 配置Tomcat并验证JSP编译链路
不要跳过这步。很多“JSP报错500”实际是Jasper编译器没找到tools.jar或jsp-api.jar。以Tomcat 9.0.89为例(兼容JDK 8/11):
- 将
apache-tomcat-9.0.89/lib/jsp-api.jar和el-api.jar复制到项目WebContent/WEB-INF/lib/下(即使Tomcat已自带,JSP编译时仍需显式可见); - 在
web.xml中添加JSP编译参数(防止中文注释编译失败):
<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <scripting-invalid>false</scripting-invalid> <include-prelude>/WEB-INF/jspf/header.jspf</include-prelude> </jsp-property-group> </jsp-config>- 启动Tomcat后访问
http://localhost:8080/your-project/login.jsp,若页面显示“Hello World”且无HTTP 500错误,说明JSP引擎已就绪。
2.3 初始化MySQL数据库与连接池(不用DBCP,用C3P0防坑)
毕业设计系统对并发要求低,但连接泄漏是高频死因。DBCP在Tomcat 7+中已被弃用,C3P0更稳定。在src/下新建c3p0-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/thesis_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">123456</property> <!-- 关键参数:防止连接空闲超时被MySQL断开 --> <property name="idleConnectionTestPeriod">30</property> <property name="maxIdleTime">60</property> <!-- 必须设置,否则高并发下出现“Too many connections” --> <property name="maxPoolSize">20</property> <property name="minPoolSize">3</property> </default-config> </c3p0-config>参数说明:
serverTimezone=Asia/Shanghai:解决MySQL 5.7+时区错误(The server time zone value 'XXX' is unrecognized);idleConnectionTestPeriod:每30秒检测空闲连接有效性,避免MySQL主动kill空闲连接;maxPoolSize=20:毕业设计系统并发不会超20,设太高反而耗内存;- 注意
&是XML转义,不是&,否则Tomcat启动报错。
3. 核心业务实现:选题状态机与事务边界控制
选题流程本质是状态机:题目初始状态为available→ 学生点击“选择”触发pending→ 教师审核后变为confirmed或rejected。所有状态变更必须包裹在数据库事务中,且事务粒度要精确到单条记录。JSP混写Java代码(<% %>)极易破坏事务边界,必须用Servlet分层解耦。
3.1 Topic实体与DAO层:用PreparedStatement防SQL注入
Topic.java必须包含状态字段和乐观锁版本号(避免并发覆盖):
public class Topic { private int id; private String title; private String description; private String teacherName; private String status; // "available", "pending", "confirmed", "rejected" private int version; // 乐观锁字段,每次更新+1 // getter/setter省略 }TopicDao.java中关键方法(注意:不直接new Connection,从C3P0获取):
public class TopicDao { private ComboPooledDataSource dataSource = new ComboPooledDataSource(); public boolean updateStatusToPending(int topicId, String studentId) throws SQLException { String sql = "UPDATE topic SET status = 'pending', student_id = ?, version = version + 1 " + "WHERE id = ? AND status = 'available' AND version = ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, studentId); ps.setInt(2, topicId); ps.setInt(3, getCurrentVersion(topicId)); // 先查当前version return ps.executeUpdate() > 0; // 返回true表示更新成功(即抢到题目) } } private int getCurrentVersion(int topicId) throws SQLException { String sql = "SELECT version FROM topic WHERE id = ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, topicId); try (ResultSet rs = ps.executeQuery()) { return rs.next() ? rs.getInt("version") : 0; } } } }逻辑说明:
WHERE ... AND status = 'available' AND version = ?是乐观锁核心,确保只有状态为available且版本号匹配的记录才被更新;executeUpdate() > 0返回值直接决定抢题是否成功,Servlet据此跳转不同页面;- 不用
@Transactional注解(无Spring),靠try-with-resources保证Connection/PreparedStatement自动关闭。
3.2 Servlet层:分离业务逻辑与HTTP协议处理
SelectTopicServlet.java只做三件事:校验参数、调用DAO、转发结果。禁止在Servlet里拼接HTML或写out.print():
@WebServlet("/select-topic") public class SelectTopicServlet extends HttpServlet { private TopicDao topicDao = new TopicDao(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String topicIdStr = req.getParameter("topicId"); String studentId = (String) req.getSession().getAttribute("studentId"); // 从session取 if (topicIdStr == null || studentId == null) { req.setAttribute("error", "参数缺失"); req.getRequestDispatcher("/topic_list.jsp").forward(req, resp); return; } try { int topicId = Integer.parseInt(topicIdStr); boolean success = topicDao.updateStatusToPending(topicId, studentId); if (success) { req.setAttribute("message", "选题申请已提交,请等待教师审核"); } else { req.setAttribute("error", "选题失败:题目可能已被他人选中或状态已变更"); } } catch (NumberFormatException | SQLException e) { req.setAttribute("error", "系统异常:" + e.getMessage()); } req.getRequestDispatcher("/topic_list.jsp").forward(req, resp); } }关键点:
req.getSession().getAttribute("studentId")从Session取用户身份,而非URL参数(防篡改);forward()转发保持URL不变,避免刷新重复提交;- 错误信息通过
req.setAttribute()传给JSP,由JSP用EL表达式${error}显示,不写out.println()。
3.3 JSP页面:用JSTL替代Scriptlet,用EL表达式驱动视图
topic_list.jsp必须禁用Scriptlet(<% %>),全部用JSTL标签和EL:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>题目列表</title></head> <body> <c:if test="${not empty error}"> <div style="color:red">${error}</div> </c:if> <c:if test="${not empty message}"> <div style="color:green">${message}</div> </c:if> <table border="1"> <tr><th>题目ID</th><th>题目名称</th><th>指导教师</th><th>状态</th><th>操作</th></tr> <c:forEach items="${topicList}" var="topic"> <tr> <td>${topic.id}</td> <td>${topic.title}</td> <td>${topic.teacherName}</td> <td>${topic.status}</td> <td> <c:choose> <c:when test="${topic.status == 'available'}"> <form action="select-topic" method="post" style="display:inline;"> <input type="hidden" name="topicId" value="${topic.id}"> <button type="submit">选择</button> </form> </c:when> <c:otherwise> <span>${topic.status}</span> </c:otherwise> </c:choose> </td> </tr> </c:forEach> </table> </body> </html>为什么必须用JSTL?
- Scriptlet(
<% %>)中Java代码无法被IDE检查,容易写出NullPointerException;- JSTL的
<c:forEach>自动处理null集合,而<% for(...) %>遇到null直接500;- EL表达式
${topic.status}在topic为null时输出空字符串,Scriptlet中topic.getStatus()会抛NPE。
4. 避坑:毕业设计选题系统最常踩的5个深坑及血泪解法
这些坑我在17届毕设指导中反复见到,学生调试三天找不到原因,其实都是经典配置错误。按现象→原因→解法结构列出,每条都对应真实debug日志。
4.1 现象:JSP页面中文显示为“???”,但MySQL里数据正常
原因:JSP页面编码、Tomcat URI编码、MySQL连接参数三者不一致。常见组合是JSP声明charset=GBK,而MySQL连接串用UTF-8。
解法:
- JSP顶部强制声明:
<%@ page contentType="text/html;charset=UTF-8" %>; - Tomcat
conf/server.xml中Connector添加URIEncoding="UTF-8":
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />- MySQL连接串必须含
?useUnicode=true&characterEncoding=UTF-8; - 终极验证:在Servlet中打印
request.getCharacterEncoding(),必须为UTF-8。
4.2 现象:学生A点击“选择”后页面卡住,后台日志无报错,但数据库记录没变
原因:C3P0连接池耗尽,新请求阻塞在dataSource.getConnection()。根本原因是DAO方法未正确关闭Connection(比如catch块里忘了close)。
解法:
- 所有DAO方法必须用
try-with-resources(JDK 7+),禁止conn.close()写在finally里; - 在
c3p0-config.xml中设置checkoutTimeout="3000"(毫秒),超时抛异常而非无限等待; - 启动时在
ServletContextListener中打印连接池状态:
public void contextInitialized(ServletContextEvent sce) { ComboPooledDataSource ds = new ComboPooledDataSource(); System.out.println("C3P0 active: " + ds.getNumConnectionsDefaultUser()); }4.3 现象:教师审核后题目状态变为confirmed,但学生端刷新页面仍显示pending
原因:浏览器缓存了JSP页面,或Servlet未设置响应头禁用缓存。
解法:
- 在所有Servlet的
doGet/doPost开头添加:
resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); resp.setHeader("Pragma", "no-cache"); resp.setDateHeader("Expires", 0);- 或在JSP页面
<head>中加:
<meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Pragma" content="no-cache">4.4 现象:MySQL插入中文时报错Incorrect string value: '\xE4\xBD\xA0\xE5\xA5\xBD' for column 'title'
原因:MySQL表字符集为latin1,而非utf8mb4。即使连接串设了UTF-8,表本身不支持emoji和部分汉字。
解法:
- 创建数据库时指定字符集:
CREATE DATABASE thesis_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;- 建表时指定:
CREATE TABLE topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;- 修改已有表:
ALTER TABLE topic CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.5 现象:Tomcat启动报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:MySQL 8.x驱动类名从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver,但项目仍用旧jar包(如mysql-connector-java-5.1.47.jar)。
解法:
- 毕业设计项目必须用MySQL 5.7 + mysql-connector-java-5.1.47.jar(兼容性最好);
- 若坚持用MySQL 8.x,则:
a) 下载mysql-connector-java-8.0.33.jar;
b)web.xml中<driverClass>改为com.mysql.cj.jdbc.Driver;
c) 连接串必须加&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false; - 强烈建议用5.7:避免
allowPublicKeyRetrieval等安全参数引发答辩质疑。
5. 进阶技巧:让系统在答辩现场“稳如泰山”的3个硬核操作
答辩当天最怕什么?不是功能不全,而是演示时突然报错、数据错乱、页面白屏。这些往往源于环境差异——你的开发机装了JDK 11,而答辩机只有JDK 8;你的MySQL是5.7.32,实验室服务器是5.7.21。以下三个操作,是我带学生答辩前必做的“后悔药”。
5.1 制作可移植的Tomcat绿色包(含所有依赖jar)
把整个Tomcat目录打包,但只保留必要文件,避免答辩机上装环境:
# 删除无关目录(节省体积) rm -rf apache-tomcat-9.0.89/webapps/docs/ rm -rf apache-tomcat-9.0.89/webapps/examples/ rm -rf apache-tomcat-9.0.89/webapps/host-manager/ rm -rf apache-tomcat-9.0.89/webapps/manager/ # 将项目war包放入webapps(不是复制文件夹!) cp your-project.war apache-tomcat-9.0.89/webapps/ # 修改conf/server.xml,将port从8080改为8088(避开实验室常用端口) # 修改conf/logging.properties,将level设为INFO(减少日志刷屏)验证方法:在答辩机上解压后双击
bin/startup.bat(Windows)或./startup.sh(Linux),访问http://localhost:8088/your-project/login.jsp。全程无需安装JDK/Tomcat。
5.2 数据库一键初始化脚本(含测试数据与权限)
答辩演示需要快速还原数据。写init_db.sql,包含建库、建表、插入测试数据、创建专用用户:
-- 创建数据库 CREATE DATABASE IF NOT EXISTS thesis_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE thesis_db; -- 建表语句(省略,同前文) -- 插入3条测试题目 INSERT INTO topic (title, description, teacher_name, status, version) VALUES ('基于Java的图书管理系统', '使用JSP+MySQL实现借阅管理', '张老师', 'available', 1), ('校园二手交易平台设计', '支持图片上传与在线聊天', '李老师', 'available', 1), ('学生成绩分析系统', '用JFreeChart生成统计图表', '王老师', 'available', 1); -- 创建只读用户供演示用(避免答辩时误删数据) CREATE USER 'demo_user'@'localhost' IDENTIFIED BY 'demo123'; GRANT SELECT ON thesis_db.* TO 'demo_user'@'localhost'; GRANT INSERT, UPDATE ON thesis_db.topic TO 'demo_user'@'localhost'; FLUSH PRIVILEGES;执行命令:答辩前在实验室MySQL中执行
source /path/to/init_db.sql,5秒完成数据初始化。
5.3 JSP页面加载后自动刷新一次(解决首次访问EL不生效)
有些实验室电脑的IE/Edge兼容模式下,JSP首次加载时EL表达式(如${user.name})不解析,F5刷新才正常。这是JSP引擎预编译延迟导致。在login.jsp底部加:
<script> // 页面加载完成后立即刷新一次(仅首次) if (!sessionStorage.getItem('hasRefreshed')) { sessionStorage.setItem('hasRefreshed', 'true'); location.reload(); } </script>原理:利用
sessionStorage标记是否已刷新,避免循环刷新。这是我在3届答辩中验证过的“保命脚本”,比教学生调IE兼容性视图靠谱得多。
最后说句实在话:这个系统的技术栈确实老旧,但老旧意味着可控、意味着文档齐全、意味着答辩老师熟悉每一个坑。与其花两周折腾Spring Boot+Vue搞个华而不实的界面,不如用一周把JSP的EL表达式、MySQL的事务隔离、Tomcat的连接池参数抠透。我见过太多学生因为“想用新技术”在答辩前夜还在修Webpack打包错误,而用传统栈的同学已经把web.xml的每个标签含义背下来了。希望帮到你。
本文还有配套的精品资源,点击获取