
简介这是一份基于 JavaWeb 的超市管理系统毕业设计资源包含完整可运行源码与数据库脚本面向计算机、通信、人工智能、自动化等专业的学生、老师及从业者适用于期末课程设计、课程大作业或毕业设计等场景也适合 Java 初学者进阶学习。系统覆盖商品管理、会员管理、供应商管理、结算等典型业务模块代码结构清晰分层设计便于二次开发。资源包为 zip 格式共 386 个文件大小约 10.36MB核心文件包括 49 个 Java 源文件、56 个 JSP 页面、98 个 class 编译文件及 SQL 数据库脚本另有前端 CSS、Less、JS 与图片资源可帮助读者快速理解前后端交互与持久层实现。项目为个人毕设答辩评审分达 98 分代码均经过调试测试可直接运行。已有 156 人学习下载基础较强的读者可在此基础上修改调整实现不同功能整体具有较高的学习借鉴价值。1. JavaWeb毕设选超市管理系统为什么这个题最不容易翻车毕设季换题是常事很多人拿到“基于JavaWeb的超市管理系统设计与实现含数据库和源码”这种题目第一反应是太老。但老题有老题的好处需求边界清楚进销存三件事展开讲不虚四五张表就能撑起整套ServletJSPJDBC的考察点代码量刚好够一个学期做完比社团管理系统多一层库存和订单的关联又远比电商系统好实现。这篇笔记就按这个标题来拆数据库怎么设计、源码怎么在IDEA里跑起来、哪些代码必须自己改一遍、翻车点在哪以及答辩时怎么把“能跑”讲成“会做”。适合正在选型或已经拿到类似题目的同学。2. 先把超市业务拆成一张ER图五张表撑起整个系统2.1 从进销存场景到模块划分系统到底要管哪些事超市管理系统核心业务围绕“商品流转”四个字。常见做法是把功能拆成四个模块基础信息商品、供应商、库存管理入库、出库、盘点、销售管理收银、订单、系统管理用户登录、权限。前端页面一般用JSPJSTL后端Servlet处理请求数据库用MySQL。一套标准的JavaWeb增删改查项目就是这么搭起来的黑马JavaWeb笔记里那套“ServletServiceDao”的分层在这个题目里正好够用也正好能被答辩老师接受。业务流是这条管理员登录后先维护商品和供应商然后进货入库、前台开单销售每一笔销售同时扣减库存所有单据落库供后续查询。谁进货、谁卖货、卖了多少一张订单头表加一张订单明细表就能覆盖。不需要做会员、促销、盘点盈亏分析那些是加分项不是必选项先把主链路跑通再谈加功能。2.2 四张核心表直接建出来SQL脚本与字段取舍先建数据库再建表。字符集统一用utf8mb4排序规则utf8mb4_general_ci就够。下面是这个系统最核心的五张表包含用户、商品、供应商、订单主表和订单明细基本覆盖毕业设计要求的全部增删改查场景。CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4; USE supermarket; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 登录密码, real_name VARCHAR(50) COMMENT 真实姓名, role TINYINT DEFAULT 1 COMMENT 1-收银员 2-管理员 ) ENGINEInnoDB COMMENT系统用户表; CREATE TABLE t_goods ( id INT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(32) UNIQUE COMMENT 条形码, name VARCHAR(128) NOT NULL COMMENT 商品名称, category VARCHAR(50) COMMENT 商品分类, price DECIMAL(10,2) NOT NULL COMMENT 销售单价, stock INT DEFAULT 0 COMMENT 当前库存 ) ENGINEInnoDB COMMENT商品表; CREATE TABLE t_supplier ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL COMMENT 供应商名称, contact VARCHAR(50) COMMENT 联系人, phone VARCHAR(20) COMMENT 联系电话 ) ENGINEInnoDB COMMENT供应商表; CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE COMMENT 订单编号, user_id INT NOT NULL COMMENT 操作员ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, order_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINEInnoDB COMMENT订单主表; CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT 所属订单ID, goods_id INT NOT NULL COMMENT 商品ID, quantity INT NOT NULL COMMENT 购买数量, price DECIMAL(10,2) NOT NULL COMMENT 成交单价, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES t_order(id), CONSTRAINT fk_item_goods FOREIGN KEY (goods_id) REFERENCES t_goods(id) ) ENGINEInnoDB COMMENT订单明细表;SQL脚本执行注意三点第一ENGINEInnoDB必须显式写后面库存扣减要依赖事务MyISAM不支持事务这个是踩坑点第二外键约束在毕设里建议只保留用户、订单、明细这层关系商品和供应商之间不要过分关联否则后面删除商品时外键报错会让人抓狂第三价格字段用DECIMAL(10,2)而不用FLOAT浮点算金额会把0.10.2这种基础问题带到系统里答辩演示时一旦出现金额对不上场面很难收。2.3 为什么订单明细要冗余一份商品价格订单明细表里存一个price字段看起来是重复设计因为商品表已经有价格了但实际上这是系统设计里的关键决策。商品价格会变动今天可乐卖3块明天促销卖2块5如果订单明细不做价格快照将来查历史订单时商品价格已经被改掉那这张订单的总金额就永远对不上了。这个现象在答辩时经常被问到回答“价格快照防止历史单据被商品调价影响”是标准答案。同样的逻辑也用在订单的order_no上。自增ID在页面上直接暴露给用户和报表都没问题但超市系统的订单号一般会拼上时间戳比如ORD202406011530001这种格式。好处是看起来专业更重要的是后续按时间范围对账时可以直接通过订单号前缀过滤。这个字段在代码里生成即可不用在SQL里做复杂规则。3. 在IDEA里把源码工程跑起来JDK、Tomcat与数据库一锅端3.1 环境版本怎么配这套组合最稳换一个就翻车拿到源码第一件事不是急着导入IDEA而是先核对环境。很多翻车现场都是版本错配导致的最常见的是Tomcat 10配老代码JDK版本对不上导致javax.servlet包导入失败。我一般会用这套固定组合JDK 8、Tomcat 8.5或9.0、MySQL 5.7或8.0。IDEA版本随意2021版以后都能跑。Tomcat 10以上把javax.servlet换成了jakarta.servlet老毕设源码里所有import javax.servlet的类全部编译不过。如果你只有Tomcat 10可以对源码做全局替换但更省事的做法是去Tomcat官网下8.5版本文件不大解压即用。MySQL这边5.7和8.0都行但驱动类和连接URL写法不一样这个坑放在第5章细讲。3.2 IDEA配置Tomcat部署war包还是war exploded源码导入IDEA后先确认它是普通Web项目还是Maven项目。普通项目在WEB-INF/lib下放了一堆jar包Maven项目则是在pom.xml里声明依赖。不管哪种最终部署到Tomcat的方式相同。在IDEA中打开Run菜单下的Edit Configurations点加号选择Tomcat ServerLocalApplication server一栏选到你本地Tomcat目录。然后切到Deployment标签点加号选Artifact这里注意调试阶段一定要选带“exploded”后缀的那个比如supermarket:war exploded热部署和断点调试都方便选完整war包每次改代码都要重新打包非常耽误事。最后在Server标签里把On Update Action设为Update resources这样改JSP后不用重启Tomcat刷新页面就能看到效果。启动前把Application context设成/supermarket浏览器访问地址就是http://localhost:8080/supermarket/。这个路径不设统一规则的话页面里所有hreflogin这种相对路径都会对不上等于全部跳转头重来一次。我见过不少同学在这上面卡了一下午明明启动日志显示成功页面就是404。3.3 数据库连接配置连接的参数都藏在这个文件里源码里一定会有一个数据库配置文件最常见的名字是db.properties或jdbc.properties放在src目录下。它的内容长这样jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里四个参数逐个说。driver在MySQL 5.7用com.mysql.jdbc.Driver也行但MySQL 8.0必须用com.mysql.cj.jdbc.Driver新旧驱动类名不一样写错会直接ClassNotFoundException。url里的useUnicode和characterEncoding必须带上不然中文全部变成问号填入数据库。serverTimezoneAsia/Shanghai是MySQL 8.0新增的必填项不加会报时区错误。username和password改成自己本机的MySQL账号。导入数据库这一步直接用Navicat或命令行都行。命令行操作是打开MySQL客户端执行source D:/supermarket.sql;或者手动建库后复制粘贴SQL脚本。注意导入前确认当前选中的库是supermarket不然表建到别的库下面代码连对了库却找不到表报错信息还会误导你去查SQL语法兜一大圈。数据库连接池在这个项目里属于可选优化。毕设源码一般直接用DriverManager.getConnection建立连接每次请求都新建连接、用完关闭演示没问题。如果你想在答辩里多说一个亮点可以把连接改成Druid连接池配置量不大但效果明显能讲出“数据库连接复用”这个考点。这里给出最小配置Resource namejdbc/supermarketDB authContainer typejavax.sql.DataSource driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/supermarket?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai usernameroot password123456 maxTotal20 maxIdle10 maxWaitMillis10000/这段XML放在Tomcat的conf/context.xml里然后在Java代码中用DataSource ds (DataSource) ctx.lookup(jdbc/supermarketDB)取连接。讲一下参数maxTotal代表最大连接数20够用maxIdle是空闲连接数10就行maxWaitMillis是拿连接的超时时间设10秒超过就报错防止数据库卡死时请求无限堆积。改完记得重启Tomcat连接池的参数变更不会热生效。4. 有三处代码必须自己改一遍登录、库存与分页查询4.1 登录逻辑别只查一次数据库把Session和密码处理一起补上源码里的登录Servlet通常是最粗糙的部分很多直接拼接SQL查一次数据库就放行。这个功能虽然简单但它是答辩高频考点值得改成更稳的写法。下面是一个常见做法的核心逻辑WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(error, 用户名或密码错误请重试); req.getRequestDispatcher(login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); resp.sendRedirect(req.getContextPath() /index.jsp); } }说明几个关键点。WebServlet(/login)注解方式注册Servlet省掉了web.xml里的配置这是Servlet 3.0以后的标准写法毕业设计里能体现你学过新特性。findByUsernameAndPassword底层用的是PreparedStatement而不是Statement拼接字符串这是防御SQL注入的基本功代码评审时老师会盯这一处。session.setMaxInactiveInterval(30 * 60)把会话超时设为30分钟意思是用户半小时不操作必须重新登录这是Web系统的基本安全规范加一行注释就能变成答辩亮点。逻辑顺序也要注意先设Session再重定向顺序不能反。如果先sendRedirect再setAttribute跳转后的页面拿不到登录用户表现成“登录成功了但页面还是未登录状态”这是个很容易被误判为Tomcat缓存问题的假象。4.2 库存扣减事务和行锁一起上防止一次开单把库存打成负数库存扣减是这个项目里最有技术含量的点也是答辩老师最爱问的点。一个典型的错误代码是先SELECT stock FROM t_goods WHERE id1查库存如果stock 购买数量再UPDATE扣减。单用户演示没问题但多线程并发下两个窗口同时卖最后一件商品两个请求都查到库存为1都执行扣减库存就变负了。这就是超卖问题。解决办法是事务加行级锁把“查库存”和“扣库存”放进同一个事务里查询时加上FOR UPDATE锁住这行记录public boolean deductStock(int goodsId, int quantity) { String sql1 SELECT stock FROM t_goods WHERE id? FOR UPDATE; String sql2 UPDATE t_goods SET stockstock-? WHERE id? AND stock?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); PreparedStatement ps1 conn.prepareStatement(sql1); ps1.setInt(1, goodsId); ResultSet rs ps1.executeQuery(); int stock 0; if (rs.next()) { stock rs.getInt(stock); } if (stock quantity) { return false; // 库存不足触发回滚 } PreparedStatement ps2 conn.prepareStatement(sql2); ps2.setInt(1, quantity); ps2.setInt(2, goodsId); ps2.setInt(3, quantity); int rows ps2.executeUpdate(); if (rows 0) { conn.rollback(); return false; } conn.commit(); return true; } catch (SQLException e) { DBUtil.rollback(conn); // 工具类里封装的回滚方法 throw new RuntimeException(库存扣减失败, e); } finally { DBUtil.close(conn); } }这个写法的核心有三层。conn.setAutoCommit(false)手动开启事务确保查询和更新要么都成功要么都回滚不会出现查了库存但没扣减成功的中间状态。FOR UPDATE是MySQL InnoDB引擎提供行级悲观锁事务里执行这条语句后该商品行被锁住其他事务对同一行的扣减操作会阻塞等待直到当前事务提交这一步直接挡住并发超卖。UPDATE语句里带上AND stock?是二次防线即使锁没生效更新影响行数为0也会触发回滚。接口返回布尔值而不是直接抛异常是为了让上层的OrderService能根据结果提示“库存不足”。这个设计层级关系清晰答辩时可以说“Service层负责事务边界Servlet只做参数校验和视图转发”一句话体现出分层意识。4.3 模糊查询与分页从LIKE拼接改成预处理顺手把LIMIT参数化商品搜索是超市管理系统的门面功能几乎每个页面都要用。源码里常见的写法是String sql SELECT * FROM t_goods WHERE name LIKE % keyword %这种字符串拼接在演示时好用但有两个问题一是keyword里带单引号或%会破坏SQL结构在答辩现场输入一个o字符直接报错场面相当难看二是没有分页商品一多页面卡顿。改造方案是统一使用PreparedStatement并加LIMIT分页public ListGoods searchGoods(String keyword, int pageNo, int pageSize) { String sql SELECT id, barcode, name, category, price, stock FROM t_goods WHERE name LIKE ? OR barcode LIKE ? ORDER BY id DESC LIMIT ?, ?; ListGoods list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String pattern % keyword %; ps.setString(1, pattern); ps.setString(2, pattern); ps.setInt(3, (pageNo - 1) * pageSize); ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Goods g new Goods(); g.setId(rs.getInt(id)); g.setBarcode(rs.getString(barcode)); g.setName(rs.getString(name)); g.setCategory(rs.getString(category)); g.setPrice(rs.getBigDecimal(price)); g.setStock(rs.getInt(stock)); list.add(g); } } } catch (SQLException e) { throw new RuntimeException(商品搜索失败, e); } return list; }这里有两个细节值得展开。第一个是LIKE ? OR barcode LIKE ?参数直接绑定%符号拼在参数里而不是SQL里避免用户输入的关键字把查询条件改掉。第二个是分页偏移量(pageNo - 1) * pageSize的计算放到了Java端而不是在SQL里写死这样页面传来的pageNo1、pageNo2都能正确对应到LIMIT 0, 10和LIMIT 10, 10。这里要注意一个边界MySQL的LIMIT不支持负数和字符串参数如果pageNo小于1(pageNo - 1) * pageSize会是负数SQL直接报错。Servlet层需要先校验pageNo 1非法值一律重置为1。这个校验虽然小但能避免用户手工修改URL参数把系统打挂属于安全边界问题答辩时可以主动讲出来。5. 跑毕设项目最容易翻车的5个位置现象、原因与解决实录5.1 404IDEA里Tomcat启动成功访问页面却报404现象是启动日志刷了一堆Deployment of web application archive成功信息Tomcat也没报错但浏览器打开http://localhost:8080/supermarket/就是404。原因几乎只有一个IDEA的Deployment标签里没有绑定Artifact或者绑定了但Application context设成了/。前者导致Tomcat根本没加载这个Web应用后者则导致页面路径对不上真实部署路径。解决方法是打开Run菜单下的Edit Configurations找到Tomcat配置切到Deployment页签点加号重新选择Artifact并确认Application context填的是/supermarket。改完重启再看启动日志里有没有出现Deploying web application archive这行字有才算部署成功。5.2 中文乱码页面、数据库、控制台四处各乱各的现象是登录后菜单栏显示“系统”这种乱码数据库里存的商品名变成问号控制台输出更是没法看。这套乱码是三层各乱各的。页面请求到Servlet传参乱码是因为JSP页面编码和Tomcat接收参数的编码不一致需保证JSP文件头声明pageEncodingUTF-8同时在Servlet里对请求做req.setCharacterEncoding(UTF-8)统一在Filter里做更省事。数据库里的中文变成问号是JDBC连接URL没带characterEncodingutf8参数连到数据库的字符集不是UTF-8。控制台乱码则是IDEA的-Dfile.encodingUTF-8参数没加到Tomcat启动配置里。解决方式分两步走。第一步在web.xml里配一个字符编码Filter拦截所有请求强制设UTF-8这是治根第二步在IDEA的Tomcat配置里加VM options-Dfile.encodingUTF-8。都配好以后新建数据库连接时也用utf8mb4字符集基本一次解决。5.3 MySQL 8连不上Access denied和服务器时区报错现象是启动Tomcat后访问登录页点登录按钮提示Access denied for user rootlocalhost或The server time zone value is unrecognized。Access denied首先检查密码是否正确本地开发环境经常是root账号密码和配置文件里对不上。如果密码确认没错则要检查MySQL 8的默认认证插件是不是caching_sha2_password而驱动版本太老不支持。解决办法是执行一条SQLALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;把认证方式改回去或者升级mysql-connector-java驱动到8.x。时区报错是纯参数问题在JDBC连接URL后面追加serverTimezoneAsia/Shanghai注意Asia/Shanghai不要拼错拼成Asia/Shangai的话等于没加。5.4 商品被卖成负数日志里没扣减记录库存还越卖越多现象是前台开单销售后库存不减反增或者出现-2、-3这种负数。查数据库操作日志发现买进有记录卖出没有记录。原因是销售Servlet里只执行了INSERT INTO t_order和INSERT INTO t_order_item忘记调用deductStock方法。这在很多源码里是“正常”情况因为题目只要求完成“管理系统”入库和出库是拆开做的入库改库存销售单只管收银两件事没有联动。这种设计在功能层面不算错但答辩一旦被问到“库存模块怎么保证数据一致”答不上来就会很被动。解决方式是把销售开单和库存扣减放进同一个事务顺序是插入订单主表拿到订单ID插入订单明细表调用deductStock扣减库存最后统一提交。任何一步失败整个回滚订单不会残留半截数据。5.5 导出war包部署后JSP里的图片全部裂开现象是IDEA里直接跑一切正常但从target目录里拷出war包放到Tomcat的webapps下启动后登录页图片全部裂开CSS样式也丢了。原因是图片和CSS引用了绝对路径比如src/images/logo.pngIDEA里项目根路径就是/所以能正常访问部署成war后应用挂载在/supermarket下/images/logo.png直接指向了Tomcat根目录当然找不到文件。解决方式是所有静态资源引用改成相对路径或者统一在页面里用${pageContext.request.contextPath}拼接比如src${pageContext.request.contextPath}/images/logo.png。这是JavaWeb项目的经典坑问题不在Tomcat在于路径写法没有考虑应用的上下文路径。6. 能跑只是及格答辩现场的三分钟验证脚本与自查表6.1 演示脚本按这条链路走五分钟讲完整个系统答辩演示最怕的是临时乱点页面跳来跳去老师看得一头雾水。我一般会固定一条演示链路提前走三遍确保每一步都有数据变化可看。链路这样设计先用管理员账号登录演示数字校验和错误密码提示进商品管理页搜索一个关键字演示模糊查询和分页跳转新建一个商品去数据库查t_goods多了一行做一笔销售单马上去Navicat里看订单表和库存字段的变化最后演示退出登录后直接访问后台页面会被拦截回登录页。这其中最具说服力的是“销售后去Navicat看库存变化”这一步直接把应用程序和数据库的关联展示出来。配合一条SQL讲透整个事务效果更好SELECT g.id, g.name, g.stock, o.order_no, oi.quantity FROM t_goods g LEFT JOIN t_order_item oi ON g.id oi.goods_id LEFT JOIN t_order o ON oi.order_id o.id WHERE o.order_no ORD202406011530001;这条SQL把商品表、订单明细表、订单主表三张表连接起来回答问题“这张订单买了什么、买了多少、现在还剩多少库存”。如果你在答辩现场能当场敲出这条SQL并解释清楚连接关系比任何PPT都管用。6.2 自查表答辩前夜按这十项过一遍最后一晚别改功能按这张表过一遍就够了。每一项都是真实环境下验证过的坑列为一项即可不用临时加功能。检查项操作方式通过标准登录失败提示故意输错密码页面有明确错误文案不跳首页会话过期等30分钟或清Cookie访问后台被拦截回登录页商品新增新增一条含中文名称的数据数据库中文正常显示模糊搜索搜索商品名半个字返回匹配结果不报SQL错库存扣减销售后去Navicat查库存库存减少且订单金额正确库存不足买超过库存的数量有“库存不足”提示订单不生成分页边界翻到最后一页删除所有数据页码不报负数错误路径静态资源登录页刷新图片和样式正常加载乱码新增和编辑各走一遍页面和数据库都是中文退出登录退出后按浏览器后退不能通过后退进入后台页面做完这张表你就能对答辩“系统还有没有Bug”这类问题有个底。记得把你修复过的问题整理成一个文档答辩开场顺带提一句“这个系统在实现过程中修复过库存并发、SQL注入、中文编码三类问题”老师的注意力就全被吸引到你能讲清楚的地方去了。我自己带毕设这几年最深的一个教训是源码能跑起来不等于你能毕业数据库设计才是答辩的灵魂。把每张表为什么建、每个字段怎么来的讲清楚比强调自己写了多少行代码更有说服力。按上面这六章把系统过一遍你会发现自己已经把“设计与实现”这四个字吃透了。希望帮到你。本文还有配套的精品资源点击获取