尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Web投票系统:Servlet+JDBC实战入门指南

Java Web投票系统:Servlet+JDBC实战入门指南 简介这是一份面向高校Web开发初学者与课程设计实践者的Java Web投票系统完整实现方案适用于《Java Web程序设计》《动态网页技术》等课程的综合实训项目。资源包含前后端可运行源码及配套Access数据库支持普通用户匿名投票与管理员后台管理双模式操作覆盖JSP页面跳转、数据库连接、用户权限控制等核心知识点。压缩包共14个文件以9个JSP页面如index.jsp、login.jsp、manage.jsp等构成前端交互逻辑1个Java类DBBean.java封装数据库操作1个web.xml配置文件定义应用结构1个MDB数据库存储投票数据另含GIF图标等辅助资源整体仅16KB轻量易部署。已有866人学习下载读者可直接导入Tomcat运行快速掌握MVC雏形结构、JDBC连接Access、表单提交与会话管理等典型Web开发流程并基于现有代码拓展统计图表、防刷票机制等功能。1. 为什么一个“Java Web投票系统”能当课程设计硬通货——它不是Demo是Web工程能力的最小闭环你翻过几十个“Java Web课程设计源码”但真正能跑通、能改、能讲清楚数据流向的不到三成。这个标题里的“基于Java的投票系统源码数据库.zip”表面看是个学生作业压缩包实则是Web开发入门者最该啃下的第一块硬骨头它强制你串起Servlet生命周期、JDBC连接池、HTML表单提交、MySQL事务控制、前后端状态同步这五根主干缺一不可。我带过17届毕业设计凡是能把这个系统从零部署、加一个“匿名投票开关”、再把票数统计改成按时间段聚合的同学面试时写CRUD几乎不卡壳——因为ta已经亲手把HTTP请求怎么变成一行INSERT、又怎么被SELECT查出来渲染成表格走了一遍真实链路。它不炫技不堆框架但每行代码都在回答“用户点一下按钮服务器到底做了什么”。适合刚学完JDBC和Servlet、正卡在“写了代码却不知道哪一步断了”的人也适合想快速验证自己是否真懂MVC分层边界的开发者。别急着找Spring Boot版——先让Tomcat原生跑起来才是照见Web底层逻辑的镜子。2. 从解压到首页可访问用最简路径跑通投票系统核心链路这个.zip包本质是一个标准Java Web项目结构不是IDE工程文件也不是Maven模块。它依赖的是Servlet规范3.1、JDK 8、MySQL 5.7所有依赖都打包在WEB-INF/lib/下没有外部仓库拉取环节。这意味着你能完全离线复现但也意味着——任何版本错配都会直接报404或500。下面步骤严格按真实部署顺序展开跳过IDE导入直击Tomcat容器级运行。2.1 解压后必须做的三件事目录校验、数据库初始化、配置文件定位提示不要双击打开jar包或直接拖进IDE先用命令行确认结构unzip -l web课程设计基于Java的投票系统.zip | head -20你会看到类似这样的关键路径投票系统/→ 项目根目录投票系统/WebContent/→ 静态资源HTML/CSS/JS投票系统/WEB-INF/web.xml→ Servlet注册入口投票系统/WEB-INF/classes/→ 编译后的.class文件投票系统/WEB-INF/lib/mysql-connector-java-5.1.47.jar→ JDBC驱动第一步确认数据库脚本位置进入投票系统/目录搜索.sql文件find . -name *.sql -type f常见路径是./sql/或./db/典型文件名如voting_system.sql。该SQL必须包含三张表users管理员/用户、topics投票主题、votes投票记录且votes表需有外键约束指向topics.id。若找不到SQL说明作者把建表语句写在Java代码里极不推荐此时需打开com.xxx.dao.DBUtil.java或类似工具类手动提取CREATE TABLE语句。第二步创建数据库并导入-- 在MySQL中执行注意字符集 CREATE DATABASE voting_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE voting_system; SOURCE /path/to/voting_system.sql; -- 替换为你的绝对路径参数说明utf8mb4是必须项否则中文主题名会变乱码COLLATE utf8mb4_unicode_ci确保排序正确。若导入报错“Unknown collation: utf8mb4_0900_ai_ci”说明MySQL版本≥8.0需将SQL中所有utf8mb4_0900_ai_ci替换为utf8mb4_unicode_ci。第三步定位数据库连接配置搜索jdbc.properties或db.propertiesgrep -r jdbc.url ./ --include*.properties典型内容如下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/voting_system?useUnicodetruecharacterEncodingUTF-8 jdbc.usernameroot jdbc.password123456关键点useUnicodetruecharacterEncodingUTF-8不能省这是中文存取的生命线若密码为空需在MySQL中执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY ;重置空密码策略。2.2 Tomcat部署不靠IDE用war包方式验证容器兼容性很多同学卡在“Eclipse能跑Tomcat独立运行就404”根源是没理解WAR包结构。这个项目需要手动打包成WAR而非直接复制文件夹。生成WAR包的最小命令Linux/macOScd 投票系统/ # 删除无用文件避免冲突 rm -rf WEB-INF/classes/*.class # 编译Java源码假设源码在src/下 javac -d WEB-INF/classes -cp WEB-INF/lib/*:. src/com/xxx/servlet/*.java src/com/xxx/dao/*.java # 打包成WAR jar -cvf voting.war .逻辑说明-d WEB-INF/classes指定编译输出目录-cp WEB-INF/lib/*:.确保编译时能找到JDBC驱动和工具类jar -cvf是JDK自带命令比IDE生成更透明。Windows用户请用PowerShell的Compress-Archive替代但务必保证WEB-INF/web.xml在WAR包根路径下。部署到Tomcat将voting.war复制到$CATALINA_HOME/webapps/启动Tomcat$CATALINA_HOME/bin/startup.shLinux或startup.batWindows访问http://localhost:8080/voting/注意路径是WAR包名去掉.war若页面空白或404请立即检查Tomcat日志tail -f $CATALINA_HOME/logs/catalina.out常见错误如ClassNotFoundException: com.mysql.jdbc.Driver说明JDBC驱动未被Tomcat加载——此时需将mysql-connector-java-5.1.47.jar复制到$CATALINA_HOME/lib/而非仅放在项目WEB-INF/lib/下Tomcat 8对驱动位置更敏感。2.3 首页可访问后的必验三连登录→创建主题→投票→查结果系统首页通常是index.jsp或login.jsp但真正验证成功要看后端逻辑是否贯通。按顺序执行管理员登录用SQL中预置的账号如admin/123456登录进入后台管理页创建投票主题填写标题、选项如“Java还是Python”、“A. Java B. Python”点击提交此时应触发TopicServlet执行INSERT INTO topics并在WEB-INF/views/admin.jsp中刷新列表普通用户投票退出管理员用user1/123456登录在vote.jsp选择选项提交关键验证点VoteServlet中是否调用connection.setAutoCommit(false)开启事务是否在INSERT INTO votes后执行UPDATE topics SET vote_count vote_count 1若票数不增说明事务未提交或SQL语法错验证结果页访问/result.jsp?topicId1应显示柱状图或百分比。若显示“null”或空数组检查ResultServlet中request.setAttribute(topic, topic)是否被request.getRequestDispatcher(result.jsp).forward(request, response)正确传递。3. 数据库设计与JDBC操作为什么投票系统必须手写DAO而不靠ORM这个系统没用Hibernate或MyBatis是刻意为之。它用原始JDBC暴露了Web开发中最易被框架掩盖的三个致命细节连接泄漏、SQL注入、事务边界。当你看到DBUtil.getConnection()被反复调用就要警觉——这不是代码冗余而是教学设计的伏笔。3.1 三张表的核心约束为什么votes表必须有复合唯一索引查看voting_system.sql中的votes表定义CREATE TABLE votes ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, topic_id INT NOT NULL, option VARCHAR(50) NOT NULL, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_topic (user_id, topic_id) -- 关键防重复投票 );参数说明uk_user_topic是复合唯一索引强制同一用户对同一主题只能投一次。若缺失此约束用户刷新页面会重复插入——这是课程设计中最常见的业务逻辑漏洞。而框架自动生成的UniqueConstraint往往被忽略手写SQL才能让你盯着这条UNIQUE KEY敲进键盘。对比topics表CREATE TABLE topics ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, options TEXT NOT NULL, -- 存储A. Java|B. Python格式便于前端split status TINYINT DEFAULT 1, -- 1:开放中 0:已关闭 create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );设计意图options TEXT不用JSON而用|分隔降低解析复杂度status字段实现“投票截止”功能避免删表这种粗暴操作。3.2 JDBC模板代码的血泪经验Connection必须close()但不能在try里close()看com.xxx.dao.VoteDao.java中的核心方法public boolean addVote(int userId, int topicId, String option) { String sql INSERT INTO votes(user_id, topic_id, option) VALUES(?, ?, ?); Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 ps conn.prepareStatement(sql); ps.setInt(1, userId); ps.setInt(2, topicId); ps.setString(3, option); int rows ps.executeUpdate(); if (rows 0) { // 更新主题票数 updateTopicVoteCount(conn, topicId); conn.commit(); // 提交事务 return true; } conn.rollback(); // 回滚 return false; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); // 确保回滚 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBUtil.close(ps, conn); // 关键统一关闭 } }逻辑说明conn.setAutoCommit(false)是事务起点updateTopicVoteCount()需传入同一conn对象否则跨连接无法保证原子性finally块调用DBUtil.close()而非conn.close()因为DBUtil.close()内部做了if (conn ! null !conn.isClosed()) conn.close()判空——这是防止NullPointerException的后悔药。为什么不用try-with-resources因为事务需要跨多个PreparedStatement而try-with-resources会在块结束时自动关闭conn导致后续conn.commit()报SQLException: connection closed。这是新手翻车高发区。3.3 防SQL注入的朴素实践为什么用PreparedStatement却仍要校验输入VoteServlet中获取参数String option request.getParameter(option); // 错误示范直接拼接 // String sql INSERT INTO votes... WHERE option option ; // 正确做法用?占位符 setString() ps.setString(3, option);但仅此不够还需校验option是否在合法范围内ListString validOptions Arrays.asList(A. Java, B. Python, C. Go); if (!validOptions.contains(option)) { request.setAttribute(error, 非法选项); request.getRequestDispatcher(vote.jsp).forward(request, response); return; }原因PreparedStatement防注入但不防业务逻辑错误。若用户篡改前端HTML的select选项值为; DROP TABLE votes; --虽不会执行SQL但可能污染统计结果。所以校验必须在DAO层之前完成。4. 前后端交互的暗礁表单提交、状态保持与中文乱码的三位一体排查这个系统用纯JSPServlet没有Ajax所有交互都靠form submit。看似简单却是乱码、跳转丢失、重复提交的重灾区。很多同学说“页面能打开但投票后数据不对”问题90%出在这里。4.1 表单提交的两个生死线method属性与enctype取值查看vote.jsp中的form标签form actionVoteServlet methodpost input typeradio nameoption valueA. Java A. Javabr input typeradio nameoption valueB. Python B. Pythonbr input typesubmit value提交投票 /form关键点methodpost是必须项若写成get中文选项值如A. Java在URL中会被编码为A.%20Java后端request.getParameter(option)可能截断或乱码。而enctype未声明时默认为application/x-www-form-urlencoded这对文本提交完全够用——不必画蛇添足加multipart/form-data那是上传文件用的。验证方法在VoteServlet开头加日志System.out.println(Raw option: [ request.getParameter(option) ]); System.out.println(Charset: request.getCharacterEncoding());若输出Raw option: [A. Java]且Charset: UTF-8说明编码链路正常若出现A. Java变成A. Java方框乱码则需强制设置request.setCharacterEncoding(UTF-8); // 必须在getParameter()前调用4.2 Session状态保持为什么登录后跳转到投票页会提示“未登录”LoginServlet中认证成功后HttpSession session request.getSession(); session.setAttribute(user, user); // user是User对象 response.sendRedirect(vote.jsp); // 错误会丢失session现象vote.jsp中session.getAttribute(user)为null原因sendRedirect()是客户端重定向会发起新HTTP请求而Tomcat默认不传递session ID除非URL重写解决改用RequestDispatcher.forward()request.getRequestDispatcher(vote.jsp).forward(request, response);但更健壮的做法是在vote.jsp顶部加校验% Object user session.getAttribute(user); if (user null) { response.sendRedirect(login.jsp); return; } %注意return必须写否则JSP继续执行后续HTML造成重定向后仍输出页面的玄学问题。4.3 中文乱码的终极定位法三段式字符集检查乱码不是单一环节问题而是浏览器→Tomcat→MySQL的三段链路断裂。按顺序排查环节检查点命令/操作浏览器页面声明是否UTF-8查看vote.jsp首行是否有% page contentTypetext/html;charsetUTF-8 %TomcatConnector配置是否URIEncoding打开$CATALINA_HOME/conf/server.xml找到Connector port8080 ... /确认含URIEncodingUTF-8MySQL连接URL是否指定编码检查jdbc.properties中jdbc.url是否含?useUnicodetruecharacterEncodingUTF-8血泪经验曾有个学生死磕三天最后发现是Chrome缓存了旧的login.jsp含charsetGBK清空浏览器缓存后立刻解决。所以排查前先CtrlF5强制刷新。5. 避坑指南五个让投票系统“看起来能跑实际全崩”的真实翻车现场注意以下问题均来自真实课程设计答辩现场非理论推测。每一条都对应至少3个学生的崩溃瞬间。5.1 现象首页能打开但登录按钮点击后空白页catalina.out无报错原因web.xml中servlet-mapping的url-pattern写成/LoginServlet带斜杠而form的actionLoginServlet没斜杠导致404。Tomcat对路径匹配极其严格。解决统一写法——web.xml中url-pattern/LoginServlet/url-patternHTML中form action/LoginServlet注意开头斜杠。或者更简单全部去掉斜杠url-patternLoginServlet/url-patternactionLoginServlet。5.2 现象投票成功但数据库votes表里user_id为0topic_id为0原因VoteServlet中request.getParameter(userId)返回null因前端没传该参数。而Integer.parseInt(null)抛NumberFormatException被捕获后静默返回0。解决在VoteServlet开头加防御String userIdStr request.getParameter(userId); if (userIdStr null || userIdStr.trim().isEmpty()) { request.setAttribute(error, 用户未登录); request.getRequestDispatcher(login.jsp).forward(request, response); return; } int userId Integer.parseInt(userIdStr);5.3 现象管理员创建主题后前台看不到新主题原因TopicServlet中查询语句写成SELECT * FROM topics WHERE status 1但status字段在SQL中默认为TINYINT而Java中rs.getInt(status)返回的是数值若数据库里存的是字符串1因建表时类型写错则永远查不到。解决检查topics表结构执行DESCRIBE topics;确认status类型是tinyint(1)不是varchar(1)。若是后者需修改建表语句并重新导入。5.4 现象投票后票数增加但result.jsp显示“0票”原因ResultServlet中request.getParameter(topicId)获取的是字符串而SQL查询用WHERE id ?PreparedStatement将字符串1转为整数时可能失败取决于JDBC驱动版本。解决显式转换String topicIdStr request.getParameter(topicId); if (topicIdStr null) throw new RuntimeException(topicId required); int topicId Integer.parseInt(topicIdStr); // 再执行查询5.5 现象本地MySQL 8.0能跑换到学校机房MySQL 5.5就报java.sql.SQLException: Unknown system variable query_cache_size原因mysql-connector-java-5.1.47.jar是为MySQL 5.x设计的但作者在jdbc.properties中写了jdbc.urljdbc:mysql://...serverTimezoneUTC——serverTimezone是MySQL 8.0参数5.5不识别导致驱动初始化失败。解决删除URL中的serverTimezoneUTC或降级驱动为mysql-connector-java-5.1.26.jar兼容5.5。绝不能强行升级MySQL版本——课程设计环境以稳定为第一优先级。6. 进阶改造加一个“投票截止时间”功能顺便搞懂Java Web里的定时任务陷阱现在系统只有“开放/关闭”状态但真实场景需要精确到分钟的截止。我们给topics表加end_time DATETIME字段并实现“到点自动关闭”。这不是为了炫技而是带你踩透Java Web中定时任务的三个深坑线程安全、容器生命周期、时间精度。6.1 数据库与实体类同步加字段≠能用必须穿透三层第一步修改表结构ALTER TABLE topics ADD COLUMN end_time DATETIME NULL AFTER status; UPDATE topics SET end_time 2025-12-31 23:59:59 WHERE id 1;第二步更新Topic实体类public class Topic { private int id; private String title; private String options; private int status; private Timestamp createTime; private Timestamp endTime; // 新增 // getter/setter... }注意Timestamp类型必须用java.sql.Timestamp不是java.util.Date。否则ResultSet.getTimestamp(end_time)会报SQLException: Cannot convert to Timestamp。第三步修改DAO查询SQL在TopicDao.findAll()中原SQL是SELECT * FROM topics必须显式列出字段String sql SELECT id, title, options, status, create_time, end_time FROM topics; // 否则MySQL 5.7 strict mode下*会忽略新增字段6.2 定时检查的两种实现ServletContextListener vs Quartz为什么选前者很多同学想用Quartz但课程设计不需要分布式调度。我们用ServletContextListener在Tomcat启动时注册一个守护线程每分钟扫描一次topics表。创建TopicScheduler.javapublic class TopicScheduler implements ServletContextListener { private ScheduledExecutorService scheduler; Override public void contextInitialized(ServletContextEvent sce) { scheduler Executors.newSingleThreadScheduledExecutor(); // 延迟1秒启动避免数据库连接未就绪 scheduler.scheduleAtFixedRate(() - checkAndCloseTopics(), 1, 60, TimeUnit.SECONDS); } private void checkAndCloseTopics() { try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement( UPDATE topics SET status 0 WHERE status 1 AND end_time NOW())) { int rows ps.executeUpdate(); System.out.println(Auto-closed rows topics); } catch (SQLException e) { e.printStackTrace(); } } Override public void contextDestroyed(ServletContextEvent sce) { if (scheduler ! null) { scheduler.shutdownNow(); // 必须否则Tomcat关不掉 } } }关键点scheduleAtFixedRate比scheduleWithFixedDelay更可靠它保证每60秒执行一次即使某次执行超时shutdownNow()在Tomcat停止时调用防止线程泄露。注册监听器在web.xml中添加listener listener-classcom.xxx.listener.TopicScheduler/listener-class /listener6.3 时间精度陷阱为什么“NOW()”在某些MySQL配置下不准测试时发现设end_time 2024-06-01 10:00:00但10:00:01才关闭。原因在于MySQL的NOW()函数返回的是服务器系统时间而Tomcat服务器和MySQL服务器若不在同一台机器时钟偏差会导致误差。解决方案用Java获取当前时间传参比对// 替换原SQL String sql UPDATE topics SET status 0 WHERE status 1 AND ? end_time; ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));参数说明System.currentTimeMillis()是Java虚拟机时间与Tomcat同源new Timestamp(...)确保毫秒级精度。这样即使MySQL服务器慢5秒也不影响业务逻辑。最终验证创建主题设end_time为当前时间1分钟观察控制台输出Auto-closed 1 topics刷新admin.jsp确认该主题status变为0尝试对该主题投票VoteServlet中应拦截并提示“投票已截止”我带学生做这个改造时总有人卡在ServletContextListener没生效。后来发现是web.xml的web-app根节点缺少version3.1声明——Tomcat 8要求明确版本号否则忽略监听器。这种细节只有亲手部署过三次以上的人才会记得。希望帮到你。本文还有配套的精品资源点击获取
返回列表