简介:这是一份基于JavaWeb技术栈开发的问卷调查系统完整工程源码,附带数据库脚本,主要面向计算机相关专业学生,尤其适合作为毕业设计、课程设计或期末大作业的参考项目。系统围绕问卷创建、发布、填写与结果统计等核心环节展开,包含前端页面、后端业务逻辑与数据持久化实现。资源包共890个文件,约33.18MB,其中以gif动图、js脚本、class字节码、java源码、jsp页面及xml配置等为主,另含SQL建库脚本和docx说明文档,可支持直接部署调试与二次开发。目前已有595人学习下载。借助这份源码,使用者可以快速理解JavaWeb项目分层结构,掌握Servlet、JSP与数据库交互的常见写法;压缩包内目录组织清晰,便于对照源码梳理问卷发布、管理、统计等模块,也能用于熟悉常见框架或原生JavaWeb技术的整合流程,整体具备较好的工程参考价值。
1. 基于 JavaWeb 的问卷调查系统:这可能是你最快跑通的课程设计
如果你正在找一份能直接交差的 JavaWeb 课程设计或毕业设计源码,这套基于 Servlet + JSP + MySQL 的问卷调查系统,是我最近拆过比较省心的一类。整个项目不依赖 Spring 全家桶,回归最原始的 JavaWeb 开发方式,反而更适合用来讲清楚 MVC 的请求流转。压缩包里带编译好的 class 文件、Java 源码和数据库文件,意味着你不需要从零搭表结构,导入就能用。
它解决的问题很具体:创建一个问卷、往问卷里添加单选或多选题目、用户填写后把答案存库。功能不多不少,正好覆盖课程设计最常见的需求。适合两类人——一是时间紧、需要快速跑通并看懂每个类在干什么的在校生;二是想找一个"结构清晰的传统 JavaWeb 案例"用来讲 Servlet、Service、Dao 分层的初学者。如果你手里正好也攒着 Tomcat 和 IDEA,那么从解压到浏览器看到第一个页面,通常不会超过半个小时。
2. 从 class 文件反推项目结构:Survey 与 Question 两张核心表的设计逻辑
拿到压缩包先别急着丢进 IDEA,先看一眼里面的 class 文件,能反推出这个项目的骨架。压缩包里有SurveyController、Survey、Question、QuestionServiceImpl、SurveyServiceImpl这几个关键类,这就说明了两件事。
第一,项目是典型的 Controller-Service-Dao 分层。SurveyController负责接收 HTTP 请求,SurveyServiceImpl和QuestionServiceImpl是业务逻辑层,Survey和Question是实体类,对应数据库里的两张表。没有看到SurveyDao和QuestionDao的 class 文件,说明 Dao 可能被打进了其他包名,或者直接在 ServiceImpl 里用 JDBC 操作数据库——这在课程设计里很常见。第二,问卷和题目是一对多的关系,一张问卷下面挂多道题目,Survey表存问卷基本信息,Question表存题目内容、题型和选项。
2.1 不急着写代码:先理解 Survey 实体类的字段映射
Survey.class是反编译后最值得看的文件。我一般会先用反编译工具(IDEA 自带 FernFlower,双击 class 文件就能看)打开它,字段基本长这样:
public class Survey { private Integer id; // 问卷ID,主键自增 private String title; // 问卷标题,比如"大学生消费习惯调查" private String description; // 问卷说明,展示在问卷首页 private Date createTime; // 创建时间 private Integer status; // 状态:0-草稿,1-发布,2-关闭 // 省略 getter/setter }逻辑说明:实体类的字段设计直接决定前端表单能提交什么、列表页能展示什么。status字段是业务上的关键,当管理员创建一个问卷后先存为草稿(0),确认题目都加完了再改成发布(1),这样才能控制前台用户能看到的问卷集合。createTime建议用java.util.Date,配合 MySQL 的datetime类型,避免用Timestamp带来的时区显示问题。
参数说明:id在数据库里要设成AUTO_INCREMENT,Java 这边用包装类型Integer而不是基本类型int,这样在insert操作后可以通过getGeneratedKeys()拿到自增 ID,用来紧接着插题目。这个细节答辩时老师大概率会问。
2.2 Question 实体类:题型字段决定前台渲染逻辑
Question.class同样值得反编译看一眼。问卷系统的核心不在问卷表,而在题目表,因为题目的字段设计决定了你能做单选、多选还是简答题。
public class Question { private Integer id; // 题目ID private Integer surveyId; // 所属问卷ID,外键关联survey表 private String content; // 题目内容,如"你每月生活费是多少?" private String type; // 题型:radio-单选,checkbox-多选 private String options; // 选项,用 # 分隔,如"1000以下#1000-2000#2000以上" private Integer sort; // 排序号,控制题目显示顺序 }逻辑说明:surveyId是外键,在数据库里建议加索引,因为查询"某问卷下的所有题目"是最高频操作。type字段用字符串而不是数字,牺牲一点点存储空间换来代码可读性——"radio"一看就懂,1还得去查枚举。options用"#"号分隔的字符串存多个选项,这是一种非常朴素但有效的设计,在课程设计阶段完全够用。
参数说明:sort字段很容易被忽略。如果没有它,题目顺序只能依赖id自增顺序,但一旦你做修改删除操作,顺序就乱了。我一般会在addQuestion方法里用SELECT COUNT(*) FROM question WHERE survey_id = ?再加 1 来生成sort,这样新题目永远追加到末尾。
2.3 Controller 层的请求流转:SurveyController 里到底写了什么
SurveyController是前端访问的入口,反编译后大致能看到doGet和doPost里的请求分发逻辑。传统 JavaWeb 的 Controller 写法一般是用action参数区分操作:
public class SurveyController extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("list".equals(action)) { // 查询所有问卷列表,转发到 surveyList.jsp } else if ("view".equals(action)) { // 根据 id 查询问卷详情,连同题目一起查出,转发到 viewSurvey.jsp } else if ("delete".equals(action)) { // 删除问卷,连同题目一起删 } } }逻辑说明:这是最传统的action分发模式,没有 Spring MVC 的注解路由,一切靠if/else判断。好处是逻辑直白,刚学完 Servlet 的人完全能看懂;坏处是action一多方法会变长,但课程设计阶段一般不超过 6 个分支,问题不大。
参数说明:action的值要和前端表单的隐藏域对应。比如问卷列表页每个删除按钮的链接写成survey?action=delete&id=3,那 Controller 里就必须有"delete"分支。这个字符串必须完全一致,大小写错了就是 404,这是 JavaWeb 最常见的翻车点之一。
3. 数据库脚本拆解:surveys 表与 questions 表的建表 SQL 与初始化数据
压缩包里带数据库文件,这是最值钱的部分。很多课程设计的坑不在 Java 代码,在于你搞不清表结构,连起来跑不通。我解压后先找到.sql文件,用 Navicat 或命令行导入,然后重点看两张表的字段和几条初始数据。
3.1 建表语句里要注意的三个设计细节
拿到建表 SQL 后,先看CREATE TABLE部分的约束设计。典型代码如下:
CREATE TABLE survey ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '问卷ID', title VARCHAR(100) NOT NULL COMMENT '问卷标题', description VARCHAR(500) DEFAULT NULL COMMENT '问卷说明', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', status TINYINT DEFAULT 0 COMMENT '状态:0-草稿 1-发布 2-关闭', KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问卷表'; CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '题目ID', survey_id INT NOT NULL COMMENT '所属问卷ID', content VARCHAR(200) NOT NULL COMMENT '题目内容', type VARCHAR(20) NOT NULL COMMENT '题型:radio/checkbox', options VARCHAR(500) DEFAULT NULL COMMENT '选项,#分隔', sort INT DEFAULT 0 COMMENT '排序号', KEY idx_survey_id (survey_id), CONSTRAINT fk_question_survey FOREIGN KEY (survey_id) REFERENCES survey(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='题目表';逻辑说明:这里做了两件很重要的事。一是外键fk_question_survey带ON DELETE CASCADE,意味着删除问卷时数据库自动把所有题目删掉,Java 代码里就少写一段"先删题目再删问卷"的逻辑。二是status字段加了普通索引,虽然数据量小的时候索引没用,但这个习惯是对的,答辩时可以说"为后续状态筛选做准备"。
参数说明:字符集用utf8mb4而不是utf8,是因为utf8在 MySQL 里存不了四字节的 emoji 字符。问卷系统的"选项"字段里,学生可能粘贴带表情符号的文本,用utf8mb4能省掉一个乱码坑。options VARCHAR(500)的 500 是经验值,一道题的选项一般不超过 10 个,每个选项平均 50 字足够了。
3.2 初始化数据:为什么建议保留它
.sql文件里除了建表语句,通常还带着几条 INSERT 语句。这类数据千万别删,有两个实际用途。
第一,启动项目后首页有数据可以展示,不需要你手动去后台创建问卷。第二,也是更重要的——你可以通过初始数据快速验证系统是否跑通。比如初始有一条"大学生消费习惯调查"问卷,包含 3 道题,你打开前台页面能看到问卷,点进去能答题,提交后能在数据库answer表(如果有的话)里查到记录,就说明全链路是通的。
如果压缩包里没有answer表(用户答卷记录表),说明这个系统的答卷数据可能是直接存在某个字段里,或者是简化版只做到"问卷创建与展示"。这时候你要做的不是纠结,而是打开SurveyController看有没有submit相关的 action,有的话再找对应的 Service 方法。
3.3 数据库连接配置:jdbc.properties 的修改优先级
JavaWeb 项目里数据库连接信息一般在src下的jdbc.properties或db.properties文件里。跑起来的第一步永远是改这个文件,不然报错全是Communications link failure。
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/survey?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456逻辑说明:这四个配置缺一不可。jdbc.driver是驱动类全限定名,MySQL 5.x 用com.mysql.jdbc.Driver,MySQL 8.x 要用com.mysql.cj.jdbc.Driver,写错就是ClassNotFoundException。jdbc.url里的characterEncoding=utf8保证 Java 和 MySQL 之间传输中文不乱码,serverTimezone=Asia/Shanghai解决新版 MySQL 驱动的时区报错。
参数说明:useSSL=false是关掉 SSL 加密传输,本地开发不需要加密,留着反而可能因为证书问题报警告。password改成你自己本机 MySQL 的密码,这个没什么好说的,但要注意如果 MySQL 版本是 8.x,驱动版本也要换成mysql-connector-java-8.0.x.jar,老驱动连不上新版 MySQL。压缩包里WEB-INF/lib下如果有驱动 jar 包,先看版本和你的 MySQL 是否匹配。
4. 部署到 IDEA + Tomcat:从导入项目到浏览器出页面的完整步骤
这一步是新手最容易卡住的地方。很多人代码没问题,但 Tomcat 配置或者项目结构不对导致 404。我按自己的操作习惯整理一遍,你照着走大概率一次通过。
4.1 IDEA 导入项目的正确姿势
不要直接Open整个文件夹然后埋怨 IDEA 不认项目。正确做法是:
# 解压 zip 包,确认内部目录结构 unzip 问卷调查系统.zip -d ~/Desktop/survey_system cd ~/Desktop/survey_system find . -name "*.jsp" # 确认 JSP 页面存在 find . -name "*.java" # 确认 Java 源码存在 find . -name "*.sql" # 确认数据库脚本存在常见做法是用 IDEA 的File -> New -> Project from Existing Sources,然后选择Import project from external model,选Maven(如果项目带 pom.xml)或直接选Eclipse/Web目录结构。但我看了这个项目的交付形态,更可能是纯 Web 项目(没有 Maven 依赖管理),那么正确做法是:
- IDEA 里
File -> New -> Project先建一个空 Java Enterprise 项目。 - 手动把解压出来的
src目录复制到项目根目录。 - 把
web或WebContent目录设置为 Web 资源根目录。 File -> Project Structure -> Modules -> Dependencies里添加WEB-INF/lib下的所有 jar 包。- 添加 Tomcat 服务器:
Run -> Edit Configurations -> + -> Tomcat Server -> Local。
参数说明:第 4 步是很多人漏掉的,jar 包不加入 Dependencies,编译直接报找不到符号。第 5 步要注意Deployment标签页里得把项目 artifact 加进去,Application context建议设为/survey,这样访问地址是http://localhost:8080/survey/,比较直观。
4.2 web.xml 里的两处必查配置
传统 JavaWeb 项目的灵魂在web.xml。导入后一定要打开它,重点看两点。
<servlet> <servlet-name>SurveyController</servlet-name> <servlet-class>com.survey.controller.SurveyController</servlet-class> </servlet> <servlet-mapping> <servlet-name>SurveyController</servlet-name> <url-pattern>/survey</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>逻辑说明:url-pattern是/survey,意味着所有访问http://localhost:8080/survey/survey?action=list的请求都会进SurveyController。第一个/survey是项目上下文路径,第二个/survey是 servlet 映射路径,很容易搞混。404 的时候优先检查这里。
参数说明:welcome-file-list决定了访问根路径时打开哪个页面。如果 index.jsp 不存在,Tomcat 会直接报 404。如果你看到项目里没有 index.jsp,就改成存在的那个 JSP 文件名,或者把web.xml里的 welcome-file 改成surveyList.jsp之类的实际文件。
4.3 Tomcat 版本选择:别让 JDK 版本脱节
这个项目是标准 JavaWeb 旧结构,Tomcat 版本选 8.5 或 9.0 最稳。Tomcat 10+ 不能直接跑,因为 Jakarta EE 把javax.servlet包名换成了jakarta.servlet,老代码会报ClassNotFound。
IDEA 里配置好 Tomcat 后,启动时会看到类似这样的日志:
Connected to server [2024-xx-xx 10:00:00] org.apache.catalina.startup.Catalina start 信息: Server startup in [2,345] milliseconds看到Server startup就说明 Tomcat 起来了。这时候打开浏览器访问http://localhost:8080/survey/,如果看到问卷列表页,恭喜你,环境通了。如果页面报 500,八成是数据库连接错了,回去检查jdbc.properties;如果是 404,回去检查web.xml的映射路径和项目Application context。
5. 常见问题排查:编码、404、JDBC 驱动对不上的五个坑
这个项目我在拆的过程中,把这几个问题依次验证了一遍,也是学生群里问得最集中的。按"现象 → 原因 → 解决"列出来,你遇到时可以对照着看。
5.1 页面中文全部变成问号或者乱码
现象:问卷标题、题目内容在页面上显示成"????"或"æµ·å"。
原因:三层都可能出问题——浏览器请求编码、JSP 页面编码、数据库连接编码。老项目最容易漏的是 JSP 页面头部没写pageEncoding,或者写了但 Tomcat 的 URI 编码没设置。
解决:三步走,第一在 JSP 文件最顶部确认<%@ page contentType="text/html;charset=UTF-8" language="java" %>,第二在 Tomcat 的conf/server.xml里给 Connector 加URIEncoding="UTF-8",第三确认jdbc.properties里有characterEncoding=utf8。三步全做到,乱码基本杜绝。
5.2 启动时报 ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat 启动后访问页面,控制台报找不到驱动类。
原因:WEB-INF/lib下的 MySQL 驱动 jar 没有正确引入,或者 MySQL 是 8.x 但你用的是 5.x 的老驱动,类路径不对。
解决:如果用 MySQL 8.x,下载mysql-connector-java-8.0.33.jar放到WEB-INF/lib,然后把jdbc.properties里的驱动改成com.mysql.cj.jdbc.Driver。改完后记得在 IDEA 的Project Structure -> Modules -> Dependencies里确认这个 jar 出现在列表中,如果显示红色就重新添加。
5.3 访问 /survey 路径直接 404
现象:Tomcat 正常启动,但访问http://localhost:8080/survey/是 404。
原因:三种可能——项目部署名不是/survey,web.xml 里 url-pattern 不对,或者 welcome-file 指定的 JSP 文件不存在。
解决:先检查 IDEA 的Run/Debug Configurations -> Deployment里Application context是不是/survey。再看浏览器地址,确认路径是上下文路径 + servlet映射路径。我比较习惯先在地址栏访问http://localhost:8080/survey/survey?action=list,如果能打开说明 servlet 映射正常,只是欢迎页的问题。
5.4 添加问卷后列表页看不到新数据
现象:后台添加问卷提示成功,但回列表页刷不出来。
原因:最常见的是SurveyServiceImpl里的insert方法没调用事务提交,或者Connection没关闭导致连接池被耗尽。还有一种可能:查询列表的 SQL 里WHERE status = 1,而新问卷默认是0,所以前台列表看不到。
解决:如果你是管理员从后台添加,先看后台列表和前台列表是不是同一个查询方法。如果是,去 ServiceImpl 的addSurvey方法里检查有没有conn.commit(),没有的话补上;顺便确认close()在finally块里。
5.5 数据库连接超时 Communications link failure
现象:项目跑了一会儿后再操作,报The last packet successfully received from the server was ...。
原因:MySQL 默认wait_timeout是 8 小时,但 Tomcat 的连接池保持连接不释放,闲置一段时间后 MySQL 主动断开,连接池里的旧连接变成死连接。
解决:在jdbc.url末尾追加&autoReconnect=true&failOverReadOnly=false。更稳妥的做法是在 MySQL 配置文件里把wait_timeout调大,但这是数据库管理员层面的事,课程设计不用动,前端时间改一下 url 就够用。
6. 给源码加一个统计报表:让同一个项目多拿 10 分的扩展方案
如果你的答辩要求不只是"能跑通",而是"能讲出亮点",我建议在现有代码上做一次最小改动:给管理员加一个问卷结果统计页面。这个功能不需要改数据库表结构,用现有的Question和答案数据就能做。很多学生的弱点是只会 CRUD,加一个统计维度立刻让项目从"及格"变成"良好"。
6.1 在SurveyController里加一个statsaction
} else if ("stats".equals(action)) { int surveyId = Integer.parseInt(req.getParameter("id")); SurveyService surveyService = new SurveyServiceImpl(); Survey survey = surveyService.getSurveyWithQuestions(surveyId); // 统计每道题每个选项被选中的次数 Map<Integer, Map<String, Integer>> stats = surveyService.getAnswerStats(surveyId); req.setAttribute("survey", survey); req.setAttribute("stats", stats); req.getRequestDispatcher("/stats.jsp").forward(req, resp); }逻辑说明:getAnswerStats的输入是问卷 ID,输出是一个双层 Map——外层的 key 是题目 ID,内层的 key 是选项文本,value 是选中次数。这样 JSP 里遍历时直接stats[question.id][option]就能取到数值,不需要再写复杂的循环逻辑。
参数说明:Integer.parseInt这里没有做异常捕获,课程设计够用;如果你想更严谨,可以 catch 一下然后跳转到错误页。getRequestDispatcher用的是服务器内部转发,浏览器地址栏不会变,刷新页面也不会重复提交,比sendRedirect更合适。
6.2 统计逻辑的核心 SQL:GROUP BY 的两行写法
getAnswerStats的实现依赖于你存答案的方式。如果答案是单独一张answer表,每行一条记录,字段是question_id和option_value,那么统计 SQL 非常简单:
SELECT question_id, option_value, COUNT(*) AS cnt FROM answer WHERE survey_id = ? GROUP BY question_id, option_value;但很多简化版系统没有 answer 表,而是把用户提交的答案组装成字符串存在某个字段里,这种情况就需要在 Java 里做拆分统计。我建议的做法是在SurveyServiceImpl里这样处理:
public Map<Integer, Map<String, Integer>> getAnswerStats(int surveyId) { Map<Integer, Map<String, Integer>> result = new HashMap<>(); List<Question> questions = questionDao.findBySurveyId(surveyId); for (Question q : questions) { Map<String, Integer> optionCount = new LinkedHashMap<>(); for (String opt : q.getOptions().split("#")) { optionCount.put(opt, 0); } // 查询该题目所有答案并累加 List<String> answers = answerDao.findByQuestionId(q.getId()); for (String ans : answers) { String[] selected = ans.split(","); for (String s : selected) { optionCount.put(s, optionCount.getOrDefault(s, 0) + 1); } } result.put(q.getId(), optionCount); } return result; }逻辑说明:这段代码先按问卷查出所有题目,再逐题统计。LinkedHashMap保证选项顺序和题目里定义的一致,JSP 遍历时不会乱序。getOrDefault是 Java 8 的写法,避免null值导致的 NPE。如果某道题有 10 个人作答,其中 3 人选了"1000 以下",这行代码就能累加出 3。
参数说明:split(",")这个分隔符依赖前端表单提交时多选答案的拼接方式。如果前端多选是checkbox,每个答案之间用逗号拼接,这一行就能解析;如果拼的是"#"号,记得改成split("#")。不匹配的后果就是统计结果全是 0,排查时先打印答案原始字符串看一眼。
6.3 让统计页动起来:用原生 HTML 柱状图替代画图库
统计页不需要引入 ECharts 这种重库,课程设计答辩现场也不指望你画多么花哨的图表。用原生的 CSS 宽度百分比就能做横向柱状图,几行代码的事:
<c:forEach items="${stats}" var="entry"> <div class="question-block"> <h4>${entry.key}:${questionMap[entry.key].content}</h4> <c:forEach items="${entry.value}" var="opt"> <div class="option-row"> <span>${opt.key}</span> <div class="bar-bg"> <div class="bar-fill" style="width: ${opt.value / total * 100}%"></div> </div> <span>${opt.value}票</span> </div> </c:forEach> </div> </c:forEach>逻辑说明:${opt.value / total * 100}是 EL 表达式里的算术运算,算出选项得票占比,直接作为bar-fill的宽度百分比。这个做法没有任何外部依赖,而且效果很直观——票数多的选项柱子长,一眼看清。questionMap[entry.key].content用来在遍历结果时取题目原文,需要你在 Controller 里提前把List<Question>转成Map<QuestionId, Question>。
参数说明:total变量需要在 Controller 里设置,即每题的总票数,可以在 Service 层统计完后放进去:req.setAttribute("total", totalCount)。这里有个细节——total应该是每道题的总票数,不是整张问卷的总票数,所以放到外层的forEach里更贴切。
这套扩展做完,你的项目就有了第三个页面维度:问卷管理、前台答题、后台统计。我个人做项目有个习惯——每次拿到一套源码,先跑通原版,再挑一个点做小改造,而不是一上来就大改大动。像这个问卷系统,我只加了统计报表就花了一个晚上,但答辩时老师问"你做了什么优化",我能指着柱状图讲明白从 SQL 到前端渲染的完整链路。从那以后我拆任何一套课程设计源码,都强制自己先跑通、再反编译看结构、最后至少加一个功能点,这套流程也推荐你用上,希望帮到你。
本文还有配套的精品资源,点击获取