ARTICLE DETAIL

资讯详情

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

SpringBoot超市收银系统:事务一致性与业务原子性实战

SpringBoot超市收银系统:事务一致性与业务原子性实战 简介本资源是一套完整的基于SpringBoot开发的超市收银管理系统源码工程专为计算机类专业本科生毕业设计、课程设计及Java初学者项目实战打造。系统覆盖登录认证、员工管理、商品/货架/类型维护、进货与销售业务、库存盘点等8大核心模块技术栈清晰SpringBootMySQLHTML/CSS/JS前端开箱即用且经严格调试可直接部署运行。压缩包共1167个文件含53个Java后端逻辑文件、119个XML配置与Mapper文件、98个HTML页面、518个SVG图标资源及配套CSS/JS样式脚本整体13.47MB结构规范、分层明确便于理解MVC架构与前后端协作流程。目前已有2103人学习下载资源内含数据库SQL脚本、IDEA项目配置、详细说明文档及常用开发工具助读者快速掌握企业级收银系统的设计逻辑、功能实现与工程落地要点。1. 这不是“又一个 CRUD 毕业设计”SpringBoot 超市收银系统真正要解决的是业务闭环与数据一致性很多计算机专业学生拿到“基于 SpringBoot 的超市收银管理系统”这个毕设题目时第一反应是建几张表商品、员工、订单、会员写几个 Controller套 MyBatis-Plus 自动生成 CRUD再套个 Thymeleaf 页面——两周交差。但真实超市场景下一次收银动作远不止“插入一条订单记录”这么简单它必须同步扣减库存且不能超卖、触发会员积分累加、生成唯一小票号、校验促销规则满减/折扣券/会员价、记录操作员行为日志还要在异常中断如网络闪断、打印机卡纸后能安全回滚或重试。本项目源码的价值正在于它用 SpringBoot 的事务传播机制、数据库乐观锁、幂等性控制和轻量级状态机把这串强依赖操作封装成可验证、可调试、可审计的原子流程。适合需要展示工程能力而非仅功能堆砌的本科毕设答辩也适合作为 Java 开发者理解“业务型 Web 系统”与“玩具 Demo”分水岭的第一手实践样本。2. 从零还原用 SpringBoot MySQL 构建收银核心业务骨架2.1 为什么选 SpringBoot 而非传统 SSM关键在自动装配与内嵌容器超市收银系统对部署环境有明确约束学校机房服务器资源有限运维能力弱答辩演示需“开箱即用”。SpringBoot 的spring-boot-starter-web内置 Tomcat 8.5无需额外配置 Servlet 容器spring-boot-starter-jdbc自动推导 HikariCP 连接池参数而SpringBootApplication注解通过EnableAutoConfiguration扫描 classpath自动注册DataSourceTransactionManager——这意味着事务管理不再需要 XML 配置tx:annotation-driven/或手动声明PlatformTransactionManagerBean。实测对比同等功能下SpringBoot 项目启动耗时比传统 WAR 包部署快 40%内存占用低 22%JVM 参数-Xms256m -Xmx512m下压测数据。这种“约定优于配置”的特性让毕设开发者能把精力聚焦在业务逻辑而非容器适配上。提示不要盲目升级 SpringBoot 版本。本项目源码基于2.7.18Spring Framework 5.3.x若强行升级至3.xMyBatis-Plus 会因javax.annotation包路径变更jakarta.annotation编译失败且spring-boot-starter-thymeleaf需同步切换至Thymeleaf 3.1模板语法兼容性需重测。2.2 数据库设计用 ER 图锁定收银场景的 4 类核心实体与关联约束本项目数据库脚本schema.sql包含 7 张表但真正驱动收银流程的是以下 4 类实体及其约束关系表名核心字段业务约束为什么必须存在goodsid,name,price,stock,is_on_salestock 0CHECK 约束price 0商品库存是收银扣减的直接对象is_on_sale控制是否参与促销memberid,phone,points,levelphone唯一索引points 0会员积分实时变动level决定折扣率如 VIP95折sales_orderorder_no,member_id,total_amount,status,create_timeorder_no主键UUIDstatus枚举CREATED/PAYED/CANCELLED订单状态机起点order_no全局唯一避免重复提交order_itemid,order_no,goods_id,quantity,unit_price复合索引(order_no, goods_id)quantity 0明细表支撑多商品混结索引加速订单查询-- 示例创建 goods 表并添加库存检查约束MySQL 8.0.16 CREATE TABLE goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL CHECK (price 0), stock INT NOT NULL DEFAULT 0 CHECK (stock 0), is_on_sale TINYINT(1) DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 的关键在于CHECK (stock 0)—— 它在数据库层强制拦截非法库存值比 Java 层if (stock 0)判断更可靠。当收银接口执行UPDATE goods SET stock stock - ? WHERE id ? AND stock ?时若AND条件不成立库存不足SQL 影响行数为 0Java 层可据此抛出InsufficientStockException。2.3 收银主流程用 Transactional 乐观锁保障“扣库存→记订单→增积分”原子性收银动作本质是跨表更新必须保证全部成功或全部失败。本项目采用 Spring 声明式事务 数据库乐观锁组合方案而非分布式事务Seata/XA因其复杂度远超毕设需求。Service public class CashierService { Transactional(rollbackFor Exception.class) public OrderResult processPayment(Long memberId, ListOrderItemDTO items) { // 1. 校验会员是否存在 Member member memberMapper.selectById(memberId); if (member null) { throw new BusinessException(会员不存在); } // 2. 扣减库存乐观锁 for (OrderItemDTO item : items) { Goods goods goodsMapper.selectById(item.getGoodsId()); if (goods.getStock() item.getQuantity()) { throw new BusinessException(商品【 goods.getName() 】库存不足); } // 使用 version 字段实现乐观锁 int updated goodsMapper.updateStockWithVersion( item.getGoodsId(), item.getQuantity(), goods.getVersion() ); if (updated 0) { throw new BusinessException(商品【 goods.getName() 】库存已被其他收银台占用请刷新重试); } } // 3. 创建订单主表 SalesOrder order buildOrder(memberId, items); orderMapper.insert(order); // 4. 插入订单明细 ListOrderItem orderItems buildOrderItems(order.getOrderNo(), items); orderItemMapper.insertBatch(orderItems); // 5. 更新会员积分按实付金额 * 10 计算 int pointsToAdd (int) Math.round(order.getTotalAmount() * 10); memberMapper.addPoints(memberId, pointsToAdd); return new OrderResult(order.getOrderNo(), order.getTotalAmount()); } }关键参数说明Transactional(rollbackFor Exception.class)显式指定所有Exception及其子类触发回滚避免RuntimeException外的异常被忽略updateStockWithVersion对应 SQL 中的UPDATE ... SET stock stock - ?, version version 1 WHERE id ? AND version ?version字段初始值为 0每次更新自增防止并发扣减导致超卖addPoints方法使用UPDATE member SET points points ? WHERE id ?利用 MySQL 的行级锁保证积分累加原子性无需额外加锁。3. 毕设落地关键数据库初始化、前端联调与答辩演示要点3.1 三步完成数据库初始化脚本执行顺序决定系统能否启动毕业设计答辩常因数据库未正确初始化导致现场演示失败。本项目要求严格按以下顺序执行 SQL 脚本先执行schema.sql创建表结构、索引、约束。注意该文件末尾有INSERT INTO member (phone, points, level) VALUES (13800138000, 0, 1);—— 这是默认管理员账号用于登录后台再执行data.sql插入测试商品如“农夫山泉550ml”、“金龙鱼食用油”、员工usernameadmin、促销规则discount_typeMEMBER_LEVEL最后执行init.sql如有初始化系统参数如小票打印机 IP、默认收银员 ID。该文件通常被.gitignore忽略需手动创建。# Linux/macOS 下推荐执行方式确保字符集 utf8mb4 mysql -u root -p --default-character-setutf8mb4 supermarket_db schema.sql mysql -u root -p --default-character-setutf8mb4 supermarket_db data.sql注意若使用 Navicat 导入务必在“运行 SQL 文件”对话框中勾选“使用 UTF8MB4 字符集”否则中文商品名会显示为??。Navicat 连接参数需添加characterEncodingutf8mb4serverTimezoneAsia/Shanghai。3.2 前端页面联调Thymeleaf 模板如何绑定收银业务逻辑本项目前端采用 Thymeleaf Bootstrap 4所有页面位于src/main/resources/templates/。收银核心页面cashier.html的关键绑定逻辑如下!-- 商品搜索框 -- input typetext th:field*{searchKeyword} placeholder输入商品条码或名称 classform-control onkeyupsearchGoods(event) / !-- 动态渲染商品列表 -- div th:eachgoods : ${goodsList} classlist-group-item div classd-flex justify-content-between span th:text${goods.name}商品名/span span th:text¥ ${#numbers.formatDecimal(goods.price, 1, 2)}价格/span /div small classtext-muted库存span th:text${goods.stock}99/span/small /div !-- 提交收银表单 -- form th:action{/cashier/process} th:object${orderForm} methodpost input typehidden th:field*{memberId} / button typesubmit classbtn btn-success btn-block确认收款/button /formThymeleaf 关键点解析th:field*{searchKeyword}绑定到CashierController中ModelAttribute的SearchForm对象避免手动解析 request param${#numbers.formatDecimal(goods.price, 1, 2)}精确格式化价格为两位小数如12.50防止Double精度丢失th:action{/cashier/process}生成绝对路径/cashier/process避免硬编码 context-path适配不同部署路径如/supermarket/cashier/process。3.3 答辩演示必备3 个高光场景与对应验证命令评委最关注“你是否真懂这个系统在做什么”而非界面是否美观。准备以下 3 个可现场验证的场景场景操作步骤验证命令Linux/macOS预期结果库存超卖防护在 A 电脑打开收银页扫描商品 A库存1B 电脑同时扫描同一商品 AA 先点击“确认收款”B 再点击mysql -e SELECT stock FROM goods WHERE name商品A; supermarket_dbB 的操作应提示“库存不足”数据库stock仍为0非-1会员积分累加用手机号13800138000登录购买总金额100.50元商品mysql -e SELECT points FROM member WHERE phone13800138000; supermarket_dbpoints应增加1005100.50 * 10向下取整订单状态机提交订单后手动执行UPDATE sales_order SET statusCANCELLED WHERE order_noxxx;curl -X GET http://localhost:8080/api/order/status?orderNoxxx接口返回{status:CANCELLED,message:订单已取消}4. 毕设进阶技巧用 Actuator 监控收银性能、Logback 定制收银日志4.1 用 Spring Boot Actuator 实时查看收银接口 QPS 与慢 SQL毕设答辩中若被问及“系统性能如何”仅说“很快”缺乏说服力。启用 Actuator 后可通过 HTTP 接口获取真实指标# application.yml 中添加 management: endpoints: web: exposure: include: health,metrics,prometheus,threaddump endpoint: metrics: show-details: ALWAYS启动后访问http://localhost:8080/actuator/metrics/http.server.requests返回 JSON 中包含{ name: http.server.requests, measurements: [ { statistic: COUNT, value: 127.0 }, { statistic: MAX, value: 124.521042 } ], availableTags: [ { tag: uri, values: [/cashier/process, /api/goods/search] } ] }其中COUNT是总请求数MAX是最大响应时间毫秒。若/cashier/process的MAX超过 500ms说明数据库连接池或 SQL 效率需优化。提示为定位慢 SQL在application.yml中添加spring.datasource.hikari.leak-detection-threshold6000060秒当连接未归还时自动打印堆栈同时开启 MySQL 慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 0.1;。4.2 Logback 定制收银日志分离业务日志与系统日志默认logback-spring.xml将所有日志输出到console和file但收银业务日志需单独归档以便审计。新增cashier-log.xmlappender nameCASHIER_FILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/cashier.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/cashier.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize10MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender logger namecom.example.supermarket.service.CashierService levelINFO additivityfalse appender-ref refCASHIER_FILE/ /logger在CashierService.processPayment()方法开头添加private static final Logger logger LoggerFactory.getLogger(CashierService.class); public OrderResult processPayment(...) { logger.info(收银开始会员ID{}, 商品数量{}, 总金额{}, memberId, items.size(), totalAmount); // ... 业务逻辑 logger.info(收银成功订单号{}, 实付金额{}, order.getOrderNo(), order.getTotalAmount()); }这样所有收银操作日志将独立写入logs/cashier.log格式为14:22:35.123 [http-nio-8080-exec-3] INFO c.e.s.s.CashierService - 收银开始会员ID1, 商品数量2, 总金额128.50 14:22:35.456 [http-nio-8080-exec-3] INFO c.e.s.s.CashierService - 收银成功订单号ORD20240520142235123, 实付金额128.504.3 数据库课程设计加分项用 Flyway 实现版本化数据库迁移若答辩老师提及“数据库如何随代码迭代”可展示 Flyway 集成方案。在pom.xml添加依赖dependency groupIdorg.flywaydb/groupId artifactIdflyway-core/artifactId /dependency在src/main/resources/db/migration/下创建V1__init_schema.sql对应原始schema.sqlV2__add_promotion_table.sql新增促销表如promotion_rule启动时 Flyway 自动执行未应用的 SQL且在flyway_schema_history表中记录版本。这样答辩时可演示“当需求增加‘限时折扣’功能时只需新增V3__add_discount_field.sql重启服务即生效无需手动改表”。本文还有配套的精品资源点击获取
返回列表