从零手搓JavaWeb在线考试系统:Servlet+JSP+JDBC完整实战与避坑指南
简介这是一套面向计算机相关专业毕设学生与Java实战学习者的JavaWeb在线考试系统基于Java EE技术栈采用JSP、Tomcat与MySQL构建可直接作为毕业设计或课程设计参考。系统区分普通用户与管理员两类角色普通用户可在线考试、查询成绩、修改密码管理员则负责考生信息、考试成绩、试卷与题目的管理功能覆盖考试业务的主要环节。资源包共148个文件约13.69MB包含23个java源文件、23个class编译文件、21个jsp页面、11个js与11个css前端资源以及27个jar依赖、6个xml配置、1个sql数据库脚本和1个doc说明文档源码、脚本与依赖齐全导入Eclipse后即可调试运行。目前已有1384人学习下载适合需要完整项目结构、数据库脚本与可运行源码来练习JavaWeb开发或完成毕设的同学参考使用。1. 从零手搓 javaWeb 在线考试系统为什么我劝你先别急着写业务代码很多人搜 javaWeb 在线考试系统脑子里想的是「找个完整案例 mysql 跑起来就行」。真动手才发现网上那些 javaweb 项目完整案例要么缺表结构要么 Servlet 版本对不上idea 运行 javaweb 项目配置这一步就能卡掉一半人。我见过太多人 clone 下来改个数据库密码Tomcat 一启动就是 404 或者 ClassNotFound最后不了了之。这个系统的本质不复杂老师出题组卷学生登录答题交卷后自动判分并出成绩。难的不是业务逻辑而是把 JavaWeb 那套东西串成一条能跑通的链路——Servlet 接请求、JSP 渲染页面、JDBC 落库、Session 管登录态。它适合两类人一是刚学完 Servlet/JSP 想找个真实项目练手的二是需要交课程设计但不想抄烂大街代码的。下面我按自己踩过的顺序把选型、建表、核心功能、避坑和进阶验证讲清楚你照着能复现一套能演示、能答辩、能继续扩展的版本。2. 技术选型与工程骨架Servlet 还是 SpringBoot先想清楚2.1 为什么这个标题下我更推荐原生 Servlet JSP搜 javaweb 的人十有八九课程要求就是 Servlet/JSP你上 SpringBoot 反而答辩时被问「Servlet 生命周期讲一下」答不上来。原生方案的好处是链路透明一个请求从web.xml或WebServlet进来到doGet/doPost再到 JDBC每一层你都能看到。坏处是重复代码多但考试系统功能就那么几块重复量可控。技术栈我一般定成这样JDK 8 或 11、Tomcat 8.5/9、MySQL 5.7/8.0、Servlet 3.1、JSP JSTL、JDBC 手写或 DBUtils。别上 MyBatis除非你熟新手配 MyBatis 的 XML 映射又是个坑。前端就用 Bootstrap 5 的 CDN别折腾构建工具JSP 里直接引。提示Tomcat 10 开始包名从javax.servlet变成jakarta.servlet你网上找的 javaweb 项目完整案例大多是javax用 Tomcat 10 会全线报错。要么降 Tomcat 9要么全局改包名新手直接选 Tomcat 9 省事。2.2 IDEA 里跑通一个空壳项目的完整配置idea 运行 javaweb 项目配置是热搜里的高频痛点我把关键步骤拆开。新建项目选 Java Enterprise勾选 Web Application注意 Application Server 要指向你本地的 Tomcat 目录不是 Maven 仓库。如果新建时没这个选项说明你 IDEA 是社区版社区版没有 Java Enterprise得换 Ultimate 或者手动配。手动配的路径是这样File → Project Structure → Modules → 加 Web facet指定web目录和web/WEB-INF/web.xml再到 Artifacts 里加一个 Web Application: Exploded输出目录默认就行。最后 Run → Edit Configurations → 加 Tomcat LocalDeployment 里把刚才的 Artifact 加进去Application context 设成/exam。# 项目目录结构照着建别乱放 exam-system/ ├── src/ │ └── com/exam/ │ ├── dao/ # 数据库访问 │ ├── model/ # 实体类 │ ├── servlet/ # 控制器 │ └── util/ # DBUtil 等工具 ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ # 放 mysql-connector-java.jar │ ├── css/ │ ├── login.jsp │ └── index.jsp └── exam-system.iml这段结构说明几点WEB-INF/lib下的 jar 是运行时依赖IDEA 里还要在 Project Structure → Libraries 里再引一次否则编译期就报找不到com.mysql.jdbc.Driver。web.xml在 Servlet 3.0 以后可以用注解替代但保留它配欢迎页和错误页更省心。启动后访问http://localhost:8080/exam/login.jsp能看到页面就说明骨架通了再往下写业务。3. 数据库设计与 JDBC 落地五张表撑起整个考试流程3.1 表结构怎么定字段别拍脑袋考试系统的数据模型核心是「谁、考哪张卷、答了什么、得了多少分」。我一般用五张表user用户、question题库、paper试卷、paper_question试卷题目关联、score成绩记录。别把题目直接塞进 paper 表用逗号拼 ID后期改一道题你会想哭。CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, -- 存 MD5别存明文 role TINYINT DEFAULT 0 -- 0 学生 1 老师 ); CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT, content TEXT NOT NULL, option_a VARCHAR(255), option_b VARCHAR(255), option_c VARCHAR(255), option_d VARCHAR(255), answer CHAR(1) NOT NULL, -- A/B/C/D type TINYINT DEFAULT 1 -- 1 单选预留多选 ); CREATE TABLE paper ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, total_score INT DEFAULT 100, duration INT DEFAULT 60 -- 分钟 ); CREATE TABLE paper_question ( paper_id INT, question_id INT, PRIMARY KEY (paper_id, question_id) ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, paper_id INT, score INT, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP );字段说明answer用单字符存选项判分时直接字符串比对简单可靠。paper_question用联合主键防止同一题重复进卷。score表不存答案明细如果要做错题回顾再加一张answer_detail表存每题作答这是进阶需求先不做。3.2 DBUtil 与连接管理别每次 new 驱动JDBC 最容易被骂的就是「每次请求都 DriverManager.getConnection」连接开销大还容易泄漏。我一般写个DBUtil用静态块加载驱动配一个简易连接获取方法。生产该上连接池但课程设计阶段手写一个够用重点是别把 Connection 当局部变量忘了关。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/exam?useSSLfalseserverTimezoneUTCcharacterEncodingutf8; private static final String USER root; private static final String PWD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); // 8.0 驱动 } catch (ClassNotFoundException e) { throw new RuntimeException(驱动加载失败, e); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 统一关闭顺序不能反 public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }逻辑说明静态块只执行一次驱动加载失败直接抛运行时异常避免后面报莫名其妙的No suitable driver。URL 里serverTimezoneUTC是 MySQL 8.0 的经典坑不写会报时区错误。close方法按 ResultSet → Statement → Connection 顺序关反过来关可能抛异常。参数上characterEncodingutf8保证中文题干不乱码这个不写你录入的题目全是问号。4. 核心功能实现登录、组卷、答题、判分四步走4.1 登录与 Session 拦截登录逻辑本身简单查user表比对用户名和 MD5 密码成功就把 user 对象塞进HttpSession。关键是后续页面要拦截未登录访问我一般写个LoginFilter别在每个 Servlet 里重复判断。WebFilter(urlPatterns {/student/*, /teacher/*}) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }说明getSession(false)表示没有就不新建避免给未登录用户也创建 Session。拦截路径按角色分目录学生和老师页面物理隔离权限判断更清晰。注意 Filter 的urlPatterns别写成/*否则登录页自己也被拦死循环。4.2 组卷与答题页渲染老师组卷就是把选中的题目 ID 批量插入paper_question。学生进入答题页时Servlet 根据 paperId 查出题目列表放 request 域转发到 JSP 渲染。JSP 里用 JSTL 循环每道题渲染四个 radioname 用q_${question.id}这样提交时能按题号取值。// 答题页数据准备 int paperId Integer.parseInt(request.getParameter(paperId)); ListQuestion list questionDao.findByPaperId(paperId); request.setAttribute(questions, list); request.setAttribute(paperId, paperId); request.getRequestDispatcher(/student/exam.jsp).forward(request, response);参数说明paperId从 URL 传入务必做数字校验否则NumberFormatException直接 500。题目列表放 request 而不是 session因为答题页是一次性渲染放 session 会占内存且刷新可能拿到旧数据。4.3 交卷判分与成绩落库判分是核心。前端提交的是一堆q_1Aq_2C这样的参数后端遍历题目拿正确答案比对累加得分。这里有个血泪经验别信前端传的题目 ID 顺序一定要以数据库查出的题目为准去取参数否则学生改一下表单就能作弊。int score 0; ListQuestion questions questionDao.findByPaperId(paperId); for (Question q : questions) { String stuAns request.getParameter(q_ q.getId()); if (stuAns ! null stuAns.equals(q.getAnswer())) { score 100 / questions.size(); // 均分注意整除误差 } } scoreDao.save(userId, paperId, score);逻辑说明按数据库题目遍历保证不漏题、不认伪造题号。100 / questions.size()是整除题目数不整除 100 时总分对不上实际项目里我会用BigDecimal或者按每题固定分值配置。判分完直接落score表然后重定向到成绩页别用 forward否则刷新会重复提交。5. 避坑与排查那些让项目跑不起来的经典问题5.1 中文乱码从请求到响应全链路现象登录页输入中文用户名后台拿到是乱码或者题目内容存进数据库变成问号。原因分三层请求体编码、数据库连接编码、响应编码。解决POST 请求在 Filter 里统一request.setCharacterEncoding(UTF-8)GET 请求 Tomcat 8 以后默认 UTF-8 一般没事但 URL 参数最好手动转数据库 URL 加characterEncodingutf8JSP 页头% page contentTypetext/html;charsetUTF-8 %。三层缺一层都可能翻车。5.2 404 与 ClassNotFound八成是路径和依赖现象启动 Tomcat 访问页面 404或者一提交就ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因404 多半是 Application context 配错或者 JSP 放在WEB-INF下直接访问WEB-INF下的资源浏览器访问不到必须走 Servlet 转发。ClassNotFound 是驱动 jar 没进WEB-INF/lib或没在 Artifacts 里。解决确认访问路径带 context确认 jar 在WEB-INF/lib且 Artifacts 输出包含它。5.3 Session 失效与重复提交现象答到一半刷新页面登录态没了或者交卷后刷新成绩重复插入。原因Session 默认 30 分钟超时考试时长设 60 分钟就会中途掉线重复提交是判分 Servlet 没做幂等。解决web.xml里把session-timeout调到大于考试时长交卷后先查score表该用户该试卷是否已有记录有就跳成绩页不再插入。5.4 时间字段与 MySQL 8 时区现象submit_time存进去比实际时间差 8 小时。原因连接串serverTimezoneUTC而服务器在东八区。解决改成serverTimezoneAsia/Shanghai或者数据库和 JVM 时区统一。这个坑不报错只是数据悄悄错答辩时被问「你这成绩时间怎么不对」就很尴尬。5.5 Tomcat 版本与 Servlet 包名冲突现象换台机器部署所有 Servlet 报cannot be cast to javax.servlet.Servlet。原因Tomcat 10 用jakarta.servlet你的代码是javax.servlet。解决统一 Tomcat 9或者全局替换包名并换依赖。团队协作时把 Tomcat 版本写进 README别让下一个人踩。6. 进阶验证用一套自动化脚本确认判分逻辑没写错功能写完不代表对。判分这种核心逻辑我习惯写个不依赖容器的单元测试直接调 DAO 和判分方法把边界情况跑一遍。这样改代码时心里有底也方便答辩时展示「我验证过」。Test public void testJudge() { // 构造 4 道题正确答案 A B C D ListQuestion qs Arrays.asList( mockQ(1, A), mockQ(2, B), mockQ(3, C), mockQ(4, D)); // 学生答对 3 道错 1 道 MapInteger, String stuAns new HashMap(); stuAns.put(1, A); stuAns.put(2, B); stuAns.put(3, C); stuAns.put(4, A); int score JudgeService.judge(qs, stuAns); assertEquals(75, score); // 4 题均分对 3 题得 75 }这段测试的价值在于把「判分」从 Servlet 里抽出来成独立方法输入题目列表和学生答案 map输出分数。参数上注意assertEquals的期望值要跟你的分值算法一致如果你改成每题 25 分固定值这里就写 75。跑通它再跑集成测试启动 Tomcat用浏览器或 Postman 模拟一次完整答题确认数据库score表多了一条正确记录。我自己的习惯是每加一个功能就先想「怎么证明它是对的」而不是「看起来能跑就行」。考试系统最怕判分错学生答对了给判错投诉起来你连日志都没有。所以判分逻辑单独抽类、单独测试是我踩过坑之后雷打不动的做法。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

DeepSeek DSec论文解读:面向大规模Agentic训练的弹性沙盒基础设施

DeepSeek DSec论文解读:面向大规模Agentic训练的弹性沙盒基础设施

这几天圈子里很多人都在转DeepSeek的新论文,题目是《DeepSeek Elastic Compute(DSec): A Sandbox Infrastructure for Effective Agentic Training at Scale》。我对这个方向一直很关注,论文放出来当天就仔细读了一遍。今天这篇就算是一篇论文阅读笔记&a…

2026/10/9 12:50:25 阅读更多 →
Redis 数据类型全景:五种基础类型怎么用、什么时候用

Redis 数据类型全景:五种基础类型怎么用、什么时候用

个人主页&#xff1a;> for_ever_love__ <&#xff08;欢迎各位大佬莅临&#x1f60a;&#xff09; 其他栏目: > 大模型开发从0到1 < 其他栏目: > iOS项目总结大全 < 其他栏目: > 我想学python了 < 其他栏目: > iOS UI < 文章目录Redis 数据类型…

2026/10/8 5:36:30 阅读更多 →
superpowers不是安装包,而是工具链、工作流与正反馈的系统工程

superpowers不是安装包,而是工具链、工作流与正反馈的系统工程

最近总能在后台收到类似的问题&#xff1a;superpowers怎么装&#xff1f;哪里有 superpowers 的下载链接&#xff1f;说真的&#xff0c;每次看到这种留言我都挺感慨。大家把 superpowers 当成某个具体的软件包&#xff0c;好像装上一个工具就能立即拥有开挂一样的能力。我自己…

2026/10/8 5:36:25 阅读更多 →

最新新闻

Altium Designer 17.0.6安装避坑指南:从环境检查到静默部署的完整方案

Altium Designer 17.0.6安装避坑指南:从环境检查到静默部署的完整方案

简介&#xff1a;Altium Designer 17.0.6安装教程PDF&#xff0c;面向电子设计工程师及PCB初学者&#xff0c;解决Altium Designer软件安装、破解与汉化流程不熟悉的问题。资源包内共1个pdf文件&#xff0c;整体大小3.03MB&#xff0c;内容紧凑&#xff0c;以图文步骤方式组织&…

2026/10/9 14:55:27 阅读更多 →
5G网络切片隔离性验证:从测试设计到pytest自动化落地

5G网络切片隔离性验证:从测试设计到pytest自动化落地

去年做5G行业专网交付的时候&#xff0c;客户在验收会上问了我一个很要命的问题&#xff1a;"你说切片隔离&#xff0c;那我车间里的视频监控流量和AGV控制流量在同一个基站下跑&#xff0c;监控业务能不能把控制业务挤垮&#xff1f;你拿什么证明它不会&#xff1f;"…

2026/10/9 14:55:27 阅读更多 →
Markdown编辑器从入门到进阶:选型、语法与工作流全攻略

Markdown编辑器从入门到进阶:选型、语法与工作流全攻略

刚拿到一个新的 .md 文件时&#xff0c;很多人第一反应是双击打开&#xff0c;然后看到满屏的 # 和 *&#xff0c;第一反应是&#xff1a;这文件是不是坏了&#xff1f;我当年也一样&#xff0c;项目文档发过来&#xff0c;我以为是文本乱码&#xff0c;差点把文件删了。后来才…

2026/10/9 14:55:27 阅读更多 →
GitHub热榜解码:技术趋势识别与工程化落地指南

GitHub热榜解码:技术趋势识别与工程化落地指南

1. 项目概述&#xff1a;这不是一份榜单&#xff0c;而是一份开源世界的实时脉搏图“GitHub 热榜项目&#xff1a;周榜&#xff08;2026-10-04&#xff09;”——看到这个标题&#xff0c;很多人第一反应是点开链接、扫一眼排名、记下几个耳熟的仓库名&#xff0c;然后关掉页面…

2026/10/9 14:55:27 阅读更多 →
GitHub日榜数据采集与验证:构建可复现的热榜观测体系

GitHub日榜数据采集与验证:构建可复现的热榜观测体系

1. 热榜不是排行榜&#xff0c;而是开发者的行为镜像“GitHub 日榜&#xff08;2026-10-04&#xff09;”这个标题乍看像一份静态榜单&#xff0c;但实际它是一扇实时窗口——透过它&#xff0c;你能看到全球开发者在这一天集体关注什么、正在解决什么真实问题、又在用什么新方…

2026/10/9 14:55:27 阅读更多 →
PHP 接口压力测试实战:用 wrk 从零搭建性能基线

PHP 接口压力测试实战:用 wrk 从零搭建性能基线

很多同学第一次给 PHP 项目做压力测试&#xff0c;第一反应都是&#xff1a;找个工具把接口打爆&#xff0c;看看会不会挂。我以前也这么想&#xff0c;直到有次新功能上线前我用 wrk 随手压了一套接口&#xff0c;发现 QPS 连 200 都没到&#xff0c;RT 的 P99 却已经飙到 1.5…

2026/10/9 14:54:26 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题&#xff0c;隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题&#xff0c;排查到最后发现是ZonedDateTime序列化后时区丢了&#xff0c;用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问&#xff1a;办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好&#xff0c;问题是工作场景经常要在几处环境之间来回切换&#xff0c;每次都先登录跳板机再层层代理&#xff0c;实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及&#xff0c;但真正动手搭过一套能跑起来的 Agent 系统的人都知道&#xff0c;从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地&#xff0c;从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →