ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零手搓JavaWeb在线考试系统:Servlet+JSP+JDBC完整实战与避坑指南

从零手搓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表多了一条正确记录。我自己的习惯是每加一个功能就先想「怎么证明它是对的」而不是「看起来能跑就行」。考试系统最怕判分错学生答对了给判错投诉起来你连日志都没有。所以判分逻辑单独抽类、单独测试是我踩过坑之后雷打不动的做法。希望帮到你。本文还有配套的精品资源点击获取
返回列表