☰
基于JSP+Servlet的在线考试管理系统设计与部署实践
2026/10/1 12:22:04 网站建设 项目流程

简介:这套基于JSP与Servlet的在线考试管理系统,面向计算机专业毕业设计和Java Web课程设计,适合有一定Java基础的开发者学习。系统包含学生端和管理员端:学生可以随机或指定试卷进行在线答题,答题过程中显示倒计时与进度,交卷后自动比对答案,红色标记错题、绿色显示正确答案,便于复习;管理员可完成试题的增删改查、试卷发布、用户与角色管理以及权限分配,覆盖从题库维护到成绩查看的完整考试流程。资源包共104个文件,主体为43个Java源文件和22个JSP页面,另含12个JAR依赖库以及CSS、JS、SQL等配置与脚本资源,压缩后约6.14MB,数据库脚本可快速初始化环境。目前已有43人学习下载。整套资料包含完整可运行源码和数据库,能帮助读者理清Servlet与JDBC的交互、MVC分层和后台管理页面的实现思路,可作为毕业设计蓝本,也可在此基础上进行二次开发与功能扩展。

1. 基于jsp+servlet的在线考试管理系统:毕业设计选题的完整落地样本

如果你正在为毕业设计选题发愁,或者想快速理解jsp+servlet这种传统Java Web项目是怎么组织起来的,这套在线考试管理系统是一个很好的参考样本。它不是一个只有登录注册的空壳,而是把“学生在线答题、交卷后查看错题、管理员维护题目和试卷、角色权限分配”这一整条链路做完了,前端用jsp+jquery+bootstrap,后端就是servlet+jdbc,没有Spring那套复杂的东西,逻辑全部暴露在明面上,很适合用来做课程设计或毕业设计的基础框架。你需要关注的核心问题是:这套代码能不能跑通、数据库脚本怎么导入、Tomcat部署有哪些坑、往上加功能容不容易。

2. 技术栈梳理:jsp+servlet+jdbc 的选型逻辑与三层架构边界

2.1 为什么这种“过时”组合反而适合做毕设

现在很多课程已经不讲jsp了,但打开各大毕设选题库,基于jsp的毕设选题依然占相当比例。原因是它的技术边界清晰:jsp负责页面展示,servlet负责接收请求和跳转,jdbc负责数据库操作,三层之间没有Spring那种自动装配的黑匣子,每一个请求从浏览器发出到数据库返回结果的过程都可以在代码里跟踪到。对于答辩来说,这种项目反而好讲清楚。

这套系统的请求流转是典型的jsp+servlet结构:浏览器访问jsp页面,页面里的表单或超链接提交到servlet,servlet里用jdbc操作MySQL数据库,拿到结果后通过request.setAttribute传回jsp,再forward或redirect到目标页面。所有的Java代码都集中在src目录下的servlet类和dao类里,jsp目录下则按学生端和管理员端做了划分。

2.2 数据库设计:登录、试卷、题库、答题记录的角色划分

从功能反推表结构,这套系统至少需要五张表:用户表用于学生和管理员的登录验证,角色表用于区分权限,试题表用于存放题干、选项和标准答案,试卷表用于把多道题目组织成一套卷,答题记录表用于保存学生的提交内容和判错结果。角色和用户之间通过角色id关联,试卷和试题之间通过试卷id关联。

建表时有个细节值得留意:试题表除了题干和四个选项,还要有一个字段标记标准答案,同时有一个锁定状态字段,锁定后题目不可用。管理员端项目里已经内置了关键词搜索功能,所以在表设计时要考虑后续的模糊查询需求,题目标题和试卷名称字段建议建普通索引。MySQL字符集统一使用utf8mb4,不然中文选项存进去后查询出来乱码的概率极大。

数据库脚本一般会附带在源码包的doc目录或sql目录下,导入时直接source执行即可。如果你拿到的版本里没有初始化数据,那就需要手动构造一套演示数据:三五个学生账号、一个管理员账号、两三套试卷、每套试卷十到十五道题,这样演示时才不会显得空。

2.3 JDBC连接的常见写法和连接失败的真正原因

这套系统的数据访问层用的是原生JDBC,常见做法是写一个DBUtil类,里面用静态代码块加载驱动,再提供一个获取Connection的方法。很多新手抄代码时只搬了Class.forName那三行,却忽略了mysql驱动jar包是否放进了WEB-INF/lib目录,这是连不上数据库的头号原因。

public class DBUtil { private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/exam_db?useUnicode=true&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } 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) {} } } }

逻辑说明:静态代码块只执行一次,保证驱动只注册一次;getConnection每次调用都创建一个新连接,虽然效率不高,但对于课程设计这种低并发场景完全够用。close方法按照先ResultSet再PreparedStatement最后Connection的顺序关闭,避免资源泄漏。

参数说明:URL里的useUnicode和characterEncoding是中文不乱码的关键,characterEncoding指定为utf8后,数据库连接层面就不会出现简体中文显示成问号的问题。USER和PASSWORD根据你自己的MySQL配置修改,注意MySQL 5.7默认的root密码和你本地可能不一致,第一件事就是核对这两项。这个类的包名一般是com.xxx.util,你再把具体包名替换到import里就行。

这里有一个很容易翻车的地方:MySQL 8.x的驱动类名已经变成了com.mysql.cj.jdbc.Driver,URL还得加上serverTimezone参数。这套项目环境是MySQL 5.7和JDK 1.8,如果你本机装的是MySQL 8,直接用原脚本大概率会报连接失败。我一般会换成JDBC驱动的兼容写法,见上方代码注释。

2.4 servlet生命周期在答题场景中的体现

servlet在项目里承担的是控制器角色。你可能被问到“servlet生命周期是什么”,答案是init、service、destroy三个阶段。在答题场景中,init在Tomcat启动后第一次访问servlet时执行,用于初始化一些全局数据;service每次请求都会执行,用于分发get和post请求。

@WebServlet("/exam") public class ExamServlet extends HttpServlet { @Override public void init() throws ServletException { // 初始化试卷列表,可以加载到application作用域,避免每次查询 System.out.println("ExamServlet初始化:加载试卷列表"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); if ("list".equals(action)) { request.getRequestDispatcher("/student/examList.jsp").forward(request, response); } else if ("start".equals(action)) { // 根据试卷id加载试题,存到session里 } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }

逻辑说明:doGet和doPost都走同一个分发方法,是为了前端提交方式不一致时不会出现405错误。action参数是这些老项目的通用做法,相当于一个简易的路由。init阶段在当前项目里被用来做初始化加载,你可以改成从数据库加载试卷列表到application范围,减少后续访问数据库的次数。

参数说明:@WebServlet注解是Servlet 3.0以后支持的写法,Tomcat 7.0.73完全兼容,不需要再在web.xml里配置servlet映射。如果你的Tomcat版本低于7,就只能在web.xml里手动配置了。这里的url-pattern是/exam,所以页面里提交或跳转的路径必须是contextPath + /exam + 参数,经常有人在jsp里少写了contextPath,导致404。

提示:仔细检查jsp里链接的写法。老项目里最常见的问题是<c:url>或者request.getContextPath()没写全,浏览器地址栏直接变成localhost:8080/exam?action=list而不是localhost:8080/项目名/exam?action=list,Tomcat会给你一个404。

3. 学生端在线答题+错题回顾:核心链路的实现拆解

3.1 在线答题的会话控制:倒计时、进度条和实时作答记录

在线答题是这套系统最核心的学生端功能。从交互上看,学生从试题列表选择一套试卷,点击开始答题,页面右上角出现倒计时和答题进度,每答完一题进度实时刷新,全部答完点击最下方的交卷按钮提交。这里的实现难点其实是两个问题:每道题之间的跳转是刷新页面还是局部刷新,倒计时是前端递减还是依赖服务器时间。

从这套系统的技术栈看,它采用的是jQuery局部刷新方案。答题页面加载第一题,点击“下一题”时通过jQuery的ajax请求把当前题的作答选项传到servlet,servlet返回下一题的题干和选项,页面用jQuery更新题干区域和选项区域。进度条则是根据当前题目序号和总题数计算百分比,用jQuery设置bootstrap进度条的width属性。

// 答题卡上方进度条更新 function updateProgress(current, total) { var percent = Math.round(current / total * 100); $('.progress-bar').css('width', percent + '%').text(percent + '%'); } // 点击下一题时发送当前选项并获取下一题 function nextQuestion(examId, paperId, currentIndex) { var selected = $('input[name="answer"]:checked').val(); if (!selected) { alert('请先选择答案'); return false; } $.ajax({ url: contextPath + '/exam?action=nextQuestion', type: 'POST', dataType: 'json', data: { examId: examId, paperId: paperId, currentIndex: currentIndex, answer: selected }, success: function(res) { if (res.hasNext) { // 更新题干、选项和进度条 $('#questionTitle').text(res.title); $('#optionA').text(res.optionA); $('#optionB').text(res.optionB); $('#optionC').text(res.optionC); $('#optionD').text(res.optionD); $('#currentIndex').val(res.currentIndex); updateProgress(res.currentIndex, res.total); $('input[name="answer"]').prop('checked', false); } else { // 已经是最后一题,提示交卷 $('#submitBtn').show(); } } }); }

逻辑说明:把当前选择的答案随请求带到servlet,servlet连同学生的作答一起存入答题记录表,这样即使答题中途浏览器崩溃也不会丢数据。success回调里根据返回的hasNext字段决定是更新题目还是显示交卷按钮,更新时使用了jQuery的prop方法重置radio选中状态,避免上一题的选中痕迹残留。

参数说明:contextPath是你在jsp页面顶部定义的全局变量,等于request.getContextPath(),ajax请求必须带这个前缀,否则路径会错。dataType使用json,因此服务端要通过Gson或fastjson把结果序列化后再写出。数据类参数里examId代表一次答题任务的id,paperId代表试卷id,currentIndex表示当前题号,初次加载时默认为0。发请求时建议不要传明文答案,否则抓包就能看到正确答案,更好的做法是只传选项下标,在service层再做比对。

3.2 交卷判分与错题标记的数据库操作

交卷逻辑比常见的一提交就显示总分要复杂一点。这套系统的实现是用户点击交卷后,servlet把所有已保存的作答记录取出来,与试题表里的标准答案逐题比对,把结果更新到答题记录表,然后跳转到结果页。结果页里错题用红色标记,正确答案用绿色标记,方便复习。

// 判分核心逻辑:逐题比对并生成错题列表 List<AnswerRecord> records = answerDao.findRecordsByExamId(examId); for (AnswerRecord record : records) { Question q = questionDao.findById(record.getQuestionId()); boolean isCorrect = (record.getUserAnswer() != null && record.getUserAnswer().equals(q.getCorrectAnswer())); record.setCorrect(isCorrect); if (!isCorrect) { // 收集错题信息,用于结果页展示 wrongList.add(record); } answerDao.updateCorrectStatus(record.getId(), isCorrect); } request.setAttribute("wrongList", wrongList); request.setAttribute("totalCount", records.size()); request.getRequestDispatcher("/student/result.jsp").forward(request, response);

逻辑说明:先按examId查出所有作答记录,id说明一次答题任务产生一批记录。然后循环里每次查询试题表,用当前记录的userAnswer和标准答案的correctAnswer做equals比较。比较结果更新回答题记录表,这样错题状态是持久化保存的,学生退出后重新登录还能看到错题历史。wrongList只收集错误记录,最终传给结果页面渲染。

参数说明:examId在开始答题时就生成,是一个自增id,session里保存的关联信息也需要对应。userAnswer存的是选项文本,比如“A”或“B”,而correctAnswer也是同样的格式,这要求建表时两者的字段类型和长度保持一致,否则取出时带空格或大小写不一致会导致比对失败。常见坑是数据库中题目选项带了HTML标签,答案文本比对时出现前后空格,这属于脏数据问题。

3.3 倒计时的前端实现和服务端兜底

倒计时这块,新手容易直接写一个JavaScript的setInterval,每秒递减。但这里有一个实际问题:如果学生切到别的页面待了十分钟再回来,前端计时器会被浏览器节流甚至完全暂停。真正能兜底的做法是在开始答题时记录服务端时间,前端倒计时只做展示,交卷时服务端再校验是否超时。

// 倒计时初始化,从服务端下发的剩余秒数开始 var remainSeconds = parseInt($('#remainSeconds').val(), 10); var timer = setInterval(function() { remainSeconds--; if (remainSeconds <= 0) { clearInterval(timer); alert('答题时间到,系统自动交卷'); $('#examForm').submit(); } else { var minutes = Math.floor(remainSeconds / 60); var seconds = remainSeconds % 60; $('#timeLabel').text(minutes + '分' + (seconds < 10 ? '0' : '') + seconds + '秒'); } }, 1000);

逻辑说明:remainSeconds从服务端下发的隐藏域读取,初始值是试卷的时长限制。每秒减一,刷新页面时重新获取剩余时间。到零时直接提交表单,触发后端的自动交卷逻辑,避免给恶意用户留出“前端绕过倒计时”的漏洞。

参数说明:隐藏域的value在jsp里用el表达式或者jstl标签写入,来源可以是session中存储的考试开始时间。如果试卷未设置时长限制,默认可以给一个120分钟的标准值,小时级别的倒计时状态恢复不会造成明显延迟。setInterval的返回值要保存到变量里,否则页面跳转前无法清除,会出现多个计时器并发递减的bug。

4. 管理员端题目、试卷、角色三条配置链路

4.1 题目管理的增删改查与数据校验细节

题目管理是管理员端最高频的操作页面,涉及题目的增删改查、按关键词搜索、题干和选项编辑、标准答案设置、锁定状态切换。这里的关键在于:题目表结构里题干用大字段存储,四个选项用四个短文本字段,标准答案是一个单字符字段,锁定状态是一个tinyint字段,0表示可用,1表示锁定。

新增或修改题目时要做的校验逻辑是:题干不能为空,至少两个选项不能为空,标准答案必须是A到D中的一个。这套系统里校验逻辑是放在servlet里用一堆if判断来写的。实际项目中我建议也做前端校验,两个地方都校验,减少无效数据进库的可能。

// 新增或修改题目处理 String title = request.getParameter("title"); String optionA = request.getParameter("optionA"); String optionB = request.getParameter("optionB"); String optionC = request.getParameter("optionC"); String optionD = request.getParameter("optionD"); String correctAnswer = request.getParameter("correctAnswer"); String isLocked = request.getParameter("isLocked"); if (title == null || title.trim().isEmpty()) { request.setAttribute("errorMsg", "题干不能为空"); request.getRequestDispatcher("/admin/questionEdit.jsp").forward(request, response); return; } if (correctAnswer == null || !"ABCD".contains(correctAnswer)) { request.setAttribute("errorMsg", "标准答案必须是A、B、C、D中的一个"); request.getRequestDispatcher("/admin/questionEdit.jsp").forward(request, response); return; } Question q = new Question(); q.setTitle(title.trim()); q.setOptionA(optionA.trim()); q.setOptionB(optionB.trim()); q.setOptionC(optionC.trim()); q.setOptionD(optionD.trim()); q.setCorrectAnswer(correctAnswer); q.setLocked("1".equals(isLocked)); boolean ok = questionDao.insertOrUpdate(q);

逻辑说明:先把页面表单参数全部取出来,逐项做非空和合法性校验,任何一项不满足就直接转发回编辑页面,并带上errorMsg提示。用trim方法去掉用户误输的首尾空格。校验通过后封装成Question对象,调用dao层的insertOrUpdate方法。这里insert和update判断的是questionId参数是否为空,为空走插入,不为空走更新。

参数说明:correctAnswer这里用了字符串contains的判断方式,如果correctAnswer是null,contains方法会返回true,先把null排除,再判断是否属于ABCD集合。isLocked参数来自下拉框或单选按钮,值为1时锁定,否则可用。页面上编辑按钮的href需要携带questionId,查询按钮的表单method必须是get,才能把keyword参数拼到URL里供servlet取用。

4.2 试卷管理的组卷方式:名称传入和题量映射

试卷管理的核心操作是增删改查和关键词搜索,新增一条试卷记录时填入试卷名称和题目数量。这套系统的组卷方式比较轻量,试卷表并不实际存储题目列表,而是通过试题表里的paper_id字段关联。你新建一张试卷后,会自动生成一个试卷id,然后往试卷里添加题目时,把题目的paperId字段更新为该试卷的id即可。

这种设计的问题在于删除试卷时容易产生孤儿数据。如果直接从试卷表删除记录,而试题表里的paperId还指向已经被删除的试卷id,学生端就可能出现试卷查询不到详情的情况。我的处理习惯是:删除试卷前先执行一条update语句,把该paperId下的所有试题的paperId置为null,再删除试卷记录,保持引用完整。

-- 删除试卷前先释放关联题目 UPDATE question SET paper_id = NULL WHERE paper_id = #{paperId}; -- 解除关联后再删除试卷 DELETE FROM paper WHERE id = #{paperId};

逻辑说明:两条SQL必须放在一个事务里执行,否则中途出错会出现一部分题目被置空而试卷仍然存在的脏状态。在dao层的deletePaper方法里,先打开连接,调用setAutoCommit(false),执行完两条update和delete后commit,再在finally块里恢复autoCommit并关闭连接。

参数说明:SQL中的#{}是MyBatis的占位符写法,这个项目是原生JDBC的话,你需要改成PreparedStatement的?占位符,再通过setInt绑定paperId。手动操作MySQL时,setAutoCommit(false)后如果忘了commit,数据不会真正写入,这是事务操作里最容易踩的坑。

4.3 角色管理与权限分配的实现思路

角色管理部分支持角色信息的增删改查和权限分配,这是快速评分点时容易得分的一块。核心表设计是用户表持有roleId,角色表持有权限描述。学生登录后只能看到在线答题和错题回顾两个入口,管理员登录后能看到题目管理、试卷管理、系统功能管理、用户管理和角色管理五个入口。

登录页面根据roleId跳转到不同首页,这是jsp里最常见的分支逻辑:先判断session里loginUser的roleId,等于管理员角色值时跳转到admin/index.jsp,否则跳转到student/index.jsp。有些项目会直接用过滤器来拦截未登录用户访问管理端页面,如果这套系统的源码里没有过滤器,建议你自己补一个LoginFilter,优先级放到最高,避免未登录状态直接访问管理员页面报500。

@WebFilter(urlPatterns = {"/admin/*", "/student/*"}) 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; Object loginUser = req.getSession().getAttribute("loginUser"); if (loginUser == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); } }

逻辑说明:通过注解拦截admin和student两个路径前缀,所有进入这两个目录的请求都会先经过过滤器。如果session里没有loginUser就重定向到登录页,有则放行。这个过滤器能顺手解决两件事:一是未登录用户直接访问管理页面,二是学生访问管理页面被拦截,权限控制在路径层面就完成了一层。

参数说明:/admin/*和/student/*是路径通配,中间注意不要漏掉斜杠花号组合。如果是tomcat7,@WebFilter注解同样受支持。过滤器里放行的代码chain.doFilter必须写在最后,否则过滤器链会提前打断,导致后端servlet无法收到请求。

5. 部署与避坑:IDEA 2021.3 + Tomcat 7 + MySQL 5.7 环境下的常见问题排查

5.1 从导入源码到浏览器访问的完整部署步骤

拿到源码包后不要急着用IDEA打开,先把部署顺序理一遍。第一步,在MySQL中执行源码包里的sql脚本,生成数据库和表结构。第二步,解压源码包,在IDEA里通过File > Open选择项目目录,等待Maven或普通项目结构识别完成。如果没有pom.xml,说明这是一个普通Web项目,要右键点击项目,选择Add Framework Support,勾选Web Application,把Web Resource Directory指定到WebContent或webapp目录。第三步,在Project Structure里设置Artifacts,把项目打成war exploded,再配置Tomcat local,设置Deployment为这个exploded目录。第四步,修改数据库连接配置,启动Tomcat访问登录页。

5.2 踩坑实录:六条真实遇到过的部署问题

问题一:Tomcat启动时报ClassNotFoundException: com.mysql.jdbc.Driver。现象是控制台在启动阶段出现驱动类找不到的错误。原因是MySQL驱动jar包没有放到WEB-INF/lib目录下,IDEA里即使添加了Libraries,部署到Tomcat时也可能不会自动拷贝。解决方法是确认Project Structure > Artifacts > Output Layout里lib下包含了mysql-connector-java的jar,然后重新构建项目。

问题二:jsp页面中文全部变成问号。现象是浏览器里显示题目内容没乱码,但后台录入的中文数据在页面显示为?。原因是数据库表的字符集不是utf8mb4,大概率是当时的建表语句少了DEFAULT CHARSET=utf8mb4。解决方法是对已存在的表执行ALTER TABLE question CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,同时确认jsp页面开头的pageEncoding也设置为utf8。

问题三:登录成功后跳转404。现象是浏览器地址是正确的项目路径,但页面找不到。原因是跳转用的路径没有加contextPath,比如request.getRequestDispatcher("/admin/index.jsp")在根路径部署时没问题,但war包部署时项目名就是上下文根,jsp路径必须拼上contextPath。解决方法是统一使用request.getContextPath()拼接,或者在response.sendRedirect里加上contextPath。

问题四:答题页面刷新后回到第一题。现象是学生答到第十题时按F5刷新,页面回到第一题。原因是session中的当前题号和已选答案只在内存里,刷新动作触发了重新加载试卷列表的请求。解决方法是把当前题号存到session的attribute里,加载题目时先检查session中是否已有记录。

问题五:管理员修改题目后学生端看到的还是旧答案。现象是题目内容改了,但在线答题时显示的还是修改前的题干。原因是题目列表查询时加了浏览器缓存,Tomcat返回304导致实战数据没有重新拉取。解决方法是在jsp页面的response头部加Cache-Control: no-cache,或者查询时给SQL加一个时间戳条件。

问题六:Tomcat 7在JDK 1.8环境下出现内存溢出。现象是频繁部署重启后Tomcat报java.lang.OutOfMemoryError: PermGen space。原因是Tomcat 7的默认永久代大小只有几十MB,多次热部署后classLoader泄漏。解决方法是在Tomcat的catalina.bat或catalina.sh的JAVA_OPTS里增加-XX:MaxPermSize=256m,如果是JDK 8以上还需要确认使用的是Metaspace而不是PermGen。

提示:IDEA里配置Tomcat时,在VM options中写入-XX:PermSize=64m -XX:MaxPermSize=256m能让开发期频繁重启更稳定。另外如果你的机器装的Tomcat 10,它默认是Jakarta EE命名空间,servlet和jsp的包名都变了,这套系统的代码大概率跑不起来,直接用Tomcat 9以下比较稳妥。

5.3 端口占用和热部署失败的规避技巧

端口占用是开发期最常见的问题。8080端口被其他进程占用时,IDEA里部署时会提示Address already in use。多数情况下使用命令netstat -ano | findstr 8080,然后按住PID在任务管理器里结束进程。更推荐的做法是给本机的Tomcat单独设置一个不常用的端口,比如8888,在IDEA的Tomcat Server配置页修改HTTP port,顺便把JMX port也改掉,避免与其他项目的端口冲突。

热部署失败的本质是资源文件没有被同步到目标的classes或webroot目录。IDEA中如果是普通web项目,修改了jsp后需要手动执行Build > Recompile,修改了Java文件后要重新Artifacts,只点浏览器刷新往往不生效。我一般习惯把Tomcat的on frame deactivation设置为Update resources,并把build project automatically打开,虽然会消耗一点CPU,但能省去大量“改完没生效”的排查时间。

6. 进阶改动:把这套源码升级成能拿出手的毕业设计作品

6.1 把JDBC裸连改成Tomcat连接池

原生JDBC每次都要创建和销毁连接,虽然毕设答辩时不一定会被追问性能,但面试官大概率会问“你项目里是怎么管理数据库连接的”。最轻量的修改是使用Tomcat自带的JDBC连接池,在META-INF/context.xml配置一个数据源,在DAO里通过JNDI获取连接,代码改动量很小。

<Context> <Resource name="jdbc/examDB" auth="Container" type="javax.sql.DataSource" maxActive="20" maxIdle="8" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/exam_db?useUnicode=true&amp;characterEncoding=utf8"/> </Context>

逻辑说明:maxActive表示最大活跃连接数,maxIdle控制在空闲时最多保留的连接,maxWait是请求连接时最长等待毫秒数,超出后抛超时异常。driverClassName继续使用MySQL 5.7的这个类名,URL里注意XML的&符号必须改写成&。

使用JNDI获取连接后,DBUtil要调整:把原来DriverManager.getConnection的写法替换成从DataSource中取连接。这种改造方式在答辩里很好解释,你也可以顺带引出“数据库连接池的作用”这个问题,展示你对性能优化有基本认知。

6.2 给项目加一个统一异常的JSP错误页

老项目中jsp报错时经常抛出黄底的异常堆栈,这在实际演示时非常不体面。在web.xml里配一个error-page,把500和404都指向一个美观的错误提示页,能显著提升演示观感。注意这个项目里如果是你自己配置了过滤器,拦截到异常可以记录到日志中,同时提示用户“系统开小差了,请稍后重试”,而不是把SQL异常直接抛给浏览器。

6.3 演示前的检查清单与解说路径

演示毕设前,我建议你按这个顺序过一遍:先用管理员账号录一套10道题左右的试卷,再建五个学生账号,然后用学生账号跑一遍完整的答题流程,确认倒计时、进度条、交卷、错题标记都正常。把项目里自带的示例数据清空,换一套与毕设主题匹配的题目数据,这一步是防止演示时出现与论文题目无关的内容。答辩时从“登录重定向到不同首页”开始讲,把请求经过servlet再到jdbc返回这一条线串联起来,扩展部分讲你改造的连接池和过滤器。

从那以后我每拿到一套jsp+servlet源码,都会先检查它的数据库连接方式和路径处理,不再盲目点击运行按钮。因为毕设项目翻车多半不是功能写不出来,而是部署环节的环境差异没有提前铺平。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询