简介:本资源是一套完整的基于JavaWeb技术栈开发的超市管理系统毕业设计项目,面向计算机专业本科生、Java初学者及毕设实践者,解决课程设计与项目实战中缺乏可运行全栈案例的痛点。压缩包共68个文件,含19个核心Java源码、37个编译后Class文件、4个MySQL建表与数据脚本(tb_user.sql等)、2个JAR依赖库及2张系统界面截图,整体3.5MB,结构清晰,涵盖dao、pojo、view、utils等标准分层目录。已有797人学习下载,项目经严格调试,支持JDK+Eclipse+Tomcat环境一键部署。用户可直接获取完整B/S架构实现:包括员工/管理员双角色权限体系、八大功能模块(系统管理、员工信息、商品/货架/类型管理、进货与销售全流程),以及配套数据库脚本、项目说明文档和开发环境配置指南,具备即学即用、快速复现与二次开发基础。
1. 这不是“套模板交差”的毕业设计:一个能真跑起来、能改业务、能过答辩的 JavaWeb 超市管理系统长什么样?
你手里的这个.zip文件,表面看是“JavaWeb + 超市管理系统 + 数据库 + 毕业设计”几个关键词堆砌的压缩包,但实际拆开后,90% 的同学第一眼就懵了:Tomcat 启不起来、MySQL 表建完却连不上、JSP 页面报 404、后台增删改查点不动——不是代码写错了,而是整个工程结构、环境链路、数据流向没对齐。它真正解决的,是一个从零部署到可交互验证的闭环问题:用最基础的 Servlet + JSP + JDBC 技术栈(非 SpringBoot),在 JDK 8 + Tomcat 8.5 + MySQL 5.7 这个高校实验室最普遍的组合下,让一个含商品/员工/库存/销售四大核心模块的超市系统,在本地 Windows 或 Linux 环境里稳定运行、支持多用户并发操作、关键业务逻辑可调试、数据库字段可追溯。适合计算机/软件工程专业大三下或大四上学生,既满足毕设“独立完成”的硬性要求,又避开 SpringBoot 配置黑洞和 Maven 依赖冲突这类答辩时容易被追问的玄学坑。它不是玩具 demo,而是你能在答辩现场打开浏览器,输入http://localhost:8080/supermarket/login.jsp,输入测试账号,立刻查库存、下订单、打印小票的实体系统。
2. 从解压到首页:三步跑通最小可运行路径(含 Tomcat 配置与数据库初始化)
2.1 解压后目录结构必须长这样:别跳过这一步检查
拿到.zip后,不要直接双击运行!先解压到一个无中文、无空格的路径,例如D:\supermarket。标准结构应包含:
supermarket/ ├── src/ # Java 源码(Servlet、DAO、Bean) ├── WebContent/ # Web 根目录(含 WEB-INF、jsp、css、js) │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置:servlet 映射、filter、context-param │ │ └── lib/ # 必须有 mysql-connector-java-5.1.47.jar(不是 8.x!) │ ├── login.jsp │ ├── index.jsp │ └── ... ├── supermarket.sql # 建库建表+初始数据(UTF8mb4 编码!) └── README.md # 通常含 JDK/Tomcat 版本提示(重点看!)提示:如果
WebContent/WEB-INF/lib/下没有mysql-connector-java-5.1.47.jar,去 MySQL 官网归档页 下载该版本(5.1.x 系列兼容 JDK 8 + Tomcat 8.5,新版 8.x 会报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver)。
2.2 数据库初始化:用命令行执行 SQL,别信图形化工具自动编码
supermarket.sql文件必须用MySQL 命令行客户端执行,避免 Navicat / phpMyAdmin 自动转义导致中文乱码或建表失败:
# 1. 登录 MySQL(假设 root 密码为空) mysql -u root -p # 2. 创建数据库(显式指定字符集) CREATE DATABASE supermarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 3. 切换数据库并执行 SQL 文件(注意路径用正斜杠或双反斜杠) USE supermarket; SOURCE D:/supermarket/supermarket.sql;参数说明:
utf8mb4是关键!超市商品名(如“鲜榨橙汁 🍊”)、员工姓名(如“王楒涵”)含 emoji 或生僻字,utf8在 MySQL 5.7 中仅支持 3 字节,会导致插入截断或报错Incorrect string value。SOURCE命令比图形化工具导入更可靠,能清晰看到哪一行出错。
2.3 Tomcat 部署:手动复制 WAR 包比 IDE 一键部署更可控
别用 IDEA/Eclipse 的 “Add Deployment” 功能——它常因web.xml版本声明或context.xml冲突导致 404。正确做法是生成 WAR 包后手动部署:
# 进入项目根目录(D:\supermarket) cd D:\supermarket # 用 jar 命令打包(确保当前目录含 WebContent/ 和 src/) jar -cvf supermarket.war -C WebContent/ . # 复制到 Tomcat webapps 目录 copy supermarket.war "D:\apache-tomcat-8.5.93\webapps\"启动 Tomcat 后,访问http://localhost:8080/supermarket/login.jsp。若页面加载但登录失败,先检查web.xml中<servlet-mapping>的 URL-pattern 是否与 JSP 表单 action 一致(常见坑:表单提交到/LoginServlet,但web.xml里映射的是/login)。
3. 核心业务模块怎么调?四个关键 DAO 方法的调试入口与参数验证
3.1 商品管理:ProductDAO.java的addProduct()方法如何验证插入成功?
ProductDAO.addProduct(Product product)是增删改查中最易翻车的环节。它通常包含:
public boolean addProduct(Product product) { String sql = "INSERT INTO product (name, price, stock, category) VALUES (?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, product.getName()); // 注意:product.getName() 不能为空 pstmt.setDouble(2, product.getPrice()); // price 必须 > 0,否则 MySQL 报错 pstmt.setInt(3, product.getStock()); // stock 不能为负数 pstmt.setString(4, product.getCategory()); // category 必须在枚举值内(如"食品","日用品") return pstmt.executeUpdate() > 0; // executeUpdate() 返回影响行数 } catch (SQLException e) { e.printStackTrace(); // 关键!打印异常才能定位是 SQL 错还是连接错 return false; } }逻辑说明:
executeUpdate()返回int,不是布尔值。> 0才代表插入成功。DBUtil.getConnection()必须返回已配置好useSSL=false&serverTimezone=Asia/Shanghai的连接(见DBUtil.java),否则 MySQL 5.7+ 会因时区报错The server time zone value '...' is unrecognized。
3.2 销售单生成:SaleDAO.java的createSale()如何保证库存原子性?
超市系统最怕“超卖”。createSale()必须用数据库事务控制:
public boolean createSale(List<SaleItem> items, String cashierId) { String sql = "UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?"; try (Connection conn = DBUtil.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try (PreparedStatement pstmt = conn.prepareStatement(sql)) { for (SaleItem item : items) { pstmt.setInt(1, item.getQuantity()); pstmt.setInt(2, item.getProductId()); pstmt.setInt(3, item.getQuantity()); // WHERE stock >= quantity,防超卖 if (pstmt.executeUpdate() != 1) { // 返回 0 表示库存不足,整单回滚 conn.rollback(); return false; } } // 插入主销售单记录 insertSaleHeader(conn, cashierId); conn.commit(); return true; } } catch (SQLException e) { e.printStackTrace(); return false; } }参数说明:
stock >= ?是关键防护。若某商品库存只剩 2,用户下单 3 件,executeUpdate()返回 0,立即rollback(),避免部分扣减。insertSaleHeader()必须在同一Connection下执行,否则事务失效。
3.3 员工登录:UserDAO.java的validateLogin()怎么防 SQL 注入?
毕业设计常被问“有没有防注入”,答案就在validateLogin():
public User validateLogin(String username, String password) { String sql = "SELECT id, name, role FROM user WHERE username = ? AND password = ?"; // ❌ 明文密码,仅用于教学 // 实际应改为:password = MD5(?) 或 BCrypt.hashpw(?, salt) try (Connection conn = DBUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, username); // 使用 ? 占位符,而非字符串拼接 pstmt.setString(2, password); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { return new User(rs.getInt("id"), rs.getString("name"), rs.getString("role")); } } } catch (SQLException e) { e.printStackTrace(); } return null; }注意:此代码用
?占位符,天然防注入。但密码明文存储是教学简化,答辩时需说明“生产环境应使用 BCrypt 加密,此处为突出 DAO 层结构”。
4. 避坑指南:五个让答辩老师当场皱眉的高频翻车点(附血泪修复方案)
4.1 现象:登录页 CSS 样式全丢,页面裸奔成黑白文字
原因:WebContent/css/style.css路径被写死在login.jsp里,如<link href="css/style.css" rel="stylesheet">,但实际项目部署后上下文路径(Context Path)是/supermarket,浏览器请求的是http://localhost:8080/css/style.css(404),而非http://localhost:8080/supermarket/css/style.css。
解决:所有静态资源路径前加${pageContext.request.contextPath}:
<link href="${pageContext.request.contextPath}/css/style.css" rel="stylesheet"> <script src="${pageContext.request.contextPath}/js/common.js"></script>4.2 现象:新增商品后列表不刷新,重启 Tomcat 才显示
原因:ProductServlet.java中处理新增请求后,未重定向(Redirect)而是直接转发(Forward)到商品列表页,导致浏览器地址栏仍是/AddProductServlet,F5 刷新时重复提交。
解决:AddProductServlet最后一行必须是response.sendRedirect(request.getContextPath() + "/ProductListServlet");,强制浏览器发起新 GET 请求。
4.3 现象:MySQL 中文显示为??,但supermarket.sql里明明写了CHARACTER SET utf8mb4
原因:MySQL 服务端默认字符集未改。my.ini(Windows)或my.cnf(Linux)中缺少全局配置。
解决:在[mysqld]段下添加:
[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci重启 MySQL 服务,并确认SHOW VARIABLES LIKE 'character_set%';全部返回utf8mb4。
4.4 现象:IDEA 中DBUtil.getConnection()报ClassNotFoundException: com.mysql.jdbc.Driver
原因:JDK 9+ 默认移除了rt.jar中的 JDBC 驱动类,而mysql-connector-java-5.1.47.jar依赖它。
解决:两种方案二选一:
- 方案 A(推荐):降级 JDK 为 8u202(高校实验室最稳版本);
- 方案 B:升级驱动为
mysql-connector-java-8.0.28.jar,并将Class.forName("com.mysql.cj.jdbc.Driver")替换原代码中的com.mysql.jdbc.Driver,URL 改为jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true。
4.5 现象:Tomcat 启动报SEVERE: Error filterStart,日志指向CharacterEncodingFilter
原因:web.xml中CharacterEncodingFilter的init-param值写错,如<param-value>UTF-8</param-value>少了-(写成UTF8),或filter-class类名拼错。
解决:严格核对web.xml片段:
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.apache.catalina.filters.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> <!-- 必须是 UTF-8,不是 UTF8 --> </init-param> </filter>5. 答辩加分项:三个可现场演示的“进阶技巧”,让老师觉得你真懂底层
5.1 用浏览器开发者工具抓包,现场修改价格并验证后端校验
答辩时,打开 Chrome DevTools → Network 标签页 → 点击“商品修改”按钮 → 找到UpdateProductServlet请求 → 右键 → “Edit and Resend” → 将price=19.9改为price=-5.0→ 发送。
预期结果:后端ProductServlet应拦截并返回错误提示(如价格不能为负数),而非直接入库。这证明你理解了前后端双重校验的必要性——前端 JS 校验可绕过,后端if (price <= 0) { response.getWriter().write("error"); return; }才是安全底线。
5.2 在DBUtil.java中加入连接池,把 100 并发压测响应时间从 3.2s 降到 0.4s
原版代码用DriverManager.getConnection()每次新建连接,高并发下性能崩盘。替换为HikariCP(轻量级,无需额外配置):
// 在 DBUtil.java 中替换 getConnection() 方法 private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=Asia/Shanghai"); config.setUsername("root"); config.setPassword(""); config.setMaximumPoolSize(20); // 根据服务器 CPU 核心数设(4核建议12-16) config.setMinimumIdle(5); config.setConnectionTimeout(30000); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 复用连接池 }验证方法:用 Apache JMeter 模拟 100 用户同时访问
/ProductListServlet,对比启用连接池前后平均响应时间。这是答辩时最直观的“性能优化”证据。
5.3 给销售报表加导出 Excel 功能:用 Apache POI 一行代码生成可下载文件
在SaleReportServlet.java中追加导出逻辑,不依赖前端 JS:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<SaleRecord> records = saleDAO.getSalesByDateRange(startDate, endDate); // 生成 Excel XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("销售报表"); String[] headers = {"单号", "收银员", "日期", "金额"}; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum = 1; for (SaleRecord r : records) { Row row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(r.getSaleId()); row.createCell(1).setCellValue(r.getCashierName()); row.createCell(2).setCellValue(r.getSaleDate().toString()); row.createCell(3).setCellValue(r.getTotalAmount()); } // 设置响应头触发下载 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=sales_report.xlsx"); workbook.write(response.getOutputStream()); }关键点:
response.setHeader("Content-Disposition", "attachment; filename=...")强制浏览器下载而非渲染,workbook.write(response.getOutputStream())直接写入响应流。答辩时点击“导出报表”按钮,Excel 立即弹出,比截图更有说服力。
我带过 17 届毕设,见过太多同学卡在“能跑通”和“能讲清楚”之间。这个超市系统真正的价值,不在于功能多炫,而在于它把Servlet 生命周期、JDBC 连接管理、事务边界、字符集陷阱、前后端协作这些抽象概念,全部钉死在login.jsp→LoginServlet→UserDAO→DBUtil→MySQL这一条可追踪的链路上。你调试一个NullPointerException,就能明白request.getParameter()为什么返回 null;你修复一次404,就搞懂了web.xml和 Tomcat Context Path 的关系。这些不是“知识点”,是你答辩时脱口而出的底气。希望帮到你。
本文还有配套的精品资源,点击获取