ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java Web问卷调查系统部署实战:从数据库建表到统计报表全流程

Java Web问卷调查系统部署实战:从数据库建表到统计报表全流程 简介这是一套基于Java Web的问卷调查系统完整项目主要面向计算机相关专业学生或初级Java开发者用于课程设计、毕业设计以及实际业务调研场景。系统实现了从问卷设计、发布、填写到回收统计的在线闭环替代传统纸质问卷有效降低印刷成本、减少发放回收耗时并规避漏卷废卷问题。压缩包共包含245个文件以Java源码、JSP页面、编译后的类文件、数据库脚本和设计说明文档为主另有图片、样式表、脚本等前端资源整体大小仅1.95MB目录结构规整便于导入开发工具后直接查看和运行。目前已有1625人学习下载资源附带数据库备份、SQL脚本和撰写好的论文文档内容涉及J2EE体系架构、设计模式以及通用框架的搭建思路。通过阅读源码和文档读者既能掌握问卷系统的完整业务流程也能学习到分层架构、数据访问对象模式及前端交互的实现细节非常适合用来做二次开发或面试项目复盘。1. 别急着解压先搞清楚这个“源码数据库文档”包里装的到底是什么拿到“基于Java web的问卷调查系统源码数据库文档.zip”这种项目包第一反应都是赶紧解压、导入数据库、点运行。我劝你先停一下。这类包十有八九是从课程设计到毕业设计一路传下来的结构一个Maven工程或传统Web工程里面塞了创建数据库的SQL脚本、一套纯Servlet/JSP或者SSMSpringSpringMVCMyBatis的源码外加一份说明文档。它的真正价值不是“能跑”而是能让你在一周内搞懂一个完整Web系统的数据流转——从建表到答题提交再到后台统计这条链路在java面试八股文里背一百遍都不如亲手把断点打在Controller里走一遍来得实在。适合三类人正在做Java web课程设计的在校生、准备面试想补项目经验的转行开发、以及公司里临时要搭内部调研工具但不想买商业问卷产品的运维或后端。这篇我按“先读数据库、再读源码、再部署、再填坑”的顺序把整个流程拆开讲。2. 从数据库反推业务四张核心表 三处必须会改的字段2.1 问卷系统的四张核心表为什么没有第五张“答案明细表”拿到包以后先别开IDE先找SQL脚本。几乎所有的问卷调查系统数据库里最终都会收敛到四张表问卷主表、题目表、选项表、答卷记录表。先记住这个结论后面读源码会快很多。我以最常见的经典设计为例四张表的核心字段长这样表名负责什么核心字段说人话的解释tb_questionnaire问卷主表id、title、status、create_time一张问卷叫什么名字是草稿还是已发布tb_subject题目表id、qid、type、title、sort这张问卷下有哪些题单选多选还是填空tb_option选项表id、sid、content、weight每道题的选项内容以及选项对应的分值tb_answer答卷记录表id、qid、user_id、content、create_time谁在什么时间答了哪张问卷答案原文存哪你可能注意到我列了“答案明细表”又说是四张核心表。这是大部分Java web课程设计里最微妙的设计取舍很多老项目不单独建答案明细表而是把整份答卷的结果拼成字符串塞进tb_answer的content字段里用逗号或竖线分隔比如“3,2,5,4”。这样做的好处是插入极快、表结构简单、写代码少一半坏处是你要做统计时得先把字符串拆开再和题目、选项逐条关联。拿到包以后先看它的content字段是TEXT还是VARCHAR如果是TEXT那你后面写统计SQL就做好心理准备——要么在Java里拆要么在SQL里用SUBSTRING_INDEX拆。还有个细节早期项目里主键经常不是自增ID而是用UUID字符串或者时间戳因为当时要考虑分布式不重复。但问卷系统根本不需要分布式这种设计纯属给自己添堵。你看脚本的时候如果发现主键是varchar(32)且没有任何默认值建议直接改成自增int改完你后面写分页SQL会轻松十倍。2.2 拿到的SQL脚本怎么倒指定字符集的导入命令与建库顺序SQL脚本分两种一种是建库建表的DDL一种是带INSERT的数据脚本。很多包会把两种合在一个.sql文件里但更规范的是data目录下分两个文件survey_schema.sql和survey_data.sql。倒库之前先翻脚本开头看有没有CREATE DATABASE语句。如果脚本里没有CREATE DATABASE就需要手动建库再导入。我一般用这一套命令顺序和字符集都处理干净了mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS survey_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p survey_db survey_schema.sql mysql -uroot -p survey_db survey_data.sql第一句是建库同时指定utf8mb4字符集。为什么不用utf8因为utf8在MySQL里是utf8mb3的别名存不了emoji和生僻字问卷用户一旦在填空里输入特殊字符再查出来就是乱码或者直接报错。第二句导表结构第三句导数据。顺序不能反——先有表才能放数据但很多人的报错“Table doesnt exist”恰恰是因为把第二条和第三条混在一起执行。导入完之后顺手验证一下表是否齐全mysql -uroot -p survey_db -e SHOW TABLES;看到四张核心表都在再执行一条SELECT看看数据有没有进去。数据量不需要大但至少要有一份问卷、两三道题、一份历史答卷这样后面跑统计和报表才有东西看。很多包里的survey_data.sql是空的只有结构没有数据这种情况你别慌先手动INSERT一条问卷和两道单选题进去后面调试报表逻辑用得着。2.3 有三处字段改完后整个系统的报表逻辑都会跟着变读数据库脚本的时候重点盯三个字段它们直接决定了系统跑出来的统计报表长什么样。第一处是tb_option表里的weight字段。有weight意味着系统支持加权计分——比如满意度调查里“非常满意”5分、“满意”4分最终能算平均分没有weight那系统只能统计票数。如果你拿到的包里没有这个字段问卷只能做频次统计做不了打分题这是个硬边界。第二处是tb_subject表里的type字段。一般用数字或字符串表示题型1代表单选、2代表多选、3代表填空、4代表打分。这个字段决定了前台答题页用什么控件渲染也决定了后台统计走哪条分支。改这个字段的值不会报错但答题页渲染会翻车——比如把一道单选题的type从1改成2前端还是渲染单选框用户却想多选数据存进去就乱了。第三处是tb_answer表里的content字段的存储格式。这是整个系统数据模型的根。多选答案到底是用“1,3,5”这种逗号拼接还是用“1|3|5”竖线拼接又或是用“135”直接拼成一串三种格式对应三种不同的拆解SQL。你后期写统计脚本时九成的时间都耗在这个字段上。看这个字段的注释或者看脚本里的样例数据一眼就能判断格式。3. 读懂源码骨架从web.xml到三层包结构再跑通一个最小登录链路3.1 读源码先看三样东西web.xml、pom.xml或lib目录、jdbc.properties解压源码之后不要急着点运行。先把工程结构铺开按我下面的顺序读三个文件基本就能判断这个项目的技术栈和可维护性。第一个看pom.xml。如果工程是Maven结构能在pom.xml里看到完整的依赖清单。看到依赖里有javax.servlet-api说明是传统Servlet项目看到spring-webmvc说明是SSM看到spring-boot-starter-web就说明其实是Spring Boot工程。标题写“Java web”这三种都有可能。判断完技术栈后续部署方式完全不同。如果没有pom.xml那就是传统的Web项目看WEB-INF/lib目录下的jar包重点看servlet-api的版本和是否存在mybatis、mysql-connector-java。第二个看web.xml这文件在src/main/webapp/WEB-INF目录下。它决定了整个应用的请求分发规则welcome-file-list里指定的页面就是访问根路径时先进哪个页面servlet-mapping的url-pattern如果是.do或.action说明是Servlet版本比较老的项目payload里有listener配置ContextLoaderListener说明整合了Spring有没有配置CharacterEncodingFilter直接决定中文字段能不能正常写入数据库。第三个看jdbc.properties或db.properties数据库连接配置基本都在这里。这里能看到数据库地址、端口、库名、用户名密码还能看到连接池是dbcp、c3p0还是没有连接池直接DriverManager。看到jdbc:mysql://localhost:3306/就说明是基于直连的老方案。读这三个文件的过程中大部分对项目的“陌生感”就消除了。3.2 三层包结构怎么落地controller、service、dao的调用链老牌的Java web项目包分层结构基本都是经典三层com.xxx.controller或servlet、com.xxx.service、com.xxx.dao。不管有没有倒腾成SSM这三层的职责边界是一致的——Servlet/Controller管接收请求和响应Service管业务规则Dao管SQL。我拿一个简化版的结构举例这也是问卷系统最常见的包分布src/main/java └── com.survey ├── controller │ ├── LoginServlet.java │ ├── QuestionnaireServlet.java │ └── StatisticServlet.java ├── service │ ├── QuestionnaireService.java │ ├── QuestionnaireServiceImpl.java │ └── StatisticService.java ├── dao │ ├── QuestionnaireDao.java │ └── QuestionnaireDaoImpl.java ├── entity │ ├── Questionnaire.java │ ├── Subject.java │ ├── Option.java │ └── Answer.java └── util ├── DBUtil.java └── StringUtil.java看这种结构的源码有个窍门先看entity里的四个类把字段和数据库四张表对应上再看dao里的SQL语句确认CRUD是否完整最后看service最理想的状况是service只调dao、不写SQL。如果看到service里直接出现Connection和PreparedStatement说明这是个把职责揉在一起的项目——能跑但后续扩展很痛苦你改一段逻辑会连带影响别的地方。新手读这种源码最容易犯的错误是在controller层花太多时间。其实controller层通常是机械的接收参数、转成对象、调用service、跳转页面核心业务逻辑根本不在那里。我一般建议直接把断点打好一路从controller跑到dao跑通一次就能理解全貌。3.3 用一条请求链路把“问卷创建→发布→答题→看统计”串起来光看不练等于白看。我从这类系统中抽取一个最典型的链路——管理员登录后创建问卷然后用户答题最后管理员看统计——把它拆分出来看数据是怎么一步步走的。登录环节的代码核心在LoginServlet的doPost方法。老项目最常见的写法是这样protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); // 接收请求时先定编码防止中文用户名乱码 String username request.getParameter(username); String password request.getParameter(password); // 实际项目中这里不会直接拼SQL而是走service层再透传到dao层 UserService userService new UserServiceImpl(); User user userService.login(username, password); if (user ! null) { // 登录成功就把用户信息塞进session后续答题时用来标记“谁答的” request.getSession().setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /questionnaire/list); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }这段代码有两个细节值得注意第一是request.setCharacterEncoding(UTF-8)必须放在读取参数之前放后面就失效了这是老Servlet项目最经典的中文乱码来源。第二是sendRedirect和forward的区别——重定向是浏览器重新发一次请求地址栏会变转发是服务器内部跳转地址栏不变。问卷系统里登录成功用重定向避免刷新时重复提交表单校验失败用转发把错误信息带回页面。这条链路串起来就是浏览器提交登录表单 → Tomcat根据web.xml里的映射找到LoginServlet → controller取参数并调用service → service调用dao执行SELECT → dao返回User对象 → controller把User放进session → 重定向到问卷列表页。后面创建问卷、答题、统计都是同样的套路。一个登录链路跑通这个项目你就已经拿到了通行证。4. 部署到Tomcat从JDK版本检测到数据库连接的完整启动步骤4.1 第一步先定环境JDK 8 Tomcat 8.5 MySQL 5.7 的兼容组合部署Java web老项目最怕的就是环境版本不对。我这些年踩出来的血泪经验是看到Servlet JSP JDBC这种组合直接默认JDK 8 Tomcat 8.5 MySQL 5.7。如果项目是SSM且pom.xml里Spring版本是4.x那也是同一套。只有看到Spring Boot的形成才考虑JDK 8 内置Tomcat MySQL 5.7或8.0的组合。先把环境验证做掉用下面的命令看当前机器的JDK版本java -version输出的第二行如果是“1.8.0_xxx”说明就是JDK 8。如果是17或21那你大概率会踩javax.servlet找不到的坑——新JDK把Java EE模块抽离了老项目里那些javax.servlet的import直接编译不过。JDK版本和Tomcat版本是绑定的Tomcat 9之前的版本都依赖JDK 8及以下Tomcat 10把javax换成了jakarta老代码完全不兼容。数据库侧我用过一个一图流对应关系项目技术栈JDKTomcatMySQL驱动类名Servlet JDBC1.88.55.7com.mysql.jdbc.DriverSSMSpring 4.x1.88.55.7com.mysql.jdbc.DriverSpring Boot 2.x1.8内置95.7或8.0com.mysql.cj.jdbc.DriverSpring Boot 3.x17内置108.0com.mysql.cj.jdbc.Driver这套组合不是玄学是兼容性约束决定的。老项目的jar包没更新过换了JDK 17分分钟编译失败MySQL用了8.0但驱动jar还停留在5.x也会报驱动类找不到。所以拿到包之后第一件事是看WEB-INF/lib里mysql-connector-java的版本号同时确认项目里的驱动类名写得是哪个。4.2 改配置文件和时间段数据库连接、文件上传路径、日志级别环境对上了下一步就是改配置。老项目的配置集中在src目录下的properties文件和XML文件里。最重要、且几乎必然要改的就是数据源配置。典型的jdbc.properties长这样jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/survey_db?useUnicodetruecharacterEncodingutf8mb4 jdbc.usernameroot jdbc.password123456改这里的时候有讲究。jdbc.url里的characterEncoding值要看数据库实际的字符集你第2章建库时用了utf8mb4这里就要对应写utf8mb4写utf8会在特殊字符上出乱码。MySQL 5.7不用加serverTimezone参数但连MySQL 8.0时必须加serverTimezoneAsia/Shanghai否则会报时区错误。密码字段是明文改完记得不要提交到Git仓库这是基本安全意识。如果包里用的是连接池配置比如c3p0的c3p0-config.xml除了上面的连接参数还会多出minPoolSize、maxPoolSize、acquireIncrement这种参数。看项目规模问卷系统这种低并发场景保持默认的5/20/3就行不用调大调大了浪费连接资源。还有一个经常会漏掉的是日志配置。老项目里有log4j.properties文件里面的log4j.rootLoggerINFO可以临时改成DEBUG这样能看到SQL语句、参数和耗时。排查问题的时候这招特别好用但排查完记得改回来否则控制台会刷屏。4.3 部署与验证war包放进webapps还是直接跑IDEA里的Tomcat配置改完就进入启动阶段。两种方式一种是打包成war丢进Tomcat的webapps目录另一种是直接用IDEA配Tomcat运行。我推荐老项目的同学用IDEA方式因为可以直接打断点调试读代码的效率翻倍。IDEA里配置Tomcat的步骤很简单Run → Edit Configurations → 点号选择Tomcat Server → Local然后选本地的Tomcat安装路径Deployment页签里把Artifact加上Application context填写你要的访问根路径。这里有个容易踩的坑Application context默认是空的你访问时用http://localhost:8080/正好如果填了/survey那访问地址就变成http://localhost:8080/survey/。很多教程里路径对不上就是因为app context和实际部署名不一致。启动成功后打开浏览器访问登录页用SQL脚本里预设的管理员账号登录。如果脚本里没有预设账号看数据库的admin表或user表找到username和password字段密码一般是MD5加密的直接在SQL里执行一条UPDATE把密码改成自己算好的MD5值-- 把admin的密码改成 admin123 的MD5值方便登录 UPDATE tb_user SET password MD5(admin123) WHERE username admin;登录进去之后别急着点功能先测两件事一是创建问卷并保存然后看数据库的tb_questionnaire表是否多了一条记录二是答一份问卷再看tb_answer表是否写入了数据。这两条验证通说明数据库连接、表结构、增删改查链路全部正常系统才真正属于你了。5. 这五个坑我基本每次都会踩从JDBC驱动版本到SQL保留字5.1 时区报错是第一个大坑serverTimezone不能照抄网上的配置现象项目启动后一到登录就报错堆栈里出现The server time zone value Öйú±ê׼ʱ¼ä is unrecognized或者干脆是Exception sending context initialized event to listener instance of ContextLoaderListener。控制台里一堆英文新手看着就心慌。原因MySQL 8.0版本开始JDBC驱动要求必须显式指定时区。网上一搜很多人让加serverTimezoneUTC结果查出来的时间比北京时间慢8小时。还有直接用MySQL 5.7配置的人把serverTimezoneAsia/Shanghai写进jdbc.url也不报错但它根本不需要这个参数纯粹是兼容性问题。解决先看mysql-connector-java.jar的版本再用对应的驱动。MySQL 5.7配com.mysql.jdbc.DriverMySQL 8.0配com.mysql.cj.jdbc.Driver并且在jdbc.url后面追加serverTimezoneAsia/ShanghaiuseSSLfalse。时区里那个“Asia/Shanghai”的斜杠不能省写GMT%2B8也行但读起来不如英文直白。改完重启Tomcat问题消失。5.2 index.jsp写中文就用不了编码、JSP头与mysql连接参数之间的拉扯现象页面一打开中文标题全部是“???”或者表单提交到数据库之后中文全变成了问号。有的人改完JSP页面的pageEncoding也没用。原因这是一个三层的编码传递问题。第一层是JSP页面的pageEncoding决定页面文件怎么读字节第二层是request.setCharacterEncoding决定请求参数怎么解码第三层是jdbc.url里的characterEncoding参数决定Java和MySQL交互时用什么编码。你只改了其中一层另外两层还在用latin1中文自然保不住。解决三层统一改成UTF-8。JSP头部写% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%login的servlet第一行执行request.setCharacterEncoding(UTF-8)jdbc.url里加useUnicodetruecharacterEncodingutf8mb4。如果用了Tomcat还需要注意conf/server.xml里Connector配了URIEncoding的话也改成UTF-8。这套改完基本不会再有问号。5.3 MySQL 8.0驱动加载类名和MySQL 5.7完全不一样现象项目里明明有mysql-connector-java.jar但启动时抛ClassNotFoundException: com.mysql.jdbc.Driver翻遍jar包都找不到这个类。原因MySQL官方的Connector/J在8.0版本做了大调整。老驱动类com.mysql.jdbc.Driver在8.0里被删除了取而代之的是com.mysql.cj.jdbc.Driver。如果你拿着一个老项目源码但本地装的是MySQL 8.0并且把数据库驱动jar也升级到了8.x代码里那个旧类名就成了死引用。解决两个方案任选。要么把mysql-connector-java.jar换回5.1.x版本数据库用5.7代码一行不用改要么把Class.forName里的驱动类名改成com.mysql.cj.jdbc.Driver同时jdbc.url加上serverTimezone和useSSL参数。我一般根据项目里其他jar包的调用风格决定——如果整个项目都停留在JDK 8老风格我倾向于连数据库也一起用5.7保持古代的兼容性如果项目里已经有Stream和Lambda这些新特性那就干脆升到8.0让代码整体现代化。5.4 数据库里改错了ID导致答卷找不到问卷清理数据不要手动删现象后台统计页面明明有答卷记录但点进去看明细提示“该问卷不存在或已被删除”。有的人后台删了一张问卷发现历史答卷变成了一堆孤儿数据统计图表直接报空。原因答案表关联问卷用的是外键或逻辑外键。很多老项目为了省事数据库层面根本不建外键约束全靠代码在service里控制。你用Navicat手动DELETE掉一条问卷记录根本没走service答卷表里的qid还指着那条已经不存在的记录前端一查关联就查空了。解决这条与其说是解决不如说是避坑习惯。所有影响关联数据的操作一律通过后台管理页面里的删除按钮走不要直接在数据库客户端里手动删。后台删除时看service层的代码是否做了级联删除——如果有说明项目设计时考虑到了关联如果没有你要么在数据库里写一个级联删除的存储过程要么删除问卷前先手动把对应答卷和题目选项都删干净。顺序是先删选项、再删题目、再删答卷、最后删问卷主表。顺序反了就会产生上面的孤儿数据。5.5 中文表名和SQL保留字字段取名叫option不是你的错但得背锅现象项目启动正常创建问卷也正常但一提交题目就报SQL语法错误You have an error in your SQL syntax near option或者表名带中文的直接执行失败。原因很多老项目喜欢把字段取名叫“option”“desc”“order”这些SQL保留字。MySQL 5.7里option是保留字建表时用不用反引号都行但如果建表脚本里没带反引号而JDBC的SQL语句里直接写SELECT * FROM tb_option WHERE sid?MySQL解析时会认为option是语法关键字直接报错。解决处理办法是给SQL语句里的保留字段加反引号比如option。但这不是长久之计最好在建表脚本里就把字段重命名。如果脚本已经导入了用一条ALTER TABLE改字段名就行ALTER TABLE tb_option CHANGE option option_content VARCHAR(255);改完字段名同步修改dao层的SQL语句和实体类的属性映射注意实体类里如果用MyBatis的resultMap列名也要对应改掉。这个操作不复杂但很容易漏改——dao改了、实体没改报错信息还特别难懂排查起来最浪费时间。这也是我把这条放在靠后位置的原因等前面基础链路都通了再动它出错范围小一些。6. 把统计报表这一块做成面试亮点权重得分与LayUI进度条的搭配6.1 用一条SQL把多选答案拆开统计防止“1,3,5”这种拼接字符串问卷系统最容易出彩的地方在统计报表。大多数课程设计作品只做了问卷CRUD后台统计就是列个表格没有可视化。你要是能在这里多花一天整个项目的完成度瞬间上一个台阶。第一个要解决的是多选答案的拆分。老项目把多选答案存成“1,3,5”这种逗号拼接统计时先算总票数再按选项拆开分别计数。MySQL里可以用这个思路-- 统计某道多选题每个选项被勾选的次数 SELECT oc.id AS option_id, oc.content AS option_content, (LENGTH(a.content) - LENGTH(REPLACE(a.content, ,, )) 1) AS total_checked FROM tb_answer a JOIN tb_subject s ON a.qid s.qid JOIN tb_option oc ON oc.sid s.id WHERE s.id 5 AND oc.id IN (1, 3, 5);这里用LENGTH(REPLACE(...))的差值来算字符串里逗号的数量数量加一就是勾选了几项。如果想统计某个选项被选了多少次用FIND_IN_SET(1, a.content)来判断当前选项的id是否在content里。FIND_IN_SET在MySQL 5.7和8.0都支持比LIKE匹配更精确——LIKE%1%会把“11”和“21”这种也匹配进去翻车就翻在这个细节上。6.2 LayUI进度条渲染后端返回题目、选项、票数三组数据前端一条进度条搞定数据拆完了展示层可以免费蹭LayUI的组件。LayUI是纯前端UI库不需要后端额外引入依赖页面里引入layui.css和layui.js就能在现代浏览器上直接出效果。统计页面的实现思路是后端统计完返回一个List每个元素包含option_id、option_content、vote_count、total_votes四个字段前端用layui的progress组件渲染// 每个选项一条进度条上面的文字是“选项内容 票数 百分比” layui.use(element, function() { var element layui.element; $.getJSON(statistic/question?id5, function(res) { res.data.forEach(function(item) { var percent Math.round(item.vote_count / item.total_votes * 100); var html div classprogress-item; html span item.option_content item.vote_count 票/span; html div classlayui-progress lay-showpercenttrue; html div classlayui-progress-bar layui-bg-green lay-percent percent %/div; html /div/div; $(#chartBox).append(html); }); element.render(progress); }); });后台统计接口返回JSON时用Map还是用实体类都行但要保证字段命名和前端一致。如果老项目返回的是ListObject[]这种裸数组前端取数据就得按下标来代码可读性差后续加字段特别容易改错。给面试官讲项目时能说清楚“我用了FIND_IN_SET处理多选字符串、用LayUI进度条做可视化、统计接口返回标准JSON”这三件事比背诵十道java面试题都管用。我现在的习惯是每个项目交付前都把自己当用户完整跑一遍“创建问卷→填答→看统计”的闭环中间遇到数据对不上的情况直接看数据库里的原值而不是猜代码逻辑。这种“先怀疑数据、再怀疑代码”的排查顺序能省掉大量自我怀疑的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表