简介:这是一套面向计算机专业本科生毕业设计与JavaWeb初学者实战训练的超市管理系统完整源码包,聚焦B/S架构下的业务管理场景,帮助学习者掌握JSP+Servlet+JDBC全栈开发流程及MySQL数据库设计与操作。资源包含68个文件,涵盖19个核心Java源文件、37个编译后Class字节码、4个建库建表SQL脚本(含员工、商品、进货、销售等关键数据表)、2个系统界面截图及必要Jar依赖与Eclipse项目配置文件(.project/.classpath/.prefs),总大小仅3.5MB,轻量易部署。已有797人学习下载,适合快速搭建本地开发环境并调试运行。读者可直接获取可运行的完整系统:含双角色权限(员工/管理员)、八大功能模块(从系统登录、员工与商品管理到进货销售全流程)、结构清晰的MVC分层目录(src下dao/view/pojo/utils等),以及配套数据库脚本与项目说明文档,显著降低毕设启动门槛与调试成本。
1. 这不是“又一个超市系统”:它是一份能跑通、能改、能答辩的 JavaWeb 毕业设计最小可行体
你搜“JavaWeb 超市管理系统 毕业设计”,页面刷出几百个同名 ZIP 包——点开全是 JSP+Servlet+MySQL 的老三件,目录结构雷同,登录页写着“欢迎来到XX超市”,连验证码都用的Random生成四位数字。但真正卡住你的从来不是“有没有源码”,而是:为什么本地 Tomcat 启动后访问 8080 页面空白?为什么 login.jsp 提交后跳转 404?为什么数据库导入了却提示Unknown column 'user_name' in 'field list'?为什么答辩老师问“事务怎么控制的”,你只能翻着代码说“我用了 conn.setAutoCommit(false)”?
这个标题里的.zip文件,本质是一个被压缩过的“毕业设计最小可行体(MVP)”:它不追求高并发、不带微服务、没用 Spring Boot 自动装配,但它必须满足三个硬约束——能在 JDK 8 + Tomcat 8.5 + MySQL 5.7 环境下 10 分钟内跑起来;所有增删改查逻辑可追踪到具体 Servlet 和 SQL;数据库表结构与 Java 实体类字段严格对齐,没有驼峰/下划线错位。它不是工业级系统,而是专为计算机专业本科生设计的“答辩友好型”载体:功能完整(商品/员工/库存/销售单)、边界清晰(无第三方支付/短信/物流集成)、技术栈透明(JSP 前端 + Servlet 控制层 + JDBC 数据层)。如果你正被开题报告 deadline 追着跑,或刚在 IDEA 里配完 Tomcat 却卡在ClassNotFoundException: com.mysql.jdbc.Driver,这篇笔记就是为你写的——我们不讲 MVC 理论,只拆解从解压 ZIP 到点击“结账”按钮弹出成功提示的每一步真实操作。
2. 解压即启动:环境配置与项目结构破译
2.1 环境版本锁死:为什么必须用 JDK 8 + Tomcat 8.5 + MySQL 5.7
这个 ZIP 包的build.xml(Ant 构建脚本)和web.xml中明确声明了 Servlet 3.0 规范,而 Servlet 3.0 是 JDK 8 的默认支持上限。若你强行用 JDK 17 启动,会直接报错:
java.lang.UnsupportedClassVersionError: com/supermarket/dao/UserDao has been compiled by a more recent version of the Java Runtime (class file version 52.0), this version of the Java Runtime only recognizes class file versions up to 51.0提示:错误信息里的
52.0对应 JDK 8,51.0对应 JDK 7——说明编译时用了 JDK 8,但你运行时用了 JDK 7。反过来,JDK 17 编译的 class 文件版本是 61,Tomcat 10+ 才支持,而本项目web.xml的<web-app>根节点声明的是version="3.0",Tomcat 9+ 才兼容。所以版本链必须闭合:JDK 8 → Tomcat 8.5 → MySQL 5.7。
验证方式:
# 检查 JDK 版本(必须输出 1.8.x) java -version # 检查 Tomcat 版本(必须输出 v8.5.x) $TOMCAT_HOME/bin/version.sh # Linux/Mac %TOMCAT_HOME%\bin\version.bat # Windows # 检查 MySQL 版本(必须输出 5.7.x) mysql --version2.2 项目结构解剖:识别src、WebContent、lib三大核心区域
解压 ZIP 后,典型目录结构如下:
supermarket/ ├── build.xml # Ant 构建脚本(非 Maven,勿用 IDEA 自动导入) ├── src/ # Java 源码根目录(注意:不是 Maven 的 src/main/java) │ ├── com/supermarket/dao/ # 数据访问层(UserDao.java, GoodsDao.java) │ ├── com/supermarket/service/ # 业务逻辑层(UserService.java, GoodsService.java) │ └── com/supermarket/servlet/ # 控制层(LoginServlet.java, SaleServlet.java) ├── WebContent/ # Web 资源根目录(等价于传统 WAR 包的 webapp) │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置:Servlet 映射、过滤器、监听器 │ │ └── lib/ # 依赖 JAR 包(mysql-connector-java-5.1.47.jar 必须存在) │ ├── index.jsp # 首页入口 │ ├── login.jsp # 登录页 │ └── admin/ # 后台页面(goods_list.jsp, employee_add.jsp) ├── database/ # 数据库脚本目录 │ └── supermarket.sql # 建库建表+初始数据(UTF8MB4 字符集!) └── README.txt # 关键提示:如默认账号密码、端口配置关键动作:将整个supermarket/目录复制到$TOMCAT_HOME/webapps/下(不要嵌套子文件夹),启动 Tomcat 后访问http://localhost:8080/supermarket/。若看到登录页,说明部署成功;若 404,请检查WebContent/WEB-INF/web.xml中<display-name>是否为supermarket,且webapps/下文件夹名是否与之完全一致(大小写敏感)。
2.3 数据库初始化:supermarket.sql的三处致命细节
database/supermarket.sql不是简单CREATE DATABASE,它包含三个易被忽略的硬编码细节:
字符集强制声明:
CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE supermarket;为什么必须是 utf8mb4?因为商品名称可能含 emoji(如 🍎🍎),MySQL 5.7 默认
utf8实际是utf8mb3,无法存储四字节 Unicode。若漏掉mb4,插入商品时会报Incorrect string value错误。外键约束显式关闭:
SET FOREIGN_KEY_CHECKS = 0; -- 关键!否则建表顺序错导致失败 DROP TABLE IF EXISTS `goods`; CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `price` decimal(10,2) NOT NULL, `stock` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; SET FOREIGN_KEY_CHECKS = 1;现象:若未加
SET FOREIGN_KEY_CHECKS = 0,且employee表引用了department表,但department表建在后面,则employee表创建失败。初始管理员账号明文写死:
INSERT INTO `user` (`username`, `password`, `role`) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'ADMIN');密码
e10adc3949ba59abbe56e057f20f883e是"123456"的 MD5 值(32 位小写)。答辩时务必修改此密码,否则老师用admin/123456直接登录后台,会质疑安全性。
执行命令:
# 进入 MySQL 命令行(确保已创建 supermarket 数据库) mysql -u root -p > source /path/to/supermarket/database/supermarket.sql3. 从登录到结账:核心业务链路的手动调试法
3.1 登录流程逆向追踪:login.jsp→LoginServlet→UserDao
当输入admin/123456点击登录,实际发生以下调用链:
login.jsp表单提交到/login(查看<form action="login" method="post">)web.xml中映射LoginServlet处理/login请求:<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.supermarket.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>LoginServlet.java的doPost()方法:protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); // 获取表单值 String password = request.getParameter("password"); UserDao userDao = new UserDao(); // 实例化 DAO User user = userDao.login(username, MD5Util.md5(password)); // 注意:密码已 MD5 加密 if (user != null) { request.getSession().setAttribute("user", user); // 存入 Session response.sendRedirect("admin/index.jsp"); // 重定向到后台首页 } else { request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); // 返回登录页 } }参数说明:
request.getParameter()获取 HTML 表单字段名;MD5Util.md5()是项目自定义工具类,位于src/com/supermarket/util/MD5Util.java;request.getSession().setAttribute()将用户对象存入 Session,后续页面通过session.getAttribute("user")获取。
3.2 商品入库操作:goods_add.jsp→GoodsServlet→GoodsDao.insert()
添加商品时,goods_add.jsp表单提交到/goods/add,由GoodsServlet的doPost()处理:
// GoodsServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("name"); String priceStr = request.getParameter("price"); String stockStr = request.getParameter("stock"); // 类型转换(必须捕获 NumberFormatException!) BigDecimal price = new BigDecimal(priceStr); int stock = Integer.parseInt(stockStr); Goods goods = new Goods(); goods.setName(name); goods.setPrice(price); goods.setStock(stock); GoodsDao goodsDao = new GoodsDao(); boolean success = goodsDao.insert(goods); // 执行插入 if (success) { request.setAttribute("msg", "商品添加成功!"); request.getRequestDispatcher("goods_list.jsp").forward(request, response); } else { request.setAttribute("msg", "商品添加失败,请重试"); request.getRequestDispatcher("goods_add.jsp").forward(request, response); } }关键点:
BigDecimal用于价格,避免float精度丢失(如0.1 + 0.2 != 0.3);Integer.parseInt()可能抛出NumberFormatException,但原代码未 try-catch——这是答辩时老师最爱问的“异常处理漏洞”;goodsDao.insert()最终执行 SQL:INSERT INTO goods (name, price, stock) VALUES (?, ?, ?),使用PreparedStatement防止 SQL 注入。
3.3 销售单生成:事务控制的实操验证
结账时,需同时更新goods表(扣减库存)和插入sale_record表(记录销售)。SaleServlet.java中的关键代码:
Connection conn = null; try { conn = JDBCUtil.getConnection(); // 获取连接 conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 1. 更新商品库存 String updateSql = "UPDATE goods SET stock = stock - ? WHERE id = ?"; PreparedStatement ps1 = conn.prepareStatement(updateSql); ps1.setInt(1, quantity); // 购买数量 ps1.setInt(2, goodsId); ps1.executeUpdate(); // 2. 插入销售记录 String insertSql = "INSERT INTO sale_record (goods_id, quantity, total_price, sale_time) VALUES (?, ?, ?, NOW())"; PreparedStatement ps2 = conn.prepareStatement(insertSql); ps2.setInt(1, goodsId); ps2.setInt(2, quantity); ps2.setBigDecimal(3, totalPrice); ps2.executeUpdate(); conn.commit(); // 提交事务 request.setAttribute("msg", "结账成功!"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 回滚事务 } catch (SQLException ex) { ex.printStackTrace(); } } request.setAttribute("msg", "结账失败:" + e.getMessage()); } finally { JDBCUtil.close(conn); // 关闭连接 }为什么必须手动 commit/rollback?若只更新库存成功、插入销售记录失败,不回滚会导致库存虚减。此处
conn.setAutoCommit(false)是事务起点,conn.commit()是终点,conn.rollback()是异常兜底——答辩时演示“故意让插入失败”,观察库存是否恢复,即可证明事务生效。
4. 避坑指南:五个让答辩前夜崩溃的真实问题
4.1 现象:Tomcat 启动后访问http://localhost:8080/supermarket/报 404
原因:web.xml中<display-name>值为supermarket,但webapps/下文件夹名是supermarket-master或SuperMarket(大小写不一致)。Tomcat 严格区分大小写,且 WAR 包解压后文件夹名必须与display-name完全一致。
解决:重命名webapps/下文件夹为supermarket(全小写),重启 Tomcat。
4.2 现象:登录页提交后跳转http://localhost:8080/login报 404
原因:login.jsp表单的action属性写成绝对路径/login,但项目上下文路径不是 ROOT。例如,若项目放在webapps/supermarket/,则正确路径应为supermarket/login。
解决:将<form action="/login" ...>改为<form action="login" ...>(相对路径),或使用 JSTL<c:url value="/login"/>。
4.3 现象:goods_list.jsp显示java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
原因:MySQL 5.1+ 驱动类名已从com.mysql.jdbc.Driver变更为com.mysql.cj.jdbc.Driver,但JDBCUtil.java中仍用旧类名,且lib/mysql-connector-java-5.1.47.jar未放入WebContent/WEB-INF/lib/。
解决:
- 确认
WebContent/WEB-INF/lib/下存在mysql-connector-java-5.1.47.jar; - 修改
JDBCUtil.java中Class.forName("com.mysql.cj.jdbc.Driver"); - URL 改为
jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=UTC(添加serverTimezone=UTC防止时区报错)。
4.4 现象:添加商品时中文名称显示为??,数据库中存为乱码
原因:MySQL 服务端字符集未设为utf8mb4,或JDBC URL未指定characterEncoding=utf8mb4。
解决:
- MySQL 配置文件
my.cnf添加:[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci JDBCUtil.java中 URL 改为:jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=UTC&characterEncoding=utf8mb4
4.5 现象:sale_record表插入成功,但goods表库存未减少
原因:SaleServlet.java中conn.setAutoCommit(false)后未执行conn.commit(),或catch块中conn.rollback()未生效(因conn为 null)。
解决:
- 检查
conn.commit()是否在try块末尾; - 确保
JDBCUtil.getConnection()返回的conn不为 null; - 在
finally块前添加日志:System.out.println("Transaction committed");验证执行路径。
5. 答辩加分项:三步让老师眼前一亮的定制化改造
5.1 第一步:给登录页加验证码(5 分钟实现防暴力破解)
原系统无验证码,易被脚本爆破。只需三步:
- 生成验证码图片:在
src/com/supermarket/util/VerifyCodeUtil.java中添加:public static String generateVerifyCode(HttpServletRequest request) { String code = RandomStringUtils.randomNumeric(4); // 生成4位数字 request.getSession().setAttribute("verifyCode", code); // 存入 Session return code; } - 添加验证码图片 Servlet:新建
VerifyCodeServlet.java:
并在protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String code = VerifyCodeUtil.generateVerifyCode(request); BufferedImage image = new BufferedImage(80, 30, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, 80, 30); g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 20)); g.drawString(code, 10, 25); response.setContentType("image/png"); ImageIO.write(image, "png", response.getOutputStream()); }web.xml中映射/verifyCode。 - 修改
login.jsp:
在<img src="verifyCode" onclick="this.src='verifyCode?'+Math.random()" alt="验证码"/> <input type="text" name="verifyCode" placeholder="请输入验证码"/>LoginServlet中增加校验:String inputCode = request.getParameter("verifyCode"); String sessionCode = (String) request.getSession().getAttribute("verifyCode"); if (!inputCode.equalsIgnoreCase(sessionCode)) { request.setAttribute("msg", "验证码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); return; }答辩话术:“老师,我增加了图形验证码,防止暴力破解。点击图片可刷新,且验证码区分大小写,提升了基础安全。”
5.2 第二步:销售单导出 Excel(用 Apache POI 实现)
老师常问“数据怎么导出分析?”,原系统无此功能。添加ExportSaleServlet.java:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<SaleRecord> records = new SaleRecordDao().findAll(); // 查询全部销售记录 HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("销售记录"); // 表头 HSSFRow headerRow = sheet.createRow(0); String[] headers = {"ID", "商品ID", "数量", "总价", "时间"}; for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 for (int i = 0; i < records.size(); i++) { SaleRecord r = records.get(i); HSSFRow row = sheet.createRow(i + 1); row.createCell(0).setCellValue(r.getId()); row.createCell(1).setCellValue(r.getGoodsId()); row.createCell(2).setCellValue(r.getQuantity()); row.createCell(3).setCellValue(r.getTotalPrice().doubleValue()); row.createCell(4).setCellValue(r.getSaleTime().toString()); } response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment; filename=sales.xls"); workbook.write(response.getOutputStream()); }依赖添加:下载poi-3.17.jar放入WebContent/WEB-INF/lib/。
价值点:导出功能体现数据闭环能力,且 POI 是 JavaWeb 常用工具,展示技术广度。
5.3 第三步:用 Filter 统一处理中文乱码(一劳永逸)
每次request.setCharacterEncoding("UTF-8")太重复。新建EncodingFilter.java:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); chain.doFilter(req, resp); }并在web.xml中配置:
<filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.supermarket.filter.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>玄学经验:Filter 必须在
web.xml中置于所有 Servlet 映射之前,否则无效。这是血泪教训——曾因位置放错,导致goods_add.jsp提交的中文始终乱码。
最后想说:这个超市系统不是要你造轮子,而是让你亲手拧紧每一颗螺丝。我当年答辩时,老师指着销售单页面问:“如果同时两人结账同一商品,库存会超卖吗?” 我当场打开SaleServlet,指出conn.setAutoCommit(false)和synchronized未加——然后快速补上synchronized块,并解释“高并发需 Redis 锁,但本设计聚焦事务一致性”。老师笑了,说:“这才是工程思维。” 希望帮到你。
本文还有配套的精品资源,点击获取