ARTICLE DETAIL

资讯详情

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

Java Web投票系统实战:JSP+Servlet+Access手写MVC教学基线

Java Web投票系统实战:JSP+Servlet+Access手写MVC教学基线 简介这是一份面向高校Web开发课程设计实践的Java Web投票系统完整实现适用于Java Web初学者巩固JSP、Servlet基础与数据库交互能力。资源包含前后端分离的双入口设计普通用户通过index.jsp免登录参与投票管理员凭admin/admin凭证登录login.jsp进行后台管理涵盖添加、删除、统计等核心功能。压缩包共14个文件以9个JSP页面如vote.jsp、manage.jsp、process.jsp构成交互逻辑1个Java类DBBean.java封装数据库操作1个XML配置文件web.xml定义应用结构1个Access数据库vote.mdb存储投票数据另含GIF图标与编译类文件整体仅16KB轻量易部署。已有866人学习下载提供开箱即用的可运行源码、清晰的目录分层vote/WEB-INF/classes/dbBean及典型MVC雏形结构便于理解Web应用请求流程、会话管理与简单权限控制机制。1. 这不是又一个“Hello World”投票页面它是一套可运行、可调试、可扩展的 Java Web 投票系统教学基线你手头这份web课程设计基于Java的投票系统源码数据库.zip表面看是学生期末作业压缩包实则是一条被反复验证过的 Java Web 实战路径——从 JSP Servlet 基础架构出发绕过 Spring Boot 的封装黑箱直击 HTTP 请求生命周期、会话管理、SQL 注入防护边界、JDBC 连接池雏形等核心机制。它不依赖 Maven 一键拉取所有类如DBBean.java和配置web.xml都摊开在WEB-INF/classes下你能清晰看到vote.mdbAccess 数据库如何通过 JDBC-ODBC 桥接被dbBean封装也能在process.jsp里逐行 debug 投票逻辑的事务控制粒度。适合两类人一是刚学完 Servlet 生命周期但写不出完整流程的新手能照着login.jsp → checkadmin.jsp → manage.jsp链路理解角色权限分层二是准备 Java 面试题中「手写简易 MVC」或「JDBC 手动事务管理」的求职者这里add.jsp提交后process.jsp中的conn.setAutoCommit(false)就是标准答案原型。它不追求高并发但每一步跳转、每一次 SQL 拼接、每一个 request.setAttribute() 都暴露在眼皮底下。2. 从 Access 数据库到 JSP 页面解析vote.mdb结构与DBBean.java的 JDBC 封装逻辑2.1vote.mdb表结构与字段语义映射该系统使用 Microsoft Access 作为后端存储文件位于根目录下。通过 Access 客户端或支持.mdb的工具如 MDB Viewer Plus打开vote.mdb可确认其包含以下关键表表名字段含类型用途说明usersid(AutoNumber),username(Text),password(Text),role(Text)存储管理员账号usernameadmin,passwordadmin及潜在用户role区分 admin / usertopicsid(AutoNumber),title(Text),description(Memo),status(Yes/No)投票主题主表statusYes表示启用中optionsid(AutoNumber),topic_id(Number),content(Text),votes(Number)每个主题下的选项votes记录得票数注意此处无乐观锁或版本号字段高并发场景需自行加锁votes_logid(AutoNumber),user_ip(Text),topic_id(Number),option_id(Number),create_time(Date/Time)投票日志表用于防刷但当前未在代码中强制校验 IP 重复提示Access 的.mdb文件在 Java 中需通过 JDBC-ODBC 桥访问这意味着部署时需在目标机器上配置 ODBC 数据源DSN且 JDK 8 及以上版本已移除sun.jdbc.odbc.JdbcOdbcDriver必须降级至 JDK 7 或改用 UCanAccess 驱动。这是该系统最易卡住的第一道门槛。2.2DBBean.java的连接管理与 SQL 执行封装DBBean.java位于WEB-INF/classes/dbBean/目录是整个数据访问层的核心。其关键方法如下package dbBean; import java.sql.*; public class DBBean { private Connection conn null; private Statement stmt null; private ResultSet rs null; // 构造函数中硬编码 DSN 名为 vote public DBBean() { try { Class.forName(sun.jdbc.odbc.JdbcOdbcDriver); // JDK 7 专用 conn DriverManager.getConnection(jdbc:odbc:vote, , ); } catch (Exception e) { e.printStackTrace(); } } // 执行查询返回 ResultSet注意未关闭资源需调用方手动 close public ResultSet executeQuery(String sql) { try { stmt conn.createStatement(); rs stmt.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } return rs; } // 执行更新INSERT/UPDATE/DELETE返回影响行数 public int executeUpdate(String sql) { int result 0; try { stmt conn.createStatement(); result stmt.executeUpdate(sql); } catch (SQLException e) { e.printStackTrace(); } return result; } // 关闭连接但未处理 stmt/rs存在资源泄漏风险 public void close() { try { if (conn ! null !conn.isClosed()) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }参数与逻辑说明jdbc:odbc:voteDSN 名称必须与操作系统 ODBC 数据源管理器中创建的名称完全一致大小写敏感空用户名密码(, )Access 数据库默认无认证若设了密码需在此处传入executeQuery()返回ResultSet调用方如index.jsp必须显式调用rs.next()并在 finally 块中关闭rs和stmt否则连接池耗尽close()方法缺陷仅关闭Connection未关闭Statement和ResultSet实际部署时需重写为try-with-resources或在每个业务方法末尾手动关闭。2.3web.xml中的 Servlet 映射与初始化参数WEB-INF/web.xml定义了该 Web 应用的基础配置。关键片段如下?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 全局初始化参数指定 Access 数据库路径实际未被 DBBean 使用属冗余配置 -- context-param param-namedbPath/param-name param-valueD:/vote.mdb/param-value /context-param !-- 欢迎文件列表 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app注意context-param中的dbPath在DBBean.java中并未读取属于课程设计常见冗余项。真实项目中应通过ServletContext.getInitParameter(dbPath)获取并动态构造 URL而非硬编码 DSN。3. 用户与管理员双入口index.jsp与login.jsp的请求流转与会话控制3.1 普通用户流程index.jsp如何加载主题与选项index.jsp是系统首页无需登录即可访问。其核心逻辑在于从topics表加载启用状态的主题并关联options表展示选项% page importjava.sql.* % % page importdbBean.DBBean % % DBBean db new DBBean(); ResultSet rsTopics db.executeQuery(SELECT * FROM topics WHERE status True); % !-- HTML 表格渲染 -- table trth投票主题/thth操作/th/tr % while(rsTopics.next()) { % tr td% rsTopics.getString(title) %/td td a hrefvote.jsp?topicId% rsTopics.getInt(id) %参与投票/a /td /tr % } % /table % // 必须手动关闭 ResultSet 和 Statement if (rsTopics ! null) rsTopics.close(); if (db ! null) db.close(); // 但此 close 不关 rs/stmt隐患在此 %关键点解析URL 参数传递vote.jsp?topicId1将主题 ID 以 GET 方式传入vote.jsp再根据该 ID 查询对应选项资源未释放rsTopics.close()被显式调用但DBBean的stmt未关闭多次刷新将导致SQLException: Too many connections无 CSRF 防护投票提交为简单表单 POST无 token 校验若开放公网需增加 hidden input token 并服务端比对。3.2 管理员流程login.jsp→checkadmin.jsp→manage.jsp的身份校验链管理员入口分为三步构成典型的 Servlet/JSP 身份验证链login.jsp纯 HTML 表单POST 到checkadmin.jspform actioncheckadmin.jsp methodpost 用户名input typetext nameusernamebr 密码input typepassword namepasswordbr input typesubmit value登录 /formcheckadmin.jsp执行校验并设置会话属性% String username request.getParameter(username); String password request.getParameter(password); DBBean db new DBBean(); ResultSet rs db.executeQuery( SELECT * FROM users WHERE username username AND password password ); if (rs.next() admin.equals(rs.getString(role))) { session.setAttribute(admin_logged_in, true); // 设置会话标识 response.sendRedirect(manage.jsp); } else { out.println(登录失败请检查用户名和密码); } // rs.close(); db.close(); —— 此处同样缺失 %manage.jsp会话保护页顶部强制校验% if (!true.equals(session.getAttribute(admin_logged_in))) { response.sendRedirect(login.jsp); return; // 终止后续执行 } % !-- 管理员操作界面 --安全缺陷与修复建议SQL 注入漏洞checkadmin.jsp中直接拼接username和password输入 OR 11即可绕过认证。修复方式改用 PreparedStatementString sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery();会话固定风险登录成功后未调用session.invalidate()再request.getSession(true)生成新会话 ID明文密码存储users表中password字段存的是明文admin生产环境必须使用 BCrypt 加密。3.3process.jsp投票动作的事务控制与防重提交process.jsp是投票逻辑的执行中心接收vote.jsp提交的optionId和topicId完成计票与日志记录% String topicId request.getParameter(topicId); String optionId request.getParameter(optionId); DBBean db new DBBean(); // 开启事务 db.conn.setAutoCommit(false); try { // 1. 更新选项票数 String updateSql UPDATE options SET votes votes 1 WHERE id optionId; db.executeUpdate(updateSql); // 2. 记录投票日志当前未校验 IP 是否已投 String logSql INSERT INTO votes_log (user_ip, topic_id, option_id, create_time) VALUES ( request.getRemoteAddr() , topicId , optionId , Now()); db.executeUpdate(logSql); db.conn.commit(); // 提交事务 response.sendRedirect(index.jsp?msgsuccess); } catch (Exception e) { db.conn.rollback(); // 回滚 response.sendRedirect(index.jsp?msgerror); } finally { db.conn.setAutoCommit(true); // 恢复自动提交 db.close(); } %事务边界与并发问题setAutoCommit(false)作用域仅对当前Connection生效DBBean实例复用时需确保每次请求独占连接当前无连接池故可行IP 防重逻辑缺失votes_log表虽存在但process.jsp未执行SELECT COUNT(*) FROM votes_log WHERE user_ip ? AND topic_id ?校验需在try块开头添加此查询并判断rs.next()Now()函数兼容性Access 使用Now()MySQL 用NOW()Oracle 用SYSDATE跨数据库迁移时需抽象为 DAO 方法。4. 本地部署实战JDK 7 Tomcat 8 UCanAccess 驱动的零配置启动方案4.1 环境准备规避 JDK 8 的 JDBC-ODBC 桥移除问题由于DBBean.java强依赖sun.jdbc.odbc.JdbcOdbcDriver而该驱动在 JDK 8u251 后被彻底移除强行使用 JDK 7 是最简方案。但若必须用 JDK 11则需替换为 UCanAccess下载 UCanAccess 5.0.1支持 JDK 11官网地址https://ucanaccess.sourceforge.net/site.html解压后获取ucanaccess-5.0.1.jar、commons-lang3-3.8.1.jar、commons-logging-1.2.jar、hsqldb-2.5.0.jar、jackcess-3.0.1.jar修改DBBean.java的驱动加载与 URL// 替换原 Class.forName(...) 和 getConnection(...) Class.forName(net.ucanaccess.jdbc.UcanaccessDriver); conn DriverManager.getConnection(jdbc:ucanaccess://D:/path/to/vote.mdb);将上述 5 个 JAR 包复制到WEB-INF/lib/目录覆盖原有依赖。提示UCanAccess 是纯 Java 实现无需配置 ODBC DSNgetConnection()中的路径必须为绝对路径Windows 下如D:/vote.mdb相对路径会导致UCanAccessException: file not found。4.2 Tomcat 8.5 部署步骤与web.xml兼容性适配该系统基于 Servlet 2.4 规范web.xml中version2.4而 Tomcat 8.5 默认支持 Servlet 3.1。需做两处适配web.xml头部声明升级推荐web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1或保持原声明但确保 Tomcat 8.5 的conf/web.xml中servlet-class未被覆盖通常无需改动。部署命令流Windows CMD# 1. 解压 vote.zip 到 Tomcat webapps 目录 unzip web课程设计基于Java的投票系统.zip -d C:\apache-tomcat-8.5.94\webapps\vote # 2. 复制 UCanAccess JAR若使用 JDK 11 copy ucanaccess-5.0.1.jar C:\apache-tomcat-8.5.94\webapps\vote\WEB-INF\lib\ copy commons-lang3-3.8.1.jar C:\apache-tomcat-8.5.94\webapps\vote\WEB-INF\lib\ # ... 复制其余 3 个 JAR # 3. 启动 Tomcat C:\apache-tomcat-8.5.94\bin\startup.bat # 4. 访问验证 # 普通用户http://localhost:8080/vote/ # 管理员http://localhost:8080/vote/login.jsp 账号 admin/admin4.3 常见启动报错与精准定位方法报错信息根本原因定位命令/步骤java.lang.ClassNotFoundException: sun.jdbc.odbc.JdbcOdbcDriverJDK 版本过高java -version查看 JDK 版本若 ≥ 8改用 UCanAccessjava.sql.SQLException: [Microsoft][ODBC Driver Manager] Data source name not foundODBC DSN 未配置或名称错误控制面板 → 管理工具 → ODBC 数据源64 位→ 系统 DSN → 确认存在名为vote的 Access DatabaseHTTP Status 404 – Not Found应用未部署成功或上下文路径错误查看C:\apache-tomcat-8.5.94\logs\catalina.out搜索deploy关键字确认解压后目录为webapps/vote/非webapps/vote/xxx/HTTP Status 500 – Internal Server Error且日志含NullPointerExceptionDBBean初始化失败如conn为 null在DBBean构造函数末尾添加System.out.println(DBBean init: (conn!null));重启后查catalina.out5. 从课程设计到工程实践三个可立即落地的增强技巧5.1 为vote.jsp添加防刷校验基于 Session 的单主题单次投票当前系统允许同一 IP 对同一主题重复投票。利用HttpSession可实现轻量级防刷无需修改数据库!-- vote.jsp 开头添加 -- % String topicId request.getParameter(topicId); String sessionKey voted_topic_ topicId; if (session.getAttribute(sessionKey) ! null) { out.println(h3您已对本主题投过票不可重复提交/h3); out.println(a hrefindex.jsp返回首页/a); return; // 终止页面渲染 } % !-- 表单提交后在 process.jsp 成功分支中添加 -- % if (success.equals(request.getParameter(msg))) { session.setAttribute(voted_topic_ topicId, true); // 并设置 24 小时过期可选 session.setMaxInactiveInterval(24 * 60 * 60); } %优势与局限零数据库压力校验在内存中完成避免SELECT COUNT查询会话级隔离不同浏览器不同 session可分别投票符合普通用户预期不防代理/IP 池仅对同一浏览器有效高级防刷需结合 Redis IP User-Agent 多维指纹。5.2manage.jsp中嵌入实时票数刷新用 AJAX 替代整页 reload管理员在manage.jsp查看票数时当前需手动刷新页面。引入原生 JavaScript AJAX 可实现局部更新!-- 在 manage.jsp 的票数表格中为每个选项的 votes 单元格添加 id -- td idvotes_% optionId %% optionVotes %/td !-- 页面底部添加 AJAX 刷新脚本 -- script function refreshVotes() { fetch(ajax_get_votes.jsp) .then(response response.json()) .then(data { Object.keys(data).forEach(topicId { Object.keys(data[topicId]).forEach(optionId { const el document.getElementById(votes_ optionId); if (el) el.textContent data[topicId][optionId]; }); }); }); } // 每 5 秒刷新一次 setInterval(refreshVotes, 5000); /script配套ajax_get_votes.jsp返回 JSON% page contentTypeapplication/json;charsetUTF-8 % % page importjava.sql.* % % page importdbBean.DBBean % % DBBean db new DBBean(); ResultSet rs db.executeQuery(SELECT t.id as topic_id, o.id as option_id, o.votes FROM topics t, options o WHERE t.id o.topic_id); StringBuilder json new StringBuilder({); while (rs.next()) { String topicId rs.getString(topic_id); String optionId rs.getString(option_id); String votes rs.getString(votes); if (json.length() 1) json.append(,); json.append(\).append(topicId).append(\: {\).append(optionId).append(\: \).append(votes).append(\}); } json.append(}); out.print(json.toString()); rs.close(); db.close(); %5.3delete.jsp的安全删除确认防止误操作的二次弹窗与 SQL 参数化delete.jsp当前直接执行删除无任何确认。增强方案如下!-- delete.jsp 中将删除链接改为按钮触发 JS -- a href# onclickconfirmDelete(% topicId %)删除主题/a script function confirmDelete(topicId) { if (confirm(确定要删除该投票主题及所有选项此操作不可撤销)) { window.location.href process_delete.jsp?topicId topicId; } } /script配套process_delete.jsp必须使用 PreparedStatement% String topicId request.getParameter(topicId); DBBean db new DBBean(); // 使用 PreparedStatement 防注入 String sql DELETE FROM options WHERE topic_id ?; PreparedStatement ps db.conn.prepareStatement(sql); ps.setString(1, topicId); ps.executeUpdate(); sql DELETE FROM topics WHERE id ?; ps db.conn.prepareStatement(sql); ps.setString(1, topicId); ps.executeUpdate(); response.sendRedirect(manage.jsp?msgdeleted); %关键加固点process_delete.jsp中的PreparedStatement彻底杜绝了通过topicId1 OR 11删除全部数据的风险这是 Java 面试中高频考察的防御式编程范例。本文还有配套的精品资源点击获取
返回列表