简介本资源为基于SpringBoot的小型超市管理系统毕业设计全套资料面向计算机专业学生及Java初学者解决零售场景下数字化管理断层、供应链信息孤岛与动态决策支持不足等问题。系统采用B/S架构集成商品分类管理、采购流程优化与销售数据分析等核心模块前后端通过HTML、CSS、JavaScript与后端交互提升易用性与响应速度。压缩包共402个文件约13.18MB以158个Java源码、58个Vue组件、30个JavaScript脚本、25个XML配置及45张JPG、36张PNG界面截图为主另含SQL建库脚本、CSS样式、JSON数据与项目说明文档结构完整便于二次开发。已有147人学习下载。读者可获得完整可运行的代码工程、数据库脚本与LW文档通过销售统计与经营报表模块理解决策支持逻辑适合作为课程设计或毕业设计参考快速掌握SpringBoot与MySQL整合开发流程。1. 一个超市老板的账本为什么值得用 Spring Boot 重写很多小型超市的日常运转靠的是一本手写台账加一台老收银机。老板白天盯货、晚上对账最怕两件事一是库存对不上二是月底盘不清。这个标题要解决的就是把「进货、销售、库存、会员、员工」这几本账从纸面搬到一套能跑起来的 Web 系统里。技术选型上Spring Boot 负责把后端接口和业务逻辑快速搭起来MySQL 负责把商品、订单、库存这些结构化数据存住前端用最朴素的页面把操作暴露给收银员和管理员。适合谁看适合正在做课程设计、毕业设计或者想给身边小超市做一套轻量管理工具的开发者。它不追求高并发追求的是「一个人能维护、一台普通电脑能跑、数据不丢」。2. 需求拆解与技术选型为什么是 Spring Boot MySQL2.1 小型超市的业务边界到底在哪先把「小型」两个字钉死。小型超市通常指单店经营、SKU 在几百到两三千之间、收银台一到两个、没有连锁调拨需求。它的核心业务只有四条线商品管理、库存管理、销售收银、基础报表。会员和员工管理属于加分项不是第一版必须做的。我一般会先把业务画成三张表的关系商品表是主数据库存表跟着商品走销售订单表在收银时产生并反向扣减库存。会员表独立存在订单表里存一个会员 ID 做关联即可。这样拆的好处是后面加促销、加退货都只是在订单表上做扩展不会把商品表搞脏。提示不要一上来就设计「供应商、采购、退货、促销、积分」全套。第一版把商品、库存、订单跑通比堆二十张空表有价值得多。2.2 为什么选 Spring Boot 而不是别的后端框架Spring Boot 在这个场景里的优势很具体起步依赖把 Web、JDBC、模板引擎一次性拉齐不用自己配 web.xml内嵌 Tomcat 让打包出来的 jar 直接java -jar就能跑省掉单独装容器的步骤application.properties里改几行就能切换数据库连接。对比一下常见选择纯 Servlet 写起来太碎每个请求都要自己处理参数和转发SSM 配置量大对只想做一个小系统的人来说心智负担重Node 或 Python 方案不是不行但很多课程设计和答辩环境默认认 Java 技术栈Spring Boot 的兼容性最好。数据库选 MySQL 的理由更直接免费、资料多、mysqldump备份一条命令搞定、图形化工具遍地都是。小型超市的数据量MySQL 单机绰绰有余。2.3 项目分层与目录结构我习惯用 controller / service / mapper / entity 四层配合 resources 下的 mapper XML 和静态页面。目录大致长这样supermarket/ ├── src/main/java/com/example/supermarket/ │ ├── controller/ # 接收请求做参数校验 │ ├── service/ # 业务逻辑事务在这里 │ ├── mapper/ # 数据库访问接口 │ └── entity/ # 与表对应的实体类 ├── src/main/resources/ │ ├── mapper/ # MyBatis 的 XML 映射文件 │ ├── static/ # 前端页面与静态资源 │ └── application.properties └── pom.xml分层的意义在于收银逻辑扣库存、生成订单必须放在 service 层并加事务不能写在 controller 里。否则一旦扣库存成功、生成订单失败账就乱了。这是小型系统最容易翻车的地方。2.4 依赖清单与版本选择思路pom.xml里我一般只留必要的几个依赖避免版本冲突dependencies !-- Web 能力内嵌 Tomcat Spring MVC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库访问MyBatis 比 JPA 更贴近手写 SQL适合报表类查询 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 连接池HikariCP 是 Spring Boot 默认无需额外配置 -- /dependencies参数说明MyBatis 的版本要和你用的 Spring Boot 大版本匹配2.x 系列对应 Spring Boot 2.x 和 3.x 都能用但 3.x 项目建议用更新的 starter。MySQL 驱动从 8.0 开始包名是com.mysql.cj.jdbc.Driver连接串里要带时区和字符集参数否则中文商品名会变问号。3. 数据库设计五张核心表怎么落地3.1 商品表与库存表的分工商品表存「是什么」库存表存「有多少」。分开的理由是库存会频繁变动商品信息很少变分开后库存表的更新不会锁住商品表的读。CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(32) NOT NULL UNIQUE COMMENT 条形码收银扫码用, name VARCHAR(64) NOT NULL COMMENT 商品名, category VARCHAR(32) COMMENT 分类, price DECIMAL(10,2) NOT NULL COMMENT 售价, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock ( product_id BIGINT PRIMARY KEY, quantity INT NOT NULL DEFAULT 0 COMMENT 当前库存, warn_line INT DEFAULT 10 COMMENT 预警线, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明DECIMAL(10,2)存金额不要用 float否则 0.10.2 会出现精度误差对账时能让你怀疑人生。utf8mb4是为了支持 emoji 和生僻字商品名。库存表用product_id做主键保证一个商品只有一条库存记录。3.2 订单表与订单明细表的一对多订单表存「这一单的总信息」明细表存「这一单买了哪些商品」。这是典型的一对多也是数据库多对多关系的落地方式。CREATE TABLE sale_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号, member_id BIGINT COMMENT 会员ID可空, total_amount DECIMAL(10,2) NOT NULL, pay_type TINYINT COMMENT 1现金 2扫码, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 成交单价防止商品调价后历史订单失真, INDEX idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明明细表里冗余存一个price是因为商品售价会变历史订单必须保留成交时的价格。idx_order索引是为了按订单查明细时不用全表扫。订单号order_no建议用「日期随机数」生成不要用自增 ID 直接暴露给用户。3.3 会员表与员工表的最小字段集CREATE TABLE member ( id BIGINT PRIMARY KEY AUTO_INCREMENT, phone VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(32), points INT DEFAULT 0 COMMENT 积分, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE staff ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 存哈希不存明文, role TINYINT DEFAULT 1 COMMENT 1收银员 2管理员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明密码字段长度给 64是为了存 SHA-256 或 BCrypt 的结果。千万不要存明文这是最基础的安全底线。员工表用role区分权限收银员只能进收银页管理员才能改商品和看报表。3.4 连接配置与初始化脚本application.properties里几个关键项spring.datasource.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver mybatis.mapper-locationsclasspath:mapper/*.xml mybatis.configuration.map-underscore-to-camel-casetrue参数说明serverTimezone必须设否则连接 MySQL 8 会报时区错误。map-underscore-to-camel-casetrue让数据库的product_id自动映射到 Java 的productId省掉大量手动映射。初始化时把建表 SQL 放在schema.sql里Spring Boot 启动会自动执行方便反复重建测试环境。4. 核心功能实现从收银到库存扣减4.1 商品查询与分页接口商品列表要支持按名称模糊查、按分类筛、分页。MyBatis 的 XML 写法select idlistProducts resultTypecom.example.supermarket.entity.Product SELECT id, barcode, name, category, price, status FROM product where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testcategory ! null and category ! AND category #{category} /if /where ORDER BY id DESC LIMIT #{offset}, #{size} /select逻辑说明where标签会自动处理第一个条件前的 AND避免拼接出WHERE AND的语法错误。CONCAT(%, #{keyword}, %)用#{}而不是${}能防止 SQL 注入。offset和size由 service 层根据页码算出来比如第 2 页每页 10 条offset 就是 10。4.2 收银下单与库存扣减的事务处理这是整个系统最不能出错的地方。收银动作包含三步生成订单、写明细、扣库存。三步必须在一个事务里任何一步失败全部回滚。Service public class SaleService { Autowired private SaleOrderMapper orderMapper; Autowired private OrderItemMapper itemMapper; Autowired private StockMapper stockMapper; Transactional(rollbackFor Exception.class) public String checkout(ListCartItem items, Long memberId) { // 1. 先校验库存避免扣减到一半才发现不够 for (CartItem item : items) { Stock stock stockMapper.selectByProductId(item.getProductId()); if (stock null || stock.getQuantity() item.getQuantity()) { throw new RuntimeException(库存不足 item.getProductId()); } } // 2. 生成订单主记录 SaleOrder order new SaleOrder(); order.setOrderNo(generateOrderNo()); order.setMemberId(memberId); order.setTotalAmount(calcTotal(items)); orderMapper.insert(order); // 3. 写明细并扣库存 for (CartItem item : items) { OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setProductId(item.getProductId()); oi.setQuantity(item.getQuantity()); oi.setPrice(item.getPrice()); itemMapper.insert(oi); stockMapper.decrease(item.getProductId(), item.getQuantity()); } return order.getOrderNo(); } }逻辑说明Transactional(rollbackFor Exception.class)里的rollbackFor很关键默认只回滚运行时异常加上它之后受检异常也会回滚。先校验再扣减是为了在并发不高的小型超市场景下用最简单的方式避免超卖。如果以后并发上来了再改成UPDATE stock SET quantity quantity - #{qty} WHERE product_id #{id} AND quantity #{qty}靠数据库行锁保证原子性。4.3 库存预警与低库存查询库存预警不需要定时任务收银扣减后顺手查一下就行或者管理员进首页时查一次。SELECT p.name, s.quantity, s.warn_line FROM stock s JOIN product p ON p.id s.product_id WHERE s.quantity s.warn_line ORDER BY s.quantity ASC;逻辑说明用 JOIN 把商品名带出来前端直接展示。quantity warn_line是预警条件warn_line默认 10管理员可以按商品单独调。这个查询走的是 stock 表全扫但库存表通常只有几千行性能没问题。4.4 销售报表的按日汇总老板最想看的是「今天卖了多少钱、哪些货走得快」。SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(total_amount) AS total FROM sale_order WHERE create_time #{start} AND create_time #{end} GROUP BY DATE(create_time) ORDER BY day DESC;参数说明start和end用日期字符串传入end取次日零点这样能覆盖当天全部订单。DATE(create_time)会用到函数数据量大时索引失效但小型超市一天几百单完全够用。如果以后要优化可以加一个sale_date冗余字段并建索引。5. 避坑与排查那些让我返工三次的问题5.1 中文乱码从连接串到建表字符集现象商品名存进去变成问号或者页面显示乱码。原因三个地方任意一处字符集不对都会出问题——数据库连接串没带characterEncodingutf8、建表时用了latin1、前端页面没声明 UTF-8。解决连接串加useUnicodetruecharacterEncodingutf8建表统一DEFAULT CHARSETutf8mb4HTML 头部加meta charsetUTF-8。三处对齐后基本不会再犯。5.2 事务不生效方法内部调用踩的坑现象收银时库存扣了但订单没生成数据对不上。原因Transactional标注的方法被同一个类里的另一个方法直接调用Spring 的代理没拦住事务根本没开。解决把事务方法抽到独立的 service 类里或者通过注入自身代理调用。我一般直接把收银逻辑单独放一个SaleServicecontroller 只调它不绕弯。5.3 库存超卖并发下的经典问题现象两个收银台同时卖最后一件商品结果库存变成 -1。原因先查后扣的写法在并发下不安全两个线程都查到库存为 1都执行扣减。解决把扣减改成带条件的原子更新UPDATE stock SET quantity quantity - #{qty} WHERE product_id #{id} AND quantity #{qty}然后判断影响行数为 0 就抛异常回滚。这是最省事的方案不用引入分布式锁。5.4 订单号重复别用时间戳裸奔现象同一秒内两笔订单订单号撞了插入报唯一键冲突。原因直接用System.currentTimeMillis()做订单号毫秒级并发会重复。解决订单号用「yyyyMMddHHmmss 4 位随机数」或者用数据库自增 ID 拼前缀。更稳妥的是单独建一张序列表但小型系统用随机数足够。5.5 连接池耗尽忘了关流和长事务现象系统跑一段时间后报「无法获取数据库连接」。原因手写 JDBC 时忘了关 Connection或者某个事务里做了耗时操作比如发短信导致连接被长时间占用。解决统一用 MyBatis 或 JdbcTemplate它们会管理连接释放。事务方法里只做数据库操作把外部调用挪到事务外。HikariCP 默认最大连接数是 10小型超市够用但别在事务里 sleep。6. 进阶技巧让这套系统多活两年第一版跑通之后真正决定这套系统能不能长期用的是几个细节。我踩过的最大教训是一开始图省事把所有配置写死在代码里结果换台机器部署就要改代码重新打包。后来我把数据库地址、端口、备份路径全挪到application.properties用不同 profile 区分开发和生产部署时只改配置文件不动一行 Java。第二个习惯是给关键操作留日志。收银、改价、删商品这三类动作我都会在 service 里打一条带操作人和时间的日志。不是为了审计是为了出问题时能回溯。有一次库存对不上就是靠日志发现某笔订单被重复提交了两次。第三个技巧是备份。MySQL 的mysqldump配合系统的定时任务每天凌晨导出一份 SQL 文件保留最近七天。命令很简单mysqldump -u root -p supermarket /backup/supermarket_$(date %Y%m%d).sql参数说明-u和-p后面跟账号密码supermarket是库名重定向到带日期的文件。恢复时用mysql -u root -p supermarket 备份文件.sql。这个习惯看起来笨但真出事的时候它是唯一的后悔药。最后一个建议是控制功能膨胀。小型超市系统的价值在于稳定和简单不在于功能多。每次想加新模块之前先问一句老板真的会用吗如果答案模糊就先不加。我见过太多课程设计堆了十几个模块最后连收银主流程都跑不顺。把商品、库存、订单这三条线打磨到不出错比什么都强。希望帮到你。本文还有配套的精品资源点击获取