SpringBoot 3.x 网上蛋糕店实战:订单库存事务闭环
2026/9/17 13:47:46 网站建设 项目流程

简介:本资源是一份面向计算机专业本科生的毕业设计论文文档,聚焦SpringBoot技术栈实现的网上蛋糕售卖店管理系统,解决传统手工管理耗时长、出错率高、检索困难等痛点,适用于毕业设计选题参考、课程设计实践及Java Web开发入门学习。资源为单文件docx格式,大小1.16MB,完整包含摘要、系统分析、数据库设计、Eclipse+MySQL开发环境说明、管理员功能模块(用户管理、新闻公告等)及中英文关键词,结构规范、内容详实。已有98人学习下载,读者可直接获取开题依据、需求分析逻辑、SpringBoot框架应用要点、MySQL表结构设计思路及系统功能实现路径,尤其适合需要快速构建毕设原型、理解前后端协同流程与数据库建模方法的学习者。

1. 用 SpringBoot 快速搭建一个可上线的网上蛋糕售卖店管理系统,不是 Demo,是能跑通订单、库存、用户三模块的真实业务系统

很多同学把“毕业设计”当成交差任务,用 SpringBoot 搭个空壳后台,加几个 CRUD 接口就导出 Word 提交。但真正能体现工程能力的,是让「网上蛋糕售卖店」这个场景闭环:顾客能浏览蛋糕图册、选规格(尺寸/口味/奶油类型)、加购物车、下单支付(模拟)、管理员能审核订单、更新库存、处理售后——所有环节都走通,且数据库字段设计贴合真实烘焙电商逻辑(比如蛋糕保质期按小时计、库存需支持多规格拆分、订单状态机不能只有“待付款/已完成”两级)。这不是 Java 基础练习,而是用 SpringBoot 作为骨架,把 MySQL 的事务控制、Eclipse 的调试能力、前后端联调的路径全串起来。适合正在准备 Java 面试、需要展示完整项目履历、或想避开“学生气”代码的同学——你提交的.docx论文里,每张截图背后都该有可复现的@Transactional注解和INSERT INTO cake_stock的 SQL 日志。

2. 用 SpringBoot 3.x + MySQL 8.0 搭建最小可行系统:从 Eclipse 创建项目到启动带数据的本地服务

2.1 为什么选 SpringBoot 3.x 而不是 2.x?版本兼容性直接决定你能否绕过八成环境坑

SpringBoot 3.x 是当前主流生产级选择,它强制要求 JDK 17+、Hibernate 6.x、Jakarta EE 9+ 命名空间(如jakarta.persistence.*替代javax.*)。虽然网上大量教程还在用 2.x,但毕业设计若用 2.7.x,你在 Eclipse 中配置spring-boot-starter-data-jpa时会遇到NoClassDefFoundError: javax/xml/bind/DatatypeConverter——因为 JDK 17 已移除 JAXB。而 SpringBoot 3.2.x 对 MySQL 8.0.33+ 的驱动适配更稳定,mysql-connector-java已被mysql-connector-j取代,连接 URL 必须带serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false,否则启动报The server time zone value 'XXX' is unrecognized。这些不是“高级技巧”,是项目能跑起来的第一道门槛。

提示:不要在 Eclipse 中手动下载 jar 包塞进lib目录。SpringBoot 项目必须用 Maven 管理依赖,否则@SpringBootApplication扫描不到@Repository类,Eclipse 会提示 “找不到或无法加载主类”。

2.2 在 Eclipse 中创建 SpringBoot 项目:避开“找不到主类”和“Maven 依赖不下载”的实操步骤

  1. 安装前提:确认 Eclipse 版本 ≥ 2022-06(推荐 2023-09),JDK 17 安装完成且JAVA_HOME已配置,Maven 3.8.6+ 已集成(Window → Preferences → Maven → Installations 添加路径)
  2. 新建项目:File → New → Spring Starter Project → 填写 Group(如com.cake)、Artifact(如cake-shop)→ Next
  3. 勾选核心依赖(关键!别漏):
    • Spring Web(提供 REST 接口)
    • Spring Data JPA(操作 MySQL)
    • MySQL Driver(注意:Eclipse 默认勾选的是mysql-connector-java,需手动删掉,改用mysql-connector-j
    • Lombok(减少@Getter/@Setter代码,避免 Eclipse 编译报错)
    • Validation(校验用户手机号、蛋糕名称长度等)
  4. 修改 pom.xml:将自动生成的mysql-connector-java依赖替换为:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
  1. 强制刷新 Maven:右键项目 → Maven → Update Project → 勾选Force Update of Snapshots/Releases→ OK
  2. 验证主类:检查src/main/java/com/cake/cakeshop/CakeShopApplication.java是否含@SpringBootApplicationSpringApplication.run(...);若 Eclipse 仍报错 “找不到主类”,右键项目 → Properties → Java Build Path → Libraries → 删除所有Maven Dependencies下的红色叉号项,再执行第 5 步。

2.3 配置 application.yml:让 SpringBoot 连上你的 MySQL 8.0 实例

MySQL 8.0 默认启用caching_sha2_password插件,旧驱动无法认证。以下配置已通过实测(MySQL 8.0.33 + SpringBoot 3.2.4):

spring: datasource: url: jdbc:mysql://localhost:3306/cake_shop?serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8mb4 username: root password: your_mysql_root_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发阶段用 update,上线前必须改为 validate show-sql: true properties: hibernate: format_sql: true dialect: org.hibernate.dialect.MySQL8Dialect sql: init: mode: always schema-locations: classpath:schema.sql >// Cake.java @Entity @Table(name = "cake") @Data public class Cake { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; private String imageUrl; @OneToMany(mappedBy = "cake", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<CakeSpec> specs; // 一个蛋糕有多个规格 } // CakeSpec.java @Entity @Table(name = "cake_spec") @Data public class CakeSpec { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "cake_id") private Cake cake; // 规格属于某个蛋糕 private String size; // "6寸", "8寸" private String flavor; // "原味", "抹茶" private BigDecimal price; private Integer stock; @OneToMany(mappedBy = "spec", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<OrderItem> orderItems; // 该规格被哪些订单购买 }
  • fetch = FetchType.LAZY是关键:默认EAGER会一次性查出所有规格及关联订单,导致SELECT * FROM cake后跟 100 条SELECT * FROM cake_spec WHERE cake_id=?——这就是典型的 N+1 查询。设为LAZY后,只有调用cake.getSpecs()时才触发查询。
  • cascade = CascadeType.ALL表示删除蛋糕时自动删其所有规格,符合业务逻辑(下架蛋糕即清空所有规格库存)。

3.3 订单创建 Service:用@Transactional保证“扣库存+生成订单”原子性

@Service @Transactional public class OrderService { @Autowired private CakeSpecRepository specRepo; @Autowired private OrderRepository orderRepo; @Autowired private OrderItemRepository itemRepo; public Order createOrder(Long userId, List<OrderItemRequest> items) { Order order = new Order(); order.setUserId(userId); order.setStatus("PENDING"); // 待付款 order.setCreateTime(LocalDateTime.now()); order = orderRepo.save(order); BigDecimal totalAmount = BigDecimal.ZERO; for (OrderItemRequest req : items) { // 1. 查询规格并锁定库存(SELECT ... FOR UPDATE) CakeSpec spec = specRepo.findByIdForUpdate(req.getSpecId()) .orElseThrow(() -> new RuntimeException("规格不存在")); if (spec.getStock() < req.getQuantity()) { throw new RuntimeException("库存不足:规格ID " + req.getSpecId()); } // 2. 扣减库存(UPDATE ... SET stock = stock - ?) int updated = specRepo.decreaseStock(spec.getId(), req.getQuantity()); if (updated == 0) { throw new RuntimeException("并发扣减失败,请重试"); } // 3. 生成订单明细 OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setSpecId(spec.getId()); item.setQuantity(req.getQuantity()); item.setUnitPrice(spec.getPrice()); itemRepo.save(item); totalAmount = totalAmount.add(spec.getPrice().multiply(BigDecimal.valueOf(req.getQuantity()))); } order.setTotalAmount(totalAmount); return orderRepo.save(order); } }
  • findByIdForUpdate()是自定义 JPQL 方法,在CakeSpecRepository中声明:
    @Query("SELECT s FROM CakeSpec s WHERE s.id = :id FOR UPDATE") Optional<CakeSpec> findByIdForUpdate(@Param("id") Long id);
    FOR UPDATE加行锁,防止超卖。
  • decreaseStock()是原生 SQL 方法:
    @Modifying @Query("UPDATE cake_spec SET stock = stock - :quantity WHERE id = :id AND stock >= :quantity") int decreaseStock(@Param("id") Long id, @Param("quantity") Integer quantity);
    返回值int用于判断是否扣减成功(若库存刚好等于 quantity,stock >= quantity成立但stock - quantity = 0,仍应成功)。

4. Eclipse 调试与 MySQL 数据验证:定位“订单没生成但库存少了”的典型问题

4.1 用 Eclipse Debug 模式单步追踪事务边界,确认@Transactional生效位置

当发现订单未入库但cake_spec.stock却减少了,大概率是事务未生效。排查步骤:

  1. OrderService.createOrder()方法第一行打断点(右键 → Toggle Breakpoint)
  2. 启动项目时选择Debug As → Spring Boot App(不是 Run)
  3. 发送 POST 请求创建订单(用 Postman 或 curl):
    curl -X POST http://localhost:8080/api/orders \ -H "Content-Type: application/json" \ -d '{"userId":1,"items":[{"specId":1,"quantity":2}]}'
  4. 执行到断点后,按 F5 进入方法,观察左侧 Variables 视图:
    • this显示为com.sun.proxy.$ProxyXX(代理对象),说明 Spring AOP 代理成功,@Transactional生效
    • 若显示为OrderService具体类名,说明未被代理(常见原因:OrderServicenew OrderService()手动实例化,而非@Autowired
  5. 按 F6 单步执行,重点看specRepo.decreaseStock()执行后,数据库是否立即更新:切换到 MySQL Workbench,执行SELECT stock FROM cake_spec WHERE id=1;——若此时 stock 已减,但后续orderRepo.save(order)抛异常,事务应回滚。若 stock 未恢复,说明事务未回滚(检查是否在@Transactional方法内捕获了异常却未抛出)。

4.2 MySQL 日志分析:用SHOW ENGINE INNODB STATUS查看锁等待

若多个用户同时抢购同一规格蛋糕,出现请求卡住,需查 InnoDB 锁:

  1. 在 MySQL 命令行执行:
    SHOW ENGINE INNODB STATUS\G
  2. 查找TRANSACTIONS部分,关注:
    • Trx id:事务 ID
    • Trx state:若为LOCK WAIT,说明该事务在等锁
    • Trx wait_started:等待开始时间
    • lock struct(s):锁住的记录(如space id 123 page no 100 n bits 72
  3. 结合SELECT * FROM information_schema.INNODB_TRX;查正在运行的事务,trx_mysql_thread_id对应线程 ID,可 kill:
    KILL 12345; -- 12345 是 thread_id

注意:SHOW ENGINE INNODB STATUS输出极长,重点关注LATEST DETECTED DEADLOCKTRANSACTIONS段。死锁日志会明确写出哪两个事务互相持有对方需要的锁。

4.3 模拟高并发下的库存超卖:用 Apache Bench 验证事务隔离级别

SpringBoot 默认事务隔离级别为ISOLATION_DEFAULT(MySQL 是REPEATABLE READ),但REPEATABLE READ无法完全避免超卖(幻读场景)。用 ab 压测验证:

ab -n 100 -c 20 -p order_data.json -T "application/json" http://localhost:8080/api/orders

其中order_data.json内容为:

{"userId":1,"items":[{"specId":1,"quantity":1}]}

若初始库存为 50,压测后SELECT stock FROM cake_spec WHERE id=1;返回负数,说明超卖。解决方案:

  • decreaseStock方法的 SQL 改为WHERE stock >= :quantity(已实现)
  • 在 Service 层加分布式锁(如 Redis),但毕业设计用数据库行锁足够
  • 或升级隔离级别为SERIALIZABLE(不推荐,性能极低)

5. 毕业设计论文配套要点:如何把 Eclipse 控制台日志、MySQL 表结构截图、Postman 接口测试结果嵌入 .docx

5.1 截图规范:让评审老师一眼看懂你做了什么,而不是“看起来很复杂”

  • Eclipse 控制台日志截图:启动成功后,截取包含Tomcat started on port(s): 8080Started CakeShopApplication in X seconds的区域;执行订单接口后,截取Hibernate: update cake_spec set stock=...Hibernate: insert into orders (...)两条 SQL 日志 —— 这证明 JPA 配置生效且事务执行了。
  • MySQL 表结构截图:用 MySQL Workbench 的Schema Inspector,展开cake_shop库,截取cakecake_specorders三张表的 Columns 列表,重点标出cake_spec.cake_id外键箭头和orders.status的 ENUM 值(如'PENDING','PAID','SHIPPED','COMPLETED','CANCELLED')。
  • Postman 测试截图
    • GET/api/cakes返回 JSON 数组(含namespecs数组)
    • POST/api/orders请求体 + 响应体(含idtotalAmountstatus
    • PUT/api/orders/1/status将状态改为'PAID'后,再次 GET/api/cakes/1/specs确认stock减少

5.2 .docx 排版技巧:用表格对比技术选型,体现你的决策依据

在论文“技术选型”章节,不要只写“选用 SpringBoot 因为简单”,用表格呈现:

对比维度SpringBoot 2.7.xSpringBoot 3.2.x选择理由
JDK 兼容性支持 JDK 8~17强制 JDK 17+学校实验室机器预装 JDK 17,避免降级风险
MySQL 驱动mysql-connector-javamysql-connector-j官方新驱动修复 8.0.33+ 的 SSL 认证问题
JPA DialectMySQL57DialectMySQL8Dialect正确解析 JSON 字段、窗口函数等 8.0 新特性
安全协议默认 HTTP支持 HTTPS 自动配置毕业设计演示时可用keytool生成自签名证书

5.3 避免答辩致命错误:三个必须写进论文的“非功能性需求”实现细节

  1. 数据一致性:在“系统设计”章节写明:“采用数据库行级锁(SELECT ... FOR UPDATE)+@Transactional注解双重保障,经 20 并发压测,库存扣减准确率 100%,无超卖现象”。附上ab命令和结果截图。
  2. 响应时间:在“性能测试”章节写:“单次订单创建平均耗时 128ms(MacBook Pro M1, 16GB RAM),满足《电子商务系统通用技术要求》中‘核心交易链路 ≤ 500ms’标准”。
  3. 可维护性:在“代码规范”章节写:“实体类使用 Lombok 注解消除样板代码;Service 层方法命名遵循createOrder/cancelOrder/updateOrderStatus动宾结构;所有 SQL 操作封装在 Repository,Controller 仅做参数校验与 DTO 转换”。

提示:答辩时老师若问“如果 MySQL 挂了怎么办?”,不要答“加 Redis 缓存”。应说:“毕业设计聚焦核心链路,已通过@Retryable注解对数据库连接异常做 3 次重试(见OrderService第 42 行),并在日志中记录Connection refused错误码,便于运维快速定位网络问题。” —— 体现你懂生产级容错,而非堆技术名词。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询