
简介基于JSPServlet构建的在线考试管理系统整合jQuery、Bootstrap与JDBC技术面向毕业设计学生与Java Web初学者用于快速实现在线答题与管理后台适合课程设计、毕业设计选题参考。学生端提供试题选择、在线答题、交卷后错题红色标记与正确答案绿色标注并支持实时计时与答题进度提示管理员端支持发布试卷与试题覆盖题目、试卷、用户、角色四大模块包括关键词搜索、试题锁定、权限分配等功能完整。压缩包共104个文件含43个Java源文件、22个JSP页面、12个jar依赖库以及CSS/JS样式脚本、SQL数据库脚本等整体大小6.14MB基于IDEA 2021.3、MySQL 5.7.26、Tomcat 7.0.73与JDK 1.8运行。目前已有43人学习下载。资源附有可导入的项目目录与数据库初始化脚本能直接部署演示代码分层清晰便于理解ServletJSP的请求处理与JDBC数据交互对毕业设计、课程实践均有参考价值。1. 基于jspservlet的在线考试管理系统是什么一个典型JavaWeb课设项目的完整落地如果你正在找java课程设计案例源码大概率会在检索结果里看到“基于jspservlet的在线考试管理系统”这类项目。它不是什么新鲜架构而是JSP做页面、Servlet做控制器、JDBC访问MySQL数据库的经典三层结构用来支撑一套简单的在线考试流程管理员维护题库和试卷学生登录后在线答题系统自动判分并返回成绩。这个方向之所以常被选做课设和毕设是因为它恰好覆盖了JavaWeb底层的核心链路——请求响应、会话跟踪、数据库增删改查、表单处理、页面渲染。读这个标题背后的资料你要拿到的不只是能跑的源码和数据库文件而是把“浏览器发起请求到数据库返回结果”这条线彻底走通的能力。适合正在做JavaWeb课设、想补Servlet和JSP底层逻辑的初学者也适合想快速复习传统JavaWeb项目打包部署的老手。2. 用IDEA新建jsp项目并跑通Tomcat骨架搭建与版本匹配的四个决策点2.1 为什么JSPServlet这套老技术还值得搭一遍网上大量免费python源码大全、springboot项目满天飞的今天JSPServlet看起来像是上个时代的产物。但你真去面试或做课设答辩时Servlet生命周期、请求转发与重定向、Session原理这些底层问题还是会被反复问。这套在线考试管理系统选JSPServlet最大的价值在于没有框架帮你遮住细节请求怎么进Servlet、Servlet怎么调DAO、结果怎么回写页面每一步都看得见。理解了这些后面再接触SpringMVC时你一眼就能看出DispatcherServlet就是把原本手写的转发逻辑统一收口了。所以不要觉得这是“老技术没用”它是最便宜的底层补课材料。2.2 在IDEA里新建一个JSP项目的标准动作用IDEA新建jsp项目常见做法是直接建一个Java Enterprise工程或者先建普通Java项目再手动添加Web支持。我一般选后者步骤少且可控。具体操作是这样# 假设你已经用IDEA创建了一个普通Java项目接下来在项目结构里加Web支持 # 1. 右键项目 - Add Framework Support - 勾选 Web Application # 2. 确认生成的 web/WEB-INF/web.xml 存在这是JavaWeb应用的入口描述文件 # 3. 在 src 下新建包结构例如 com.exam.servlet / com.exam.dao / com.exam.entity建完Web支持后还需要配置Tomcat才能跑起来。打开Run/Debug Configurations新增Tomcat Server - Local在Server标签页里把Application Server指向你本地的Tomcat安装目录。Deployment标签页里点加号选择Artifact一般会出现一个带“:war exploded”的选项选它。这个“exploded”模式表示解压目录开发时修改JSP或类文件不用重新打包浏览器刷新就能生效。?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-nameOnlineExamSystem/display-name welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app这段web.xml声明了应用名称和欢迎页。实际项目里Servlet的注册你可以选择在web.xml里用servlet和servlet-mapping配对写也可以用WebServlet(/login)注解直接标在Servlet类上。两种方式都常见课设源码里往往混用容易造成排查困难。我建议新写的代码统一用注解方式少一层映射文件要维护。2.3 Tomcat、JDK和Servlet API三者的版本匹配这是新手第一个容易翻车的地方。Tomcat版本直接决定你能用哪个版本的Servlet API。Tomcat 9对应Servlet 4.0规范需要JDK 8及以上Tomcat 10对应Servlet 5.0但包名从javax.servlet变成了jakarta.servlet。这意味着你从网上下载的大多数JSPServlet课设源码很多是基于Tomcat 8或9写的强行丢进Tomcat 10会直接编译报错提示找不到javax.servlet包。解决办法很简单本地老老实实用Tomcat 9.0.x配JDK 8或JDK 11。这个组合兼容性最好能跑通绝大多数传统jsp项目。如果你已经装了Tomcat 10参考下面的做法# 在pom.xml里显式引入Servlet API编译期兜底 # 注意scope用providedTomcat运行时自己会提供实现避免重复 dependency groupIdjakarta.servlet/groupId artifactIdjakarta.servlet-api/artifactId version5.0.0/version scopeprovided/scope /dependency如果你新建的是空Java项目没用Maven那就不需要这个依赖直接靠IDEA的Web Facet和Tomcat运行容器提供编译支持。2.4 传统JSP项目打包war构建输出与依赖目录的约定开发时用war exploded跑得很开心但最后交付课设或部署上线时需要传统jsp项目打包war。这里有个关键约定IDEA的Build Artifacts里如果项目里有本地lib目录存放jar包一定要在Output Layout里把lib目录加进去并勾选“Include in project build”。否则打包出来的war里只有class文件和JSP运行时会报ClassNotFoundException。正确配置后war包结构是标准的exam.war ├── index.jsp ├── WEB-INF │ ├── web.xml │ ├── lib │ │ ├── mysql-connector-java-8.0.33.jar │ │ └── jstl-1.2.jar │ └── classes │ └── com/exam/servlet/LoginServlet.class打包时注意Project Structure - Artifacts里如果同时存在exam:war和exam:war exploded两个条目点Build - Build Artifacts时选择exam:war的Build动作。打出来的war包放在项目根目录的out/artifacts/exam_war/下把这个war文件丢进Tomcat的webapps目录启动Tomcat即可自动解压部署。这一步很多人栽在“代码能跑但打不出能部署的包”本质就是Artifact里漏了lib目录导致war里缺少数据库驱动。3. 在线考试管理系统的数据库设计建表脚本、初始化数据与MySQL连接池配置3.1 标题里“70”的含义与系统表结构怎么拆标题里的“70”如果是指工程编号或作业序号那对应的源码包大概率包含完整的SQL脚本和初始化数据。如果把“70”理解为表数量说明设计者把表拆得很细。实际做课设的在线考试系统表规模通常在10到20张之间就能覆盖全部功能。核心表就六张学生表、教师表或管理员表、试卷表、试题表、考试记录表、答题明细表。再加上扩展用的课程表、班级表、成绩汇总表已经足够支撑“管理员出卷、学生答题、系统判分”的完整闭环。3.2 试卷与试题的两种组织方式固定试卷和随机抽题这个设计决策直接影响建表脚本。固定试卷的意思是试卷表里直接存题目ID集合学生打开试卷看到的题目顺序和内容都一样。随机抽题则需要在试卷配置里定义规则比如“从章节一抽5道单选题从章节二抽3道多选题”学生每次进入试卷生成的题目不同。课设级别一般选固定试卷逻辑简单判分也直观。如果你想做得有区分度可以在试卷表里加一个exam_type字段0表示固定卷1表示随机卷随机卷的题目在进入考试时动态生成并写入考试记录表。这个字段一点架构就从“死卷”变成了“活卷”答辩时能多讲三分钟。3.3 MySQL建表脚本与初始化数据从ER图到可直接执行的SQL下面给出最小可运行的核心表结构覆盖用户、试卷、试题、考试记录四个主体。注意字符集统一用utf8mb4否则存中文容易变问号。-- 用户表学生和管理员共用一张表用role字段区分 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码课设可用MD5生产要加盐, real_name VARCHAR(50) COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 0学生 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_role (role) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 试卷表 CREATE TABLE exam_paper ( id INT PRIMARY KEY AUTO_INCREMENT, paper_name VARCHAR(100) NOT NULL COMMENT 试卷名称, total_score INT NOT NULL DEFAULT 100 COMMENT 总分, duration_minutes INT NOT NULL DEFAULT 60 COMMENT 考试时长(分钟), exam_type TINYINT NOT NULL DEFAULT 0 COMMENT 0固定卷 1随机卷, create_by INT COMMENT 创建人ID关联sys_user, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试卷表; -- 试题表 CREATE TABLE exam_question ( id INT PRIMARY KEY AUTO_INCREMENT, paper_id INT NOT NULL COMMENT 所属试卷ID, question_type TINYINT NOT NULL COMMENT 1单选 2多选 3判断, content TEXT NOT NULL COMMENT 题干, option_a VARCHAR(255), option_b VARCHAR(255), option_c VARCHAR(255), option_d VARCHAR(255), answer VARCHAR(10) NOT NULL COMMENT 正确答案多选用逗号分隔, score INT NOT NULL DEFAULT 5 COMMENT 单题分值, FOREIGN KEY (paper_id) REFERENCES exam_paper(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT试题表; -- 考试记录表学生每次答卷生成一条记录 CREATE TABLE exam_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 学生ID, paper_id INT NOT NULL, score DECIMAL(5,1) NOT NULL DEFAULT 0 COMMENT 判分后的成绩, status TINYINT NOT NULL DEFAULT 0 COMMENT 0考试中 1已交卷, start_time DATETIME, submit_time DATETIME, UNIQUE KEY uk_user_paper (user_id, paper_id) COMMENT 防止同一学生重复进同一张卷, FOREIGN KEY (user_id) REFERENCES sys_user(id), FOREIGN KEY (paper_id) REFERENCES exam_paper(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考试记录表;这段SQL的要点在于uk_user_paper这个唯一键。它的作用是防止学生反复进入同一张固定试卷刷成绩这也是在线考试系统区别于普通增删改查的关键点。初始化数据时先插入管理员账号admin/123456再插入几道试题目让项目一启动就能看到效果。INSERT INTO sys_user (username, password, real_name, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 管理员, 1); -- 上面的密码是123456的MD5值课设演示足够 INSERT INTO exam_paper (paper_name, total_score, duration_minutes, exam_type, create_by) VALUES (Java基础测试卷, 100, 60, 0, 1); INSERT INTO exam_question (paper_id, question_type, content, option_a, option_b, option_c, option_d, answer, score) VALUES (1, 1, Servlet的生命周期方法中以下哪个方法只被调用一次, init(), service(), doGet(), destroy(), A, 10), (1, 3, JSP页面本质上是一个Servlet。, NULL, NULL, NULL, NULL, 对, 10);这段SQL就是典型的“可抄作业”内容。不管你从网上下载的在线考试管理系统源码里表结构是什么样这套核心表都能作为你改造或手写建表脚本的底稿。字段注释写清楚课设文档也能直接复用。3.4 MySQL数据库连接池的选择从DriverManager到HikariCP课设级别的源码里最常见的是每个DAO方法里写一遍Class.forName(com.mysql.cj.jdbc.Driver)然后DriverManager.getConnection(url, user, password)。这种写法能跑但性能和资源管理很差。mysql的数据库连接池主流方案在JavaWeb传统项目里是Druid或HikariCP。Druid在国内课设中使用率高因为它自带监控页面答辩时打开监控页展示SQL执行情况效果很加分。配置如下// Druid连接池工具类项目启动时初始化一次 public class DbUtil { private static DruidDataSource dataSource; static { // 注意MySQL 8的driverClass和8.0以下不一样 // com.mysql.jdbc.Driver 是MySQL 5.x用的8.x必须用 com.mysql.cj.jdbc.Driver dataSource new DruidDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/exam_db?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(你的密码); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMinIdle(5); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这里三个参数值得说明。serverTimezoneAsia/Shanghai是因为MySQL 8的驱动要求显式指定时区不写会报Server returns invalid timezone错误。useSSLfalse是本地开发环境关掉SSL握手减少连接耗时。initialSize和maxActive是连接池的初始连接数和最大活跃连接数一个只有几十人同时在线的课设项目配5/20完全够用。如果你下载的源码包没有lib目录里的Druid jar记得在项目的WEB-INF/lib下补上druid-1.2.x.jar和mysql-connector-java-8.0.x.jar两个文件。否则运行时ClassNotFoundException是跑不掉的。4. 从JSP到Servlet再到DAO核心业务链路实现与关键参数约定4.1 Servlet生命周期和请求分发三个方法一个都不能搞混Servlet生命周期是面试高频题也是在线考试系统里控制器设计的理论基础。init()在Servlet首次被访问时执行一次负责初始化资源。service()每次请求都会执行根据HTTP方法分发到doGet或doPost。destroy()在应用卸载时执行一次释放资源。写在线考试系统的登录功能时我一般把表单提交用doPost处理把页面跳转用doGet处理。这样做的原因是doGet会把参数拼在URL上不做登录校验时容易被浏览器历史记录和日志暴露内容。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 先统一设置请求编码解决中文乱码 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 调用DAO层验证账号密码 UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if (user null) { // 失败回登录页并携带一个错误提示参数 request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } else { // 成功把用户信息存入session HttpSession session request.getSession(); session.setAttribute(loginUser, user); // 管理员跳后台学生跳考试列表 if (user.getRole() 1) { response.sendRedirect(request.getContextPath() /admin/paperList.jsp); } else { response.sendRedirect(request.getContextPath() /student/examList.jsp); } } } }这段代码里有个容易被忽略的细节forward和sendRedirect的区别。登录失败用forward因为要携带request域里的错误信息回登录页信息还在同一个请求内。登录成功后用sendRedirect因为要避免表单重复提交——如果成功也用forward用户刷新页面就会再次提交登录请求虽然不影响结果但会在浏览器警告框里提示“确认重新提交表单”。这个细节在答辩时被问到的概率极高。4.2 登录拦截与会话管理Filter过滤器的标准写法在线考试系统里学生没登录直接访问考试页面是不允许的。课设源码里常见的做法是每个JSP页面顶部写一段Java代码判断session是否为空这种写法虽然有用但代码重复。更规范的方案是用Filter统一拦截。注意Servlet的Filter在传统的javax.servlet包下Tomcat 9之前都这样。WebFilter(urlPatterns {/student/*, /admin/*}) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; HttpSession session request.getSession(false); User user (session null) ? null : (User) session.getAttribute(loginUser); if (user null) { // 没有登录丢回登录页 response.sendRedirect(request.getContextPath() /login.jsp); return; } // 管理员访问学生页面要用role做二次校验 if (request.getRequestURI().contains(/admin/) user.getRole() ! 1) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权限访问); return; } chain.doFilter(req, res); } }这里有两个参数值得注意。request.getSession(false)里的false表示“如果当前没有session就返回null而不是新建一个session”。很多新手用getSession()不传参会导致未登录用户也拿到一个空session虽然不致命但浪费内存。另一个是sendRedirect里request.getContextPath()的作用它会把项目部署名带上比如/exam避免重定向时丢失应用前缀。如果你不写这个前缀部署名一旦从Root改成exam所有重定向路径全部失效。4.3 在线考试核心流程加载试卷、提交答题、自动判分这是系统里最核心的业务链路。学生点击“开始考试”后Servlet加载试卷和题目渲染到JSP页面学生提交答案后Servlet逐题比对答案计算总分。WebServlet(/exam/submit) public class ExamSubmitServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); HttpSession session request.getSession(false); User user (User) session.getAttribute(loginUser); int paperId Integer.parseInt(request.getParameter(paperId)); // 所有题目的ID和用户答案分别提交用约定好的命名格式 String[] questionIds request.getParameterValues(questionId); String[] answers request.getParameterValues(answer); ExamDao examDao new ExamDao(); // 查询正确答案列表 MapInteger, String correctMap examDao.listCorrectAnswers(paperId); // 逐题判分 double totalScore 0; int correctCount 0; for (int i 0; i questionIds.length; i) { int qid Integer.parseInt(questionIds[i]); String userAnswer answers[i].trim(); if (userAnswer.equalsIgnoreCase(correctMap.get(qid))) { // 答对加上该题分数 totalScore examDao.getQuestionScore(qid); correctCount; } } // 写入考试记录状态置为已交卷 examDao.saveExamRecord(user.getId(), paperId, totalScore); // 跳转到成绩页面把成绩放在request域里 request.setAttribute(score, totalScore); request.setAttribute(correctCount, correctCount); request.getRequestDispatcher(/student/result.jsp).forward(request, response); } }这段代码里判分逻辑用的是全等比对适合单选和判断。如果有多选题答案顺序可能不同比如正确答案是“A,C”用户提交“C,A”直接比对就会判错。处理方式是对字符串排序后再比对或者把多选题答案按固定顺序存储。课设里为了省事通常要求用户按选项顺序作答这样判分逻辑就保持简单。另一个关键点是saveExamRecord里要处理唯一键冲突同一学生重复提交同张试卷时数据库会报Duplicate entry异常所以提交前要先查询exam_record表里是否已有记录有则做更新而不是插入。4.4 JSP页面与Java代码分离JSTL和EL表达式的配合原始JSP页面里写满% %脚本片段的代码能跑但极难维护。更合理的写法是用EL表达式取后端数据用JSTL标签做循环和判断。在线考试系统的考试列表页就是一个典型场景。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body h2考试列表/h2 table border1 tr th试卷名称/th th总分/th th时长(分钟)/th th操作/th /tr c:forEach items${paperList} varpaper tr td${paper.paperName}/td td${paper.totalScore}/td td${paper.durationMinutes}/td tda href${pageContext.request.contextPath}/exam/start?paperId${paper.id}开始考试/a/td /tr /c:forEach /table /body /html使用JSTL的前提是WEB-INF/lib下有jstl-1.2.jar并且web.xml声明的是Servlet 2.4以上版本。这里最常见的坑是JSP页面报错“The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved”——原因是用了高版本的jakarta.servlet标签包或者JSTL版本与Servlet版本不匹配。Tomcat 9 JDK 8 JSTL 1.2这个组合最稳定。另外注意${pageContext.request.contextPath}这个写法它等效于前面Java代码里的request.getContextPath()用于拼接带应用前缀的URL。如果你图省事写死路径/exam/start一旦部署名变化所有超链全部404。5. 在线考试管理系统常见的翻车现场避坑与排查5.1 现象Tomcat能启动但访问Servlet报404这个问题在JSPServlet课设里出现频率最高。明明Java类路径、注解都写了浏览器访问http://localhost:8080/exam/login却404。原因之一是WebServlet注解没被扫描到——IDEA的Web Artifact没有把编译后的class目录输出到WEB-INF/classes。打开Project Structure - Artifacts看Output Layout里有没有WEB-INF/classes这一项没有就手动加把Module Output添加进去。原因之二是注解路径写错servlet映射不区分大小写路径匹配但必须和浏览器地址保持一致的上下文。检查方式是启动项目后看Tomcat的控制台日志日志里会打印“Initializing Servlet”之类的字样没打印就是Servlet没加载。5.2 现象登录页输入中文用户名保存到数据库变成“???”这是JSP页面、Servlet请求、数据库表三级编码不一致导致的。解决要三处同时设。页面顶部必须有% page contentTypetext/html;charsetUTF-8 %Servlet里的doGet和doPost开头要写request.setCharacterEncoding(UTF-8)数据库连接URL里带characterEncodingutf8参数。注意顺序Tomcat 8及以上版本对POST请求的编码默认不是UTF-8只靠页面声明不够。另外GET请求的参数编码不归request.setCharacterEncoding管需要改Tomcat的server.xml里Connector加URIEncodingUTF-8。课设检查时优先看数据库表字符集如果建表时用了DEFAULT CHARSETlatin1JSP和Servlet全改对也没用必须ALTER TABLE改成utf8mb4。5.3 现象数据库连接失败控制台报ClassNotFoundException或Communications link failure报ClassNotFoundException十有八九是MySQL驱动jar没放进WEB-INF/lib目录。注意不是放到项目模块的classpath里就算数war打包时以WEB-INF/lib为准。报Communications link failure则分两种情况MySQL服务没启动或连接URL的端口不对。MySQL默认3306如果你本地跑了多个实例改过端口记得同步改URL。另外MySQL 8的驱动类名必须用com.mysql.cj.jdbc.Driver用旧的com.mysql.jdbc.Driver会提示Loading class com.mysql.jdbc.Driver is deprecated虽然MySQL 5.7驱动还能兼容但8.x版本里这个旧类已经移除。5.4 现象JSP页面能打开但渲染时报500错误提示EL表达式无法解析这个坑多出现在JSTL标签库版本搭配上。常见报错是org.apache.jasper.el.JasperELException: The identifier [paper] is not a valid Java identifier。原因有两个可能一是Servlet里setAttribute时把key写成了带特殊字符的字符串比如request.setAttribute(paper list, list)EL表达式${paper list}无法解析改成${paperList}就好二是JSTL版本和Servlet版本冲突Tomcat 10用jakarta.servlet作为包名后老JSTL的uri不对换成jakarta.tags.core标签前缀也不能用.jar里的旧标签库。解决方式就一个Tomcat 9配jstl-1.2.jar什么都别换这是传统JSP项目里最稳的组合。5.5 现象用户刷新交卷页面成绩被重复计算这是典型的表单重复提交问题。在线考试系统里如果判分和保存是同一个请求完成的刷新或浏览器回退就会触发二次判分成绩记录出现两条或覆盖正确成绩。两层防线第一层是前端交卷按钮点击后置灰并禁用配合JavaScript阻止重复提交第二层是后端在ExamSubmitServlet里先查询exam_record表是否已有该生该卷的记录有则直接跳转成绩页不再执行判分。后端校验才是关键前端禁用只是体验优化。用唯一键uk_user_paper(paper_id, user_id)配合数据库层兜底这是最后一道防线能扛住并发重复提交。6. 部署、验证与平滑过渡把JSPServlet项目用到位6.1 war包部署到Tomcat的两种方式及场景选择开发完成后传统jsp项目打包war部署方式就两种。第一种是把war包扔进Tomcat的webapps目录启动Tomcat自动解压部署。这种方式适合交付课设、在局域网演示。注意Tomcat默认会解压war到同名目录你改代码后重新打war包要先在Tomcat里停掉应用并删除旧的解压目录否则会出现“代码改了但页面没变化”的幻觉。第二种是在Tomcat的conf/server.xml里配置Context docBase/opt/exam/exam.war path/exam /这种适合war包放在Tomcat外部解耦部署路径。第二种方式对于“源码数据库”类项目资源尤其实用——数据库文件、配置外置、SQL脚本都放到独立目录下一次重建环境时不用动Tomcat。6.2 生产级部署数据库配置外置与JNDI数据源切换课设级项目把数据库账号密码写死在DbUtil.java里可以理解。真正部署时这等于把钥匙挂在门口。常见做法是把数据库配置挪到web.xml或外部properties文件。一个轻量做法是让DbUtil读取classpath下的db.properties文件部署时只替换这个文件不动代码。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/exam_db?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password这里改成你的密码 jdbc.maxActive20对应的DbUtil读取逻辑改成加载properties文件替换硬编码。这样数据库密码、连接参数都集中在配置层后续迁移服务器时不用改一行Java代码。Druid连接池本身支持用Properties对象初始化直接配置文件驱动即可。6.3 Nginx能不能配合JSP项目静态资源和动态请求的分工很多同学问nginx支持jsp吗准确地说Nginx不解析JSP它只负责静态资源响应和反向代理。JSP的解析执行必须在Tomcat里完成。部署时常见的分工方式是Nginx监听80端口把.html/.css/.js/.png这类静态资源直接返回把所有/exam/开头的动态请求转发给后面的Tomcat端口。这个架构的优势是静态响应快且Nginx的代理能力可以为后续加HTTPS打基础。Nginx配置不需要写Java但处理动态路径时要注意配置proxy_pass的路径拼接方式——如果proxy_pass http://127.0.0.1:8080;不带URI请求/exam/login会原样转发到Tomcat如果写成proxy_pass http://127.0.0.1:8080/;路径可能被重写导致404。6.4 验证这个方案值不值得做部署检查清单与一次性能验证部署完成后别急着收工按清单过一遍第一用未登录浏览器直接访问/student/examList.jsp确认被Filter踢回登录页第二用一个测试账号完成一次完整考试确认成绩写入后再次点击开始考试确认被唯一键拦下第三查看Druid监控页面或Tomcat日志确认SQL执行没有全表扫描——如果题目表几百条数据量还出现慢SQL检查查询条件是否用了索引。课程设计答辩时评委最常问的两个问题就是“Session失效怎么处理”和“并发交卷会怎样”这两个场景按上面的代码逻辑回答基本能站住。最后一件事值得做用JMeter模拟20个用户同时交卷看Tomcat默认线程池是否扛得住。如果响应时间超过3秒先看数据库连接池是否配了Druid再看代码里有没有在循环里反复getConnection()——把获取连接提到循环外能省掉大部分开销。我自己踩过的坑是DAO层每个方法都打开和关闭连接20个并发就把连接池打满了改成方法级复用和事务边界的正确划分后同一份代码扛住了100并发。这种受挫—排查—优化—验证的循环恰恰是JSPServlet这套老技术最值得你动手走一遍的理由希望帮到你。本文还有配套的精品资源点击获取