尧图精选

Java+JSP+MySQL毕设选题系统:从建库到发布避坑指南

🕒 发布时间:2026/10/1 19:05:18 📁 来源:尧图网络
简介这是一份基于JavaJspMysql实现的高校毕业设计选题系统完整项目适合计算机专业毕业设计参考、Java Web课程实训及自学练手。系统采用经典MVC分层结构实现管理员、教师、学生三种角色闭环管理管理员统一维护学生、教师与课题数据并进行系统维护教师可维护个人信息、添加并查看自己的课题学生可查看全部选题并在线选择流程覆盖管理员—教师—学生的完整业务链。资源共221个文件主要包含98个JSP页面、12个Java源码、24个class文件以及MySQL数据库脚本、XML配置、JAR依赖库、CSS样式、GIF配图和Excel说明文档等压缩包仅5.57MB轻量易部署。项目SQL脚本位于sql文件夹可直接导入数据库部署门槛低代码结构清晰便于按角色模块进行二次开发或用于毕业设计答辩演示。目前已有2416人学习下载经多轮测试运行无误可放心参考使用。1. 用JavaJspMysql做毕设选题系统一套能答辩也能上线的技术选型每年毕业季网上都会冒出大量“JavaJspMysql实现Web毕业设计选题系统”的需求学生要交毕设老师要管理选题而传统JSP项目恰好是教学方案里成本最低、最容易把原理讲清的一套组合。它解决的典型问题很具体——学生登录浏览课题、提交选题申请、教师审核、管理员重置流程业务闭环不复杂却覆盖了Servlet、Session、JDBC、SQL事务这些Java Web核心考点。适合两类人一是第一次完整做Web项目的应届生需要一份可运行、可解释、能接住追问的代码二是要把老系统快速迁移或重构的工程师。我下面按实际做过的方案展开从建库、搭框架到打包部署的坑位一次交代清楚。2. 先建数据库选题系统的三张表与角色权限模型2.1 角色模型学生、教师、管理员在JSP页面里怎么区分毕业设计选题系统的业务闭环并不复杂。学生登录后浏览课题库每个课题有容量上限比如某位教师最多带6个人学生选定一个课题后进入待审核状态教师看到自己名下课题的申请记录选择通过或退回管理员负责兜底比如学生双选冲突、误操作选错题时由管理员手动重置。这里首先要做一个角色建模决策是把三类角色拆成三张表还是放进同一张用户表用role字段区分。拆三张表的好处是字段各自专有——学生有学号教师有职称、研究方向——但选题系统的业务里这些扩展字段使用频率极低拆表反而让登录校验的逻辑变复杂。我一般建议用一个sys_user表加role字段1代表学生2代表教师3代表管理员通过外键关联课题表和选题记录表。这样设计还有个答辩时的实用好处画ER图时三张表之间的关系干净外键、唯一约束都能讲出真实业务依据不是为建表而建表。在传统的JavaJspMysql项目里Session中存一个User对象JSP页面通过${sessionScope.loginUser.role}判断当前是什么角色再决定页面顶部显示哪个导航菜单。不少新手会把角色判断写成字符串“student”“teacher”这样也能跑但数据库里存TINYINT更省空间也更容易在Servlet里做整数分支。接口传参时记得做白名单校验不能直接把前端传来的role值写进数据库否则就有了越权创建管理员的洞。2.2 MySQL表结构设计从字段到索引一次定义到位下面这份DDL是整套系统的地基。我把它按“用户、课题、选题记录”三张表拆开每一处约束都在对应真实业务规则CREATE DATABASE IF NOT EXISTS graduation_selection DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE graduation_selection; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) NOT NULL, student_no VARCHAR(20) NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1学生 2教师 3管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT系统用户表; CREATE TABLE topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT, teacher_id INT NOT NULL, max_students INT NOT NULL DEFAULT 1, selected_count INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT 1开放 0关闭, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_teacher_id (teacher_id) ) ENGINEInnoDB COMMENT课题表; CREATE TABLE selection_record ( id INT PRIMARY KEY AUTO_INCREMENT, topic_id INT NOT NULL, student_id INT NOT NULL, select_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1通过 2退回, teacher_comment VARCHAR(500), UNIQUE KEY uk_topic_student (topic_id, student_id), KEY idx_student_status (student_id, status) ) ENGINEInnoDB COMMENT选题记录表;表结构里最容易讲不清的是为什么要在topic表里冗余一个selected_count字段。如果是严格关系型设计已选人数随时可以用COUNT(*)统计不用冗余。但毕设系统的课题列表页需要在一次循环里同时展示“剩余名额”和“容量”如果每次都去count列表页就要为每个课题发一条子查询10个课题就是20条SQL页面延迟立刻上去。用selected_count冗余后列表页一次查完即可代价是必须在选题、退选、重置时用事务同步维护这个字段否则数字会花。索引方面除了主键我还会在selection_record上建(student_id, status)联合索引因为最频繁的查询是“某学生选过的课题按状态过滤”。topic表的teacher_id也必须有索引教师登录后第一件事就是查自己发布的课题。这几条索引在几千条数据时看不出差距但答辩时能展开讲索引选择逻辑面试官会认同你不是背题的。提示建表统一用utf8mb4不要用utf8。如今JSP页面里的默认表情、部分生僻字都会让utf8插入失败utf8mb4是MySQL 8的默认方向早改早省事。2.3 连接层选型JDBC裸写还是用连接池如果只做毕业设计很多人会在每个Servlet里写Class.forName(com.mysql.cj.jdbc.Driver)然后每次请求都新建连接。这样不是不能跑但并发一高就卡一旦连接没有finally关闭MySQL的max_connections会被耗尽整个Web项目彻底假死。责任在代码里表面症状却是“数据库连不上”。我一般用Apache DBCP2连接池理由很朴素传统JSP项目丢两个jar进WEB-INF/lib就能用不引入Spring配置集中在properties文件里答辩时能把连接池原理讲完整。相比C3P0DBCP2的配置项更少相比Druid没有那种重型监控面板但一个毕设系统根本用不到慢SQL分析。DataSourceUtil是整套代码里被复用次数最多的类写成这样package com.example.util; import org.apache.commons.dbcp2.BasicDataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DataSourceUtil { private static final BasicDataSource DATA_SOURCE new BasicDataSource(); static { try { Properties props new Properties(); props.load(DataSourceUtil.class.getClassLoader() .getResourceAsStream(db.properties)); DATA_SOURCE.setDriverClassName(props.getProperty(jdbc.driver)); DATA_SOURCE.setUrl(props.getProperty(jdbc.url)); DATA_SOURCE.setUsername(props.getProperty(jdbc.username)); DATA_SOURCE.setPassword(props.getProperty(jdbc.password)); DATA_SOURCE.setInitialSize(5); DATA_SOURCE.setMaxTotal(20); DATA_SOURCE.setMaxIdle(10); DATA_SOURCE.setMinIdle(5); DATA_SOURCE.setMaxWaitMillis(3000); // 每次取连接前先验证避免MySQL 8小时闲置后连接失效 DATA_SOURCE.setTestOnBorrow(true); DATA_SOURCE.setValidationQuery(SELECT 1); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DATA_SOURCE.getConnection(); } }db.properties里最关键的是连接串参数jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/graduation_selection?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码连接串里四个参数缺一不可characterEncodingutf8解决中文写入乱码serverTimezoneAsia/Shanghai解决MySQL 8的时区警告useSSLfalse避免本地开发时SSL握手开销allowPublicKeyRetrievaltrue解决MySQL 8 caching_sha2_password认证下的公钥获取报错。如果用MySQL 8.0以上版本驱动类名必须写成com.mysql.cj.jdbc.Driver旧版com.mysql.jdbc.Driver在新驱动里已经不存在了。如果你刚装完MySQL还不会配环境变量记得先把bin目录加到Path里否则后续命令行操作全部卡住。3. 搭工程idea新建jsp项目到第一行可运行代码的完整链路3.1 idea新建jsp项目配置Tomcat与项目目录结构IntelliJ IDEA里新建JSP项目有两条路线。一条是直接建Java Enterprise项目勾选Web Application再配置本机Tomcat另一条是先建普通Java项目手动添加Web Facet。新手建议走第一条IDEA会自动生成webapp/WEB-INF/web.xmlArtifacts默认打出war exploded包点运行就能启动Tomcat省去手工配置的挫败感。如果你习惯用Maven管理依赖也可以建Maven WebApp骨架把javax.servlet-api、jstl、mysql-connector-java等依赖写进pom.xml。但纯JSP项目的jar数量很少不用Maven直接往WEB-INF/lib里丢完全可行很多教学项目就是这么带课的。用Maven时要注意servlet-api的scope必须是provided这个问题会在第5章单独展开。项目结构我习惯按功能分包Controller、DAO、Entity清清楚楚src/main/java/com/example ├── entity/ User, Topic, SelectionRecord ├── dao/ 只写数据库操作 ├── servlet/ 接收HTTP请求调度业务 ├── filter/ 编码、登录校验 └── util/ DataSourceUtil src/main/webapp ├── login.jsp ├── index.jsp ├── student/ topic_list.jsp, my_selection.jsp ├── teacher/ publish_topic.jsp, audit_list.jsp ├── admin/ reset.jsp ├── css/js/ ├── WEB-INF │ ├── web.xml │ └── lib/ 所有jar这里给新手一个提醒JSP页面不要全部塞进WEB-INF下。虽然放WEB-INF里更安全、不能直接URL访问但对毕设项目来说页面跳转链路会变得很绕学生做到一半容易把自己绕晕。折中做法是敏感页面加登录过滤器保护不要指望物理路径隐藏来保证安全。JSP本质是服务端渲染模板只要过滤器和角色校验到位放哪都不是安全漏洞。3.2 登录校验过滤器比在每个Servlet里写Session判断干净得多传统JSP项目里最容易被忽略但又最影响体验的是登录态控制。不少同学在每个Servlet里重复写“session.getAttribute为空就重定向”代码冗余且容易漏。用Filter统一拦截是Servlet规范里最合适的做法改一个类就能覆盖所有页面。package com.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.*; import java.io.IOException; WebFilter(/*) 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; String uri req.getRequestURI(); // 登录页、登录接口、静态资源直接放行 if (uri.endsWith(/login.jsp) || uri.endsWith(/LoginServlet) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } HttpSession session req.getSession(false); Object loginUser session null ? null : session.getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明这里没有用WebFilter(urlPatterns /student/*)做细粒度匹配而是用/*全量拦截后在代码里放行。好处是后续要加“学生角色才能访问/student/、教师角色才能访问/teacher/”的限制只需在同一过滤器里加分支判断。很多教材喜欢把登录态判断写在Servlet基类里但对没有继承体系的传统JSP项目过滤器侵入性更小加的代码也最少。参数说明req.getSession(false)是关键。传false时如果当前没有Session就直接返回null不会为未登录用户创建多余对象。传true的话未登录用户先被建一个空Session再进入判断虽然不影响安全校验结果但会无谓增加服务器内存占用。JSP入门阶段的同学看到false往往不理解这行字可以当面试考点记下来。3.3 统一编码过滤器为什么必须在过滤器而不是JSP里写JSP页面里有人用pageEncoding和contentType同时设置UTF-8结果POST请求还是乱码。原因是pageEncoding只控制JSP页面输出本身不控制请求体的解码。浏览器表单提交时按UTF-8编码容器默认按ISO-8859-1解码数据一进Servlet就变成乱码后面存数据库当然也是错的。我用一个编码过滤器在请求生命周期最早层统一设置package com.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } }这段代码看着简单但它必须在LoginFilter之前执行否则先取参数后设置编码就晚了。注解方式下过滤器的执行顺序按类名字典序EncodingFilter排在LoginFilter前面正好符合需求。如果你喜欢保险直接在web.xml里用filter-mapping显式声明顺序那是最高优先级IDE里调整也直观。提示IDEA的File Encodings面板里Global Encoding、Project Encoding、Default encoding for properties files三处都改成UTF-8。这个设置在新建jsp项目时最容易漏漏了之后乱码问题会反复出现。4. 实现选题闭环学生选、教师审、管理员兜底的完整事务4.1 学生端选题用FOR UPDATE加事务解决并发超选学生端核心页面是课题列表每个课题显示标题、指导教师、剩余名额点击“选题”后进入选题Servlet。这里最容易被单测蒙蔽的是并发场景两个学生同时点了最后一个名额朴素写法是先查询“是否已选”再插入记录这个“查-插”的时间差会让两个人同时成功。等老师审核时才发现课题爆了管理员就得手动删记录这就是典型的黑匣子事故。解决方式不需要分布式锁在单库单表架构下对课题行加锁就够了。先SELECT ... FOR UPDATE锁定目标行再判断容量、插入记录、更新已选数。下面代码给出完整流程package com.example.servlet; import com.example.util.DataSourceUtil; import com.example.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; WebServlet(/student/ChooseTopicServlet) public class ChooseTopicServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); HttpSession session request.getSession(false); User loginUser (User) (session null ? null : session.getAttribute(loginUser)); if (loginUser null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } int topicId Integer.parseInt(request.getParameter(topicId)); int studentId loginUser.getId(); Connection conn null; try { conn DataSourceUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 锁定课题行防止并发超选 String lockSql SELECT max_students, selected_count, status FROM topic WHERE id? FOR UPDATE; try (PreparedStatement ps conn.prepareStatement(lockSql)) { ps.setInt(1, topicId); try (ResultSet rs ps.executeQuery()) { if (!rs.next()) { conn.rollback(); request.setAttribute(msg, 课题不存在); request.getRequestDispatcher(/student/topic_list.jsp).forward(request, response); return; } if (rs.getInt(status) ! 1) { conn.rollback(); request.setAttribute(msg, 课题未开放选课); request.getRequestDispatcher(/student/topic_list.jsp).forward(request, response); return; } if (rs.getInt(selected_count) rs.getInt(max_students)) { conn.rollback(); request.setAttribute(msg, 名额已满请选其他题目); request.getRequestDispatcher(/student/topic_list.jsp).forward(request, response); return; } } } // 2. 插入选题记录状态默认0待审核 String insertSql INSERT INTO selection_record(topic_id, student_id, select_time, status) VALUES (?, ?, NOW(), 0); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, topicId); ps.setInt(2, studentId); ps.executeUpdate(); } // 3. 冗余字段同步1 String updateSql UPDATE topic SET selected_count selected_count 1 WHERE id?; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, topicId); ps.executeUpdate(); } conn.commit(); response.sendRedirect(request.getContextPath() /student/topic_list.jsp?success1); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (Exception ignore) { } } throw new ServletException(选题失败, e); } finally { if (conn ! null) { try { conn.close(); } catch (Exception ignore) { } } } } }逻辑说明FOR UPDATE是InnoDB的行级悲观锁锁住的这一行从“查询容量”到“更新已选数”之间其他事务的FOR UPDATE会等待从根源上掐掉并发超选。使用这套方案前要确认topic表是InnoDB且id是主键。如果表是MyISAM行锁会退化成表锁并发能力大幅下降整个列表页操作都会被拖慢。参数说明setAutoCommit(false)之后每一步都必须严谨。finally里的conn.close()把连接还给连接池不主动提交的话事务在归还前就已经通过commit或rollback结束。业务分支里的rollback点在容量已满、课题关闭等场景直接返回页面提示catch里的rollback则是兜住SQL异常两条路径分开处理不会出现“异常后还继续提交”的尴尬。事务提交后要对密码字段做掩码处理吗不需要密码在登录时用MD5加盐存储即可。实体类里的password字段加JsonIgnore或者置空再放Session避免JSP页面误展示。4.2 教师端审核JSP列表页与审核Servlet教师登录后默认看到自己发布的课题每个课题下关联的待审核申请需要一屏展示。这里不需要复杂前端表格组件一个JSP加JSTL循环就能搞定。JSP里强烈建议用JSTL而不是scriptlet写if/for页面短得多也不会满屏% %。教师审核列表片段% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table tr th学号/th th姓名/th th课题/th th申请时间/th th操作/th /tr c:forEach varapply items${applyList} tr td${apply.studentNo}/td td${apply.studentName}/td td${apply.topicTitle}/td td${apply.selectTime}/td td form action${pageContext.request.contextPath}/teacher/AuditServlet methodpost input typehidden namerecordId value${apply.id}/ input typehidden nameaction valueapprove/ button typesubmit通过/button /form form action${pageContext.request.contextPath}/teacher/AuditServlet methodpost input typehidden namerecordId value${apply.id}/ input typehidden nameaction valuereject/ input typetext nameteacherComment placeholder退回原因必填/ button typesubmit退回/button /form /td /tr /c:forEach /table教师在看到applyList之前AuditListServlet要先从selection_record关联topic和sys_user查出数据封装成一个ApplyView对象字段包括recordId、studentNo、studentName、topicTitle、selectTime。查询时用PreparedStatement或者MyBatis不要用字符串拼接SQL否则一个单引号就能让整个选题表被拖库。审核Servlet的业务本质是一次状态更新。设计细节在于通过时selected_count不动退回时是否把名额减回去答案是要减否则学生退回后重新选题容量计数越走越高。更稳的做法是课题列表页直接用SQL实时统计已通过数量而不是信任冗余字段。如果坚持用冗余字段审核Servlet里退回分支必须同步执行“更新记录状态selected_count减1”两件事并且放在同一个事务里。管理员的统计报表才能对得上。4.3 管理员重置与统计报表管理员模块的核心不是增删改查而是两件事重置学生选课状态、查看选题统计。重置的典型场景是学生误选教师还没审核管理员直接删除selection_record记录并把topic.selected_count减1。这个操作必须放在事务里不然删了记录名额没减或者名额减了记录还在数据就对不上了。统计页最常用的一条SQL是SELECT t.id, t.title, t.max_students, t.selected_count, COUNT(sr.id) AS approved_count FROM topic t LEFT JOIN selection_record sr ON sr.topic_id t.id AND sr.status 1 GROUP BY t.id;这条SQL用LEFT JOIN而不是INNER JOIN是因为有的课题还没有任何人申请但仍然要显示在页面上。GROUP BY t.id之后approved_count统计的是已通过的学生数selected_count是冗余的申请数两者一起看就知道哪些课题还剩真实名额。如果两个数字对不上优先查审核退回操作是否同步更新了计数器。管理员页面本身不需要太多魔法一个search.jsp拉出全部用户和课题状态配合reset按钮即可。这里要注意的是管理员操作必须记录日志。最简单的方式就是在selection_record表加一个operation_log字段或者在工程里加一张audit_log表。答辩时被问到“误操作怎么恢复”有日志可查和没有日志是完全不同的回答深度。5. 避坑清单传统jsp项目打包war的5个高频翻车点5.1 Public Key Retrieval is not allowedMySQL 8驱动联调第一道坎现象项目启动后第一次执行SQL控制台直接抛出java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed整个选题列表加载不出来。原因MySQL 8默认认证插件是caching_sha2_password在非SSL连接下客户端需要向服务器请求公钥来完成加密口令交换。驱动为了安全默认禁止自动获取公钥于是连接建立阶段就失败。解决jdbc.url里追加两个参数组合出现useSSLfalseallowPublicKeyRetrievaltrue。如果你的网络环境要求必须开SSL那就用useSSLtrue并导入服务器证书但对毕业设计项目来说关SSL简化开发更务实生产环境再考虑证书体系。这个参数在db.properties里改了以后需要重启Tomcat才生效光点刷新页面没用。5.2 JSP页面中文全乱码四个地方必须对齐现象浏览器打开页面中文字符变成问号或乱码刷新多次时好时坏。原因JSP页面本身编码、服务器响应输出编码、请求参数解码编码、数据库连接编码四个环节只要有一层配置不一致中文就会在某一环变形。常见情况是JSP文件用UTF-8保存但连接串里没加characterEncoding数据库存进去的就是乱码。解决第一JSP头写成% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %第二IDEA的File Encodings面板三处全部设为UTF-8第三用第3.3节的编码过滤器统一设置请求和响应第四db.properties连接串加上characterEncodingutf8。四条同时满足中文问题基本绝迹。如果还乱检查MySQL表结构默认字符集是不是建表时被建成了latin1。5.3 Tomcat部署后404欢迎页、上下文路径和war命名现象war包放进Tomcat的webapps目录后启动访问http://localhost:8080/直接404访问http://localhost:8080/项目名/也404。原因一半是web.xml里没配欢迎页一半是Artifacts输出名导致context path不对。Tomcat启动日志里其实已经打印了部署路径但很多人不看日志直接猜。解决web.xml加上welcome-filelogin.jsp/welcome-file确保login.jsp在webapp根目录。部署路径对应的是war包文件名访问时要带一层目录。为了评委演示方便我一般把构建产物改名叫ROOT.war或配置Tomcat的context映射到根路径这样输入http://localhost:8080/就直接进登录页少一层目录演示时出错概率低。5.4 javax.servlet-api与Tomcat原生类冲突现象IDEA里编译正常部署到Tomcat后启动报NoClassDefFoundError或提示某个Servlet类被重复加载。原因项目把servlet-api.jar打包进了WEB-INF/lib而Tomcat的lib目录里自带一份同款类。类加载器里出现两份javax.servlet的接口类加载顺序不同导致版本冲突。Maven项目则是因为scope没有设成provided。解决WEB-INF/lib里不要放servlet-api、jsp-api这类容器提供的jar。用Maven时依赖声明写成dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependencyprovided表示编译期需要、运行期由Tomcat提供打war时不会打进去。这是“传统jsp项目打包war”最容易踩的依赖坑。5.5 Tomcat无法Undeploy连接池线程泄漏导致的假死现象在IDEA里点热部署或Tomcat Manager里点Undeploy应用卡在“正在停止”日志停在线程等待最后要杀进程。原因DBCP连接池创建的线程是non-daemon如果应用销毁时没有显式关闭数据源Tomcat会等这些线程退出等不到就假死。很多情况下背后是代码里连接没close连接池的维护线程一直无法回收。解决写一个ServletContextListener在contextDestroyed里显式关闭数据源package com.example.listener; import com.example.util.DataSourceUtil; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; WebListener public class ContextListener implements ServletContextListener { Override public void contextInitialized(ServletContextEvent sce) { // 可在这里加载全局配置 } Override public void contextDestroyed(ServletContextEvent sce) { DataSourceUtil.closeDataSource(); } }DataSourceUtil类里加一个closeDataSource方法调用BasicDataSource的close()即可。这个坑平时不出现只在反复热部署时给你来一下解决一次就养成固定习惯所有Servlet的finally块里必须归还连接Listener里必须关池子。提示出现假死时最快的自救是直接重启Tomcat然后看catalina.out里最后几十行日志凡是在“Waiting for n non-daemon threads”附近停了很久的基本都是连接池泄漏。6. 进阶把JSP项目往Spring Boot迁移的三步走做完这套系统如果准备找工作那句“JavaJspMysql”只是第一层脚手架。真正被面试官追问的多半不是JSP标签而是“怎么防止超选”“事务边界在哪”“SQL怎么优化”。所以我建议的进阶路径是把连接层换掉、把控制器换掉、把登录校验换掉但业务逻辑不动表结构和事务边界全部保留这样你就有一份能讲出渊源的“改造经历”。迁移时先做三件事。第一把topic和selection_record的DDL原封不动搬过去表名、索引、约束都不用改。第二把ChooseTopicServlet里的事务逻辑抽到一个Transactional的Service方法中。这里有个血泪经验事务注解默认只在方法入口生效如果类内部用this调用另一个方法会绕过Spring代理事务失效超选问题会像绿皮火车一样直着开回来。所以要单独注入一个Service让Spring的代理对象去执行方法。第三页面层可以保留JSP加上spring-boot-starter-tomcat和jasper依赖就能继续用也可以改造成Thymeleaf二者选一个别同时上。做完迁移验证工作我习惯用三张测试单。第一张是功能验证表覆盖“学生选课题、教师通过、学生重新选、管理员重置”四个主流程每个流程记录期望结果和实际结果。第二张是并发验证表用两个浏览器窗口同时登录不同账号同时点同一个课题的最后一个名额看是否只有一个成功。第三张是边界验证表比如教师退回时不填原因是否被拦截、退选后再选统计数是否对得上。这三张表整理一下就能变成答辩时的项目材料比截图更硬核。我踩过最不值当的坑是以为“加了事务就一定安全”。其实如果Service方法里catch了异常没有继续向上抛Transactional感知不到失败照样提交。后来我给自己定了个规矩手写事务代码时catch块第一行永远是rollback先回滚再考虑要不要做补偿。这套JavaJspMysql的毕设选题系统做完你对数据一致性的理解会比背八股文扎实得多希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →