首页 / 资讯中心 / 文章详情

JavaWeb试题库系统实战:Servlet+JSP+MySQL全流程部署与调优

JavaWeb试题库系统实战:Servlet+JSP+MySQL全流程部署与调优 ★ FEATURED ARTICLE
简介这是一份面向计算机、通信、人工智能及自动化等专业学生的JavaWeb课程实践资源聚焦试题库管理系统的完整开发实现适用于期末大作业、课程设计或毕业设计参考。资源包含可直接运行的源码、配套MySQL数据库脚本及详细文档说明答辩成绩达99分代码经多轮调试验证兼顾新手入门与进阶二次开发需求。压缩包共375个文件涵盖24个Java源文件、78个编译后class、28个jar依赖库、24个properties配置项及109个dll动态链接库等支撑系统权限管理、题库维护、组卷阅卷等核心模块整体体积147.4MB结构完整、模块清晰含管理员、组卷员、题库员等多角色UI类如guanliyuanUI.class、tikuyuanUI.class及安全配置文件cacerts、jmxremote.access等。目前已有87人学习下载提供从环境搭建、功能调试到部署上线的全链路学习支撑。1. 这不是又一个“登录注册增删改查”的JavaWeb摆设项目它真能跑通教师出题、学生刷题、自动组卷、成绩分析全流程且99分答辩实测——适合零基础硬啃ServletJSPMySQL的期末冲刺者你肯定见过那种“JavaWeb试题库系统”首页一个登录框点进去全是灰色按钮数据库连不上Tomcat一启动就404文档里写着“本系统基于Struts2Hibernate开发”结果连web.xml都找不到struts-filter配置。这不是教学资源这是玄学考题。而这份源码是我去年帮三个不同学院的学生复现调试时唯一一份从javac编译到localhost:8080/tiku全程无报错、所有角色管理员/组卷员/题库员/知识点管理员界面全可交互、MySQL数据表字段和Java实体类字段100%对齐、连jmxremote.access这种冷门JVM安全配置都提前注释说明的实战型期末大作业。它不炫技没用Spring Boot自动装配但把Servlet生命周期、JDBC连接池、JSP内置对象作用域、HTML表单与request.getParameter()映射、MySQL外键约束与事务回滚这些JavaWeb最核心的“血泪经验点”全埋在代码里。如果你正被课程设计 deadline 追着跑或者想用真实业务逻辑反推Servlet原理而不是对着MVC图空想——这份带完整数据库脚本、含guanliyuanUI.class等6个编译后class文件、附答辩PPT逻辑图的源码就是你能立刻抄起来跑、改、交、过审的后悔药。2. 从解压到首页可见5步完成环境搭建与项目部署关键参数必须手改3处2.1 环境清单与版本锚定为什么必须用JDK 8u202 Tomcat 8.5.90 MySQL 5.7.32这不是兼容性妥协而是项目底层逻辑决定的硬约束。源码中大量使用Override标注的HttpServlet方法重写如doGet()内直接调用request.setCharacterEncoding(UTF-8)而JDK 9默认禁用javax.xml.bind包会导致XMLUtil.java解析题干富文本时抛NoClassDefFoundErrorTomcat 9强制要求web.xml声明metadata-completetrue但本项目web.xml仍采用旧式servlet-mapping写法Tomcat 8.5.90是最后一个兼容该配置且自带catalina-jmx-remote.jar的稳定版MySQL 5.7.32则是因为exam_paper.sql中CREATE TABLE paper_info (id BIGINT AUTO_INCREMENT PRIMARY KEY)依赖5.7的严格模式默认关闭特性——若用MySQL 8.0INSERT INTO paper_info (title, create_time) VALUES (模拟卷, NOW())会因create_time未设默认值直接报错。我一般会用SDKMAN!管理JDK版本sdk install java 8.0.202-amznTomcat从archive.apache.org下载apache-tomcat-8.5.90.tar.gzMySQL用Docker拉取官方镜像docker run -d --name mysql-tiku -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 -e MYSQL_DATABASEtiku_db -v $(pwd)/mysql-data:/var/lib/mysql mysql:5.7.32。2.2 数据库初始化执行exam_paper.sql前必须手动修正的3个字段类型别急着双击运行SQL脚本打开exam_paper.sql你会看到第17行CREATE TABLE question_bank (id INT(11) NOT NULL AUTO_INCREMENT, ...)——这里的INT(11)是MySQL 5.7的遗留写法但实际插入ID超2147483647时会溢出。更致命的是第42行content TEXT NOT NULL题干内容常含HTML标签如p下列关于JVM内存模型的说法正确的是/p而TEXT类型在MySQL 5.7默认排序规则utf8mb4_general_ci下无法索引全文检索字段。必须手动改为-- 原始语句错误 content TEXT NOT NULL, -- 修改后正确 content MEDIUMTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,同理paper_info表的description字段也需同步改为MEDIUMTEXT。执行前先用mysql -u root -p123456 -e CREATE DATABASE tiku_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;建库再导入修正后的SQLmysql -u root -p123456 tiku_db exam_paper.sql。导入后验证SELECT COUNT(*) FROM question_bank;应返回非零值且SHOW CREATE TABLE question_bank\G中content字段显示为MEDIUMTEXT。2.3 Web应用部署WEB-INF/web.xml中3个必须核对的路径映射项目结构是标准JavaWeb目录树但web.xml里藏着3个易被忽略的硬编码路径servlet-mapping中url-pattern/admin/login.do/url-pattern对应AdminLoginServlet.java但源码中该Servlet类实际位于src/com/tiku/servlet/AdminLoginServlet.java编译后class路径为WEB-INF/classes/com/tiku/servlet/AdminLoginServlet.class——确保Tomcatconf/catalina.properties中org.apache.catalina.STRICT_SERVLET_COMPLIANCEtrue未启用否则会因URL模式不匹配拒绝请求context-param里param-valuecom/tiku/dao//param-value定义DAO包扫描路径但BaseDao.java中Class.forName(com.mysql.jdbc.Driver)已过时必须替换为Class.forName(com.mysql.cj.jdbc.Driver)并在pom.xml若用Maven或WEB-INF/lib/中放入mysql-connector-java-8.0.28.jarfilter-mapping中url-pattern/*/url-pattern绑定EncodingFilter但该Filter的init()方法中config.getInitParameter(encoding)读取的是web.xml里init-param的encoding值而源码中该值写死为UTF-8——若你的IDE如IntelliJ IDEA项目编码设为GBK需同步修改Project Encoding和File Encoding为UTF-8否则中文乱码。2.4 启动与首屏验证localhost:8080/tiku返回404检查这3个物理路径Tomcat启动后访问http://localhost:8080/tiku出现40490%问题出在物理路径映射检查apache-tomcat-8.5.90/webapps/下是否存在tiku文件夹不是tiku.war本项目是解压部署非WAR包进入tiku/WEB-INF/确认classes/目录下有com/tiku/servlet/子目录且其中包含AdminLoginServlet.class等编译文件——若只有.java源码需用javac -cp .:WEB-INF/lib/* -d WEB-INF/classes src/com/tiku/servlet/*.java手动编译tiku/WEB-INF/lib/中必须存在mysql-connector-java-8.0.28.jar和commons-dbutils-1.7.jar用于QueryRunner缺一则BaseDao.getConnection()抛ClassNotFoundException。我习惯用jar -tf WEB-INF/lib/mysql-connector-java-8.0.28.jar | grep Driver验证JAR包完整性。提示若启动日志出现SEVERE: Error filterStart说明EncodingFilter的init()方法中config.getInitParameter(encoding)返回null——回到web.xml确认filter节点内init-param的param-name拼写为encoding非encode或ENCODING且param-value标签闭合正确。3. 四类用户角色权限链路拆解从guanliyuanUI.class到zhishidianUI.class的请求流转逻辑3.1 管理员入口guanliyuanUI.class如何触发AdminLoginServlet的Session校验guanliyuanUI.class本质是编译后的JSP页面原始JSP位于/admin/guanliyuan.jsp其HTML表单form actionadmin/login.do methodpost提交至AdminLoginServlet。该Servlet核心逻辑在doPost()中// AdminLoginServlet.java String username request.getParameter(username); String password request.getParameter(password); Admin admin new Admin(username, password); AdminService service new AdminService(); if (service.login(admin)) { HttpSession session request.getSession(); session.setAttribute(admin, admin); // 关键将admin对象存入session response.sendRedirect(admin/main.jsp); // 跳转至管理员主页 } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }注意session.setAttribute(admin, admin)后后续所有管理员页面如admin/main.jsp通过session.getAttribute(admin)获取登录态。若main.jsp中% session.getAttribute(admin) null ? 请登录 : 欢迎 %始终显示“请登录”说明AdminService.login()返回false——此时需检查AdminDao.findByNameAndPassword()中SQL是否为SELECT * FROM admin WHERE username? AND password?且数据库admin表中密码是明文存储本项目未加盐直接比对字符串。3.2 组卷员操作zujuanyuanUI.class调用PaperService.generatePaper()的事务边界zujuanyuanUI.class对应/paper/zujuan.jsp点击“生成试卷”按钮触发PaperServlet的doPost()// PaperServlet.java String subject request.getParameter(subject); int difficulty Integer.parseInt(request.getParameter(difficulty)); PaperService service new PaperService(); try { Paper paper service.generatePaper(subject, difficulty); // 此方法开启事务 request.setAttribute(paper, paper); request.getRequestDispatcher(paper_detail.jsp).forward(request, response); } catch (SQLException e) { e.printStackTrace(); request.setAttribute(msg, 组卷失败请重试); request.getRequestDispatcher(zujuan.jsp).forward(request, response); }PaperService.generatePaper()内部调用PaperDao.insertPaper()和PaperQuestionDao.batchInsert()二者共用同一个Connection对象由BaseDao.getConnection()获取。若batchInsert()中某条题目插入失败如question_id外键不存在PaperDao.insertPaper()已提交的试卷记录不会回滚——必须手动添加事务控制// PaperService.java修正后 public Paper generatePaper(String subject, int difficulty) throws SQLException { Connection conn null; try { conn BaseDao.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 Paper paper paperDao.insertPaper(conn, subject, difficulty); paperQuestionDao.batchInsert(conn, paper.getId(), getQuestions(subject, difficulty)); conn.commit(); // 显式提交 return paper; } catch (Exception e) { if (conn ! null) conn.rollback(); // 异常时回滚 throw e; } finally { if (conn ! null) conn.close(); } }3.3 题库员流程tikuyuanUI.class中QuestionDao.addQuestion()的防重复插入机制tikuyuanUI.class对应/question/add.jsp提交新题时QuestionServlet调用QuestionService.addQuestion()。原代码中addQuestion()仅执行INSERT INTO question_bank (...) VALUES (...)但实际业务要求同一题干选项组合不能重复。需在QuestionDao.addQuestion()中增加唯一性校验// QuestionDao.java增强版 public boolean addQuestion(Question question) throws SQLException { String sql SELECT COUNT(*) FROM question_bank WHERE content ? AND option_a ? AND option_b ? AND option_c ? AND option_d ?; Object[] params {question.getContent(), question.getOptionA(), question.getOptionB(), question.getOptionC(), question.getOptionD()}; long count (Long) queryRunner.query(connection, sql, new ScalarHandler(), params); if (count 0) { return false; // 已存在相同题目 } sql INSERT INTO question_bank (...) VALUES (...); queryRunner.update(connection, sql, question.getParams()); // 执行插入 return true; }前端需配合add.jsp中input typesubmit onclickreturn confirm(确认添加重复题干将被跳过)避免用户误操作。3.4 知识点管理员zhishidianUI.class的树形结构渲染与递归查询实现zhishidianUI.class对应/knowledge/knowledge_tree.jsp展示知识点层级如“Java基础 面向对象 封装”。后端KnowledgeServlet调用KnowledgeService.getTreeRoot()// KnowledgeService.java public ListKnowledge getTreeRoot() throws SQLException { String sql SELECT * FROM knowledge WHERE parent_id 0 ORDER BY sort_order; return queryRunner.query(connection, sql, new BeanListHandler(Knowledge.class)); }knowledge表结构为id, name, parent_id, sort_order前端JSP用递归函数渲染% page importjava.util.* % % page importcom.tiku.entity.Knowledge % % ListKnowledge roots (ListKnowledge) request.getAttribute(roots); // 递归渲染函数简化版 out.print(ul); for (Knowledge k : roots) { out.print(li k.getName()); ListKnowledge children knowledgeService.getChildren(k.getId()); if (!children.isEmpty()) { out.print(ul); for (Knowledge child : children) { out.print(li child.getName() /li); } out.print(/ul); } out.print(/li); } out.print(/ul); %坑点若getChildren()未加缓存每次渲染都会查库——建议在KnowledgeService中用MapInteger, ListKnowledge cache new HashMap()缓存子节点首次查询后存入后续直接cache.get(parentId)。4. 避坑指南95%新手卡住的5个具体问题与血泪解决方案4.1 现象登录页输入正确账号密码跳转admin/main.jsp后提示“HTTP Status 404 – /tiku/admin/main.jsp”原因main.jsp中% include file../common/header.jsp %路径错误。源码中header.jsp实际位于/common/header.jsp但main.jsp在/admin/目录下相对路径应为../common/header.jsp。若项目部署为ROOT上下文即访问localhost:8080/而非localhost:8080/tiku../common/会越级到Tomcat根目录。解决统一用绝对路径% include file/common/header.jsp %或在web.xml中配置welcome-file-list指定index.jsp为首页所有JSP跳转均以/开头。4.2 现象MySQL插入题目时报错Data truncation: Data too long for column content at row 1原因question_bank.content字段类型为TEXT最大65535字节但题干含大量HTML标签如divp.../p/div导致超长。源码中未对content做长度截断处理。解决在QuestionService.addQuestion()中添加截断逻辑if (question.getContent().length() 65500) { question.setContent(question.getContent().substring(0, 65500) ...内容过长已截断); }同时将数据库字段改为MEDIUMTEXT最大16MBALTER TABLE question_bank MODIFY content MEDIUMTEXT;4.3 现象PaperServlet生成试卷后paper_detail.jsp显示题目但答案为空原因PaperQuestionDao.selectByPaperId()查询语句中SELECT q.* FROM paper_question pq JOIN question_bank q ON pq.question_id q.id WHERE pq.paper_id ?但question_bank表中answer字段为VARCHAR(1)只存A/B/C/D而paper_detail.jsp中% q.getAnswer() %试图输出完整选项文字如“A. public class”。解决修改SQL为关联选项字段SELECT q.*, q.option_a, q.option_b, q.option_c, q.option_d FROM paper_question pq JOIN question_bank q ON pq.question_id q.id WHERE pq.paper_id ?并在Question实体类中添加optionA,optionB等字段JSP中用% A. q.getOptionA() %拼接。4.4 现象jmxremote.access文件导致Tomcat启动失败日志报java.lang.SecurityException: JMX Remote Access File not found原因本项目为演示JMX远程监控在conf/jmxremote.access中预置了monitorRole readonly权限但Tomcat 8.5默认不启用JMX远程——该文件纯属干扰项可安全删除。解决直接删除apache-tomcat-8.5.90/conf/jmxremote.access和jmxremote.password并在bin/catalina.sh中注释掉JAVA_OPTS$JAVA_OPTS -Dcom.sun.management.jmxremote...相关行。4.5 现象fontconfig.bfc文件引发Linux下Tomcat启动卡死CPU占用100%原因fontconfig.bfc是Java AWT字体缓存文件位于$JAVA_HOME/jre/lib/fontconfig.bfc。当JDK版本与系统字体库不兼容如OpenJDK 8u202在Ubuntu 22.04上该文件会无限重建。解决删除$JAVA_HOME/jre/lib/fontconfig.bfc并设置JVM参数跳过字体检测在bin/catalina.sh的JAVA_OPTS中添加-Djava.awt.headlesstrue。5. 进阶技巧用3个SQL脚本1个JSP改造把“静态试题库”升级为“动态难度自适应系统”5.1 难度系数动态计算基于历史答题数据的question_difficulty.sql脚本原系统中题目难度是人工录入的固定值1-5星。我们可通过学生答题记录自动计算难度系数difficulty 正确率 × 0.5 平均用时秒× 0.01用时越长难度越高。先创建统计表-- 创建答题记录表若不存在 CREATE TABLE IF NOT EXISTS answer_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20) NOT NULL, question_id BIGINT NOT NULL, is_correct TINYINT(1) NOT NULL DEFAULT 0, used_time INT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_qid (question_id), INDEX idx_sid (student_id) ); -- 计算每题难度系数执行一次 UPDATE question_bank q JOIN ( SELECT question_id, AVG(is_correct) AS correct_rate, AVG(used_time) AS avg_time FROM answer_record GROUP BY question_id ) r ON q.id r.question_id SET q.difficulty ROUND(r.correct_rate * 0.5 r.avg_time * 0.01, 2);注意question_bank.difficulty字段需提前添加ALTER TABLE question_bank ADD COLUMN difficulty DECIMAL(3,2) DEFAULT 1.0;5.2 智能组卷算法PaperService.generateAdaptivePaper()的权重策略将原generatePaper()替换为自适应版本按知识点覆盖率难度均衡性选题// PaperService.java public Paper generateAdaptivePaper(String subject, int targetDifficulty) throws SQLException { ListKnowledge knowledgeList knowledgeService.getBySubject(subject); ListQuestion selectedQuestions new ArrayList(); for (Knowledge k : knowledgeList) { // 每个知识点至少选2题 ListQuestion questions questionDao.findByKnowledgeId(k.getId()); // 按难度接近targetDifficulty优先排序 questions.sort((q1, q2) - Double.compare(Math.abs(q1.getDifficulty() - targetDifficulty), Math.abs(q2.getDifficulty() - targetDifficulty))); selectedQuestions.addAll(questions.subList(0, Math.min(2, questions.size()))); } // 补足至50题随机选难度偏差0.5的题目 while (selectedQuestions.size() 50) { Question q questionDao.findRandomByDifficultyRange( targetDifficulty - 0.5, targetDifficulty 0.5); if (q ! null !selectedQuestions.contains(q)) { selectedQuestions.add(q); } } // 生成试卷 Paper paper paperDao.insertPaper(subject, targetDifficulty); paperQuestionDao.batchInsert(paper.getId(), selectedQuestions); return paper; }5.3 学生端个性化推荐student/recommend.jsp的实时题目推送在学生首页添加“为你推荐”模块根据最近3次答题的错题知识点推送同类新题% String studentId (String) session.getAttribute(studentId); ListLong wrongKIds answerRecordService.getWrongKnowledgeIds(studentId, 3); ListQuestion recommendations new ArrayList(); for (Long kid : wrongKIds) { ListQuestion qs questionDao.findByKnowledgeId(kid); recommendations.addAll(qs.subList(0, 3)); // 每个知识点推3题 } % div classrecommend-section h3为你推荐/h3 ul % for (Question q : recommendations) { % li% q.getContent() % a hrefanswer.jsp?questionId% q.getId() %立即作答/a /li % } % /ul /div5.4 文档说明升级用PlantUML重绘系统架构图与类图原文档说明.docx中的架构图模糊不清。用PlantUML生成可执行代码的图表startuml package Web Layer { [AdminLoginServlet] -- [AdminService] [PaperServlet] -- [PaperService] } package Service Layer { [AdminService] -- [AdminDao] [PaperService] -- [PaperDao] [PaperService] -- [PaperQuestionDao] } package Data Layer { [AdminDao] -- [MySQL: admin] [PaperDao] -- [MySQL: paper_info] [PaperQuestionDao] -- [MySQL: paper_question] } enduml保存为architecture.puml用VS Code PlantUML插件一键导出PNG——比截图专业10倍。从那以后我每次接手JavaWeb课程设计第一件事就是检查web.xml的url-pattern是否与Servlet类名一致、WEB-INF/lib/是否缺JDBC驱动、webapps/下是否多了一层嵌套目录。这三个动作做完90%的404和500错误当场消失。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站