SpringBoot+JDK17实战:从零构建采购管理系统

发布时间:2026/8/27 10:19:25
SpringBoot+JDK17实战:从零构建采购管理系统 1. 背景与项目价值1.1 为什么要做采购管理系统在校招、社招简历里“管理系统”类项目永远是出镜率最高的选题之一。但在实际面试中面试官最常问的一句话是“这个系统是你自己从头写的吗核心模块有哪些有没有遇到什么坑”很多同学用网上下载的 SSM 或 JSP 老项目代码年代久远Spring 版本还停留在 XML 配置时代既跑不起来也讲不清楚。真正能拿得出手的是那种技术栈新、结构清晰、功能闭环、能演示能部署的项目。采购管理系统恰好满足这些要求。它属于典型的企业级 CRUD 业务流项目业务逻辑清晰涉及供应商管理、采购订单、入库管理、库存统计等模块既有单表操作也有多表关联和状态流转。用 SpringBoot JDK17 MyBatis MySQL 实现既能体现 JavaWeb 基本功又能展示 SpringBoot 的快速开发能力。1.2 项目适合哪些人群这个项目适合下面几类读者Java 初学者想找一个完整的 JavaWeb 项目练手而不是零散的 Demo。毕业设计选题需要一套能运行、能答辩、有业务深度的系统。简历项目储备想在后端岗位面试中有项目可讲应付“项目细节”追问。转行学习者学了 SpringBoot 基础语法后急需一个完整项目串联零散知识点。如果你正处于以上任何一种情况这篇文章值得你从头跟到尾。1.3 学完能掌握什么通过本项目你将掌握JDK17 的安装与多版本配置尤其是 Windows 和 Mac 的差异点。SpringBoot 3.x 项目从零搭建的完整流程。MyBatis 或 MyBatis-Plus 操作数据库的常用姿势。采购业务的数据库表设计思路包括一对多、多对多关系拆解。Controller、Service、Mapper 三层的代码组织方式。前端页面Thymeleaf 或 Vue 静态页与后端接口的对接方式。常见启动报错、依赖冲突、端口占用等问题的排查思路。下面开始实操。整个项目我会按“环境准备 → 数据库设计 → 项目搭建 → 代码实现 → 运行验证”的顺序展开最后补充常见问题与简历讲解建议。2. 技术选型与版本说明2.1 为什么选 SpringBoot JDK17很多学校课程还在教 JDK8但 2024 年以后JDK17 已经成为 Java 生态的主流长期支持版本。Spring Boot 3.x 官方要求 JDK17 作为最低版本Tomcat 10、Jakarta EE 9 等标准也全面切换。SpringBoot 相比传统 SSM 的优势非常明显自动配置不需要手写大量的 XML Bean 配置默认配置开箱即用。内嵌服务器打成 Jar 包后直接java -jar运行不需要外置 Tomcat。生态丰富连接 MySQL、Redis、MQ 等中间件都有对应的 Starter。运维简单自带 Actuator 监控适合微服务部署。JDK17 本身带来了不少新特性比如 Switch 表达式、文本块、密封类、增强的伪随机数生成器等。对管理类系统来说我们最直接感受到的是更稳定、性能更好、长期维护无忧。2.2 项目技术栈清单本项目采用的技术栈如下版本按实际环境调整组件推荐版本说明JDK17必须 JDK17 及以上Spring Boot 3.x 强制要求构建工具Maven 3.8也可用 Gradle本文以 Maven 为例开发工具IntelliJ IDEA社区版/专业版均可推荐 2023 以上Spring Boot3.2.x稳定版本支持 JDK17MyBatis-Plus3.5.x极大的减少 SQL 编写量MySQL8.05.7 也能跑推荐 8.0前端模板ThymeleafSpringBoot 默认模板引擎前后端未分离数据库连接池HikariCPSpringBoot 默认集成版本需要根据你的项目实际情况调整本文示例以 Spring Boot 3.2 JDK17 MySQL 8 为基准重点是演示配置思路和代码结构版本不同时只需要调整依赖坐标。2.3 JDK17 安装与多版本配置这里单独说一下 JDK17 的安装因为不少同学在环境上卡了很久。Windows 下安装 JDK17去 Oracle 官网或 Adoptium 下载 JDK17 的 Windows x64 安装包双击安装。安装时记住安装路径例如C:\Program Files\Java\jdk-17。安装完成后配置环境变量JAVA_HOMEC:\Program Files\Java\jdk-17 Path%JAVA_HOME%\bin配置完成后在命令行输入java -version javac -version如果输出类似下面内容说明环境配置成功java version 17.0.10 2024-01-16 LTS Java(TM) SE Runtime Environment (build 17.0.1011-LTS-120) Java HotSpot(TM) 64-Bit Server VM (build 17.0.1011-LTS-120, mixed mode, sharing)Mac 下安装 JDK17推荐使用 Homebrewbrew install openjdk17安装完成后需要做符号链接否则java -version可能读不到sudo ln -sfn $(brew --prefix)/opt/openjdk17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk多版本切换如果你电脑上同时有 JDK8 和 JDK17建议使用 JEnvMac/Linux或手动切换 JAVA_HOMEWindows。不要盲目卸载旧版本有些老旧项目仍然需要 JDK8 才能编译。3. 系统功能与数据库设计3.1 功能模块梳理采购管理系统这个名字比较宽泛不同场景有不同的功能范围。为了做一个“能答辩”的项目我建议覆盖以下核心模块供应商管理维护供应商基本信息包括联系人、电话、地址、合作状态。采购订单管理创建采购订单填写订单编号、供应商、采购日期、订单状态。订单明细管理每个订单关联多个采购商品记录商品名称、数量、单价、小计。商品管理维护商品档案包括商品编码、名称、规格、单位。入库管理采购订单到货后执行入库操作增加库存数量。库存管理查看当前库存数量支持库存预警提示。用户登录与权限简单的登录功能区分管理员和普通操作员。业务逻辑上核心的流程是创建供应商 → 创建商品档案 → 创建采购订单 → 添加订单明细 → 审核订单 → 收货入库 → 更新库存这样的一组功能闭环比单纯堆 CRUD 更有说服力。面试时你可以围绕“订单状态如何流转”“入库时如何防止超卖”这类问题展开。3.2 数据库表设计数据库设计是面试高频考点这个项目至少要保证三张核心表之间的外键关系清晰供应商表、采购订单表、采购订单明细表。再加上商品表和用户表组成一套完整业务模型。下面给出核心表结构的 SQL这里使用 MySQL 为例。供应商表supplierCREATE TABLE supplier ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, supplier_code VARCHAR(32) NOT NULL COMMENT 供应商编码, supplier_name VARCHAR(100) NOT NULL COMMENT 供应商名称, contact_person VARCHAR(50) DEFAULT NULL COMMENT 联系人, contact_phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, address VARCHAR(255) DEFAULT NULL COMMENT 地址, status TINYINT DEFAULT 1 COMMENT 状态1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 ) COMMENT 供应商表;商品表productCREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, product_code VARCHAR(32) NOT NULL COMMENT 商品编码, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, spec VARCHAR(50) DEFAULT NULL COMMENT 规格型号, unit VARCHAR(20) DEFAULT NULL COMMENT 单位, stock_quantity INT DEFAULT 0 COMMENT 库存数量, warning_quantity INT DEFAULT 10 COMMENT 库存预警值, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) COMMENT 商品表;采购订单表purchase_orderCREATE TABLE purchase_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, supplier_id BIGINT NOT NULL COMMENT 供应商ID, supplier_name VARCHAR(100) DEFAULT NULL COMMENT 供应商名称, order_date DATE DEFAULT NULL COMMENT 采购日期, total_amount DECIMAL(12,2) DEFAULT 0.00 COMMENT 总金额, status TINYINT DEFAULT 0 COMMENT 状态0待审核 1已审核 2已入库 3已取消, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 ) COMMENT 采购订单表;采购订单明细表purchase_order_itemCREATE TABLE purchase_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, order_id BIGINT NOT NULL COMMENT 订单ID, product_id BIGINT NOT NULL COMMENT 商品ID, product_name VARCHAR(100) DEFAULT NULL COMMENT 商品名称, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 采购单价, quantity INT DEFAULT 0 COMMENT 采购数量, amount DECIMAL(12,2) DEFAULT 0.00 COMMENT 小计金额 ) COMMENT 采购订单明细表;用户表sys_userCREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码, real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role VARCHAR(20) DEFAULT USER COMMENT 角色ADMIN/USER, status TINYINT DEFAULT 1 COMMENT 状态1启用 0禁用 ) COMMENT 系统用户表;这几张表的字段不算多但足够覆盖日常采购业务。如果你需要做演示数据可以手动插入几条供应商、商品和订单记录方便测试列表查询和状态流转。3.3 数据层设计要点订单明细中冗余了product_name和supplier_name虽然有些冗余但能避免联表查询过多这种设计在报表场景中很常见。订单总金额total_amount可以在创建订单时计算并更新也可以在查询时通过 SUM 计算。生产环境建议保存快照避免历史订单金额被商品价格变动影响。status字段用数字表示状态比字符串更省存储也方便做状态机流转。4. 项目搭建与核心代码实现4.1 创建 SpringBoot 项目推荐直接在 IDEA 中创建打开 IDEA选择 New Project → Spring Initializr。填写项目信息Group 为com.exampleArtifact 为purchase。指定 Java 版本为 17。选择依赖Spring Web、Thymeleaf、MyBatis Framework、MySQL Driver、Lombok。如果你是使用 Maven 手工创建可以在pom.xml中加入以下核心依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里特别说明一下 MyBatis-Plus 的坐标Spring Boot 3 需要引入mybatis-plus-spring-boot3-starter而不是旧版的mybatis-plus-boot-starter否则启动时会因为包版本问题抛异常。4.2 编写核心配置文件文件路径src/main/resources/application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/purchase_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto配置说明map-underscore-to-camel-case: true用于把数据库的下划线字段自动映射为 Java 驼峰属性例如supplier_code映射到supplierCode。log-impl设置为 StdOutImplSQL 会打印在控制台方便调试。密码和具体数据库名需要换成你自己的。4.3 编写实体类以订单和订单明细为例使用 Lombok 简化 getter/setter。文件路径src/main/java/com/example/purchase/entity/PurchaseOrder.javapackage com.example.purchase.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; Data TableName(purchase_order) public class PurchaseOrder { TableId(type IdType.AUTO) private Long id; private String orderNo; private Long supplierId; private String supplierName; private LocalDate orderDate; private BigDecimal totalAmount; private Integer status; private String remark; private LocalDateTime createTime; private LocalDateTime updateTime; }文件路径src/main/java/com/example/purchase/entity/PurchaseOrderItem.javapackage com.example.purchase.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; Data TableName(purchase_order_item) public class PurchaseOrderItem { TableId(type IdType.AUTO) private Long id; private Long orderId; private Long productId; private String productName; private BigDecimal price; private Integer quantity; private BigDecimal amount; }4.4 编写 Mapper 层Mapper 接口继承 BaseMapper 后就已经内置了 selectById、insert、updateById、deleteById、selectList 等常用方法不需要手写 SQL。文件路径src/main/java/com/example/purchase/mapper/PurchaseOrderMapper.javapackage com.example.purchase.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.purchase.entity.PurchaseOrder; import org.apache.ibatis.annotations.Mapper; Mapper public interface PurchaseOrderMapper extends BaseMapperPurchaseOrder { }同样的方式创建PurchaseOrderItemMapper、SupplierMapper、ProductMapper、SysUserMapper。如果需要自定义查询例如统计每个供应商的采购总额可以在 Mapper 中写一个方法并使用Select注解Select(SELECT supplier_name, SUM(total_amount) AS total FROM purchase_order WHERE status 2 GROUP BY supplier_name) ListMapString, Object selectSupplierTotal();4.5 编写 Service 层Service 层是核心业务逻辑所在。以订单创建为例这里需要做的事生成订单编号。保存订单主表。遍历明细列表保存每个明细。计算订单总金额并更新。文件路径src/main/java/com/example/purchase/service/impl/PurchaseOrderServiceImpl.javapackage com.example.purchase.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.purchase.entity.PurchaseOrder; import com.example.purchase.entity.PurchaseOrderItem; import com.example.purchase.mapper.PurchaseOrderItemMapper; import com.example.purchase.mapper.PurchaseOrderMapper; import com.example.purchase.service.PurchaseOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; Service public class PurchaseOrderServiceImpl extends ServiceImplPurchaseOrderMapper, PurchaseOrder implements PurchaseOrderService { Autowired private PurchaseOrderItemMapper purchaseOrderItemMapper; Override Transactional(rollbackFor Exception.class) public void createOrder(PurchaseOrder order, ListPurchaseOrderItem items) { // 1. 生成订单编号PO 年月日时分秒 String orderNo PO new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()); order.setOrderNo(orderNo); order.setStatus(0); // 2. 计算总金额 BigDecimal total BigDecimal.ZERO; for (PurchaseOrderItem item : items) { BigDecimal amount item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); item.setAmount(amount); total total.add(amount); } order.setTotalAmount(total); // 3. 保存主表 this.save(order); // 4. 保存明细 for (PurchaseOrderItem item : items) { item.setOrderId(order.getId()); purchaseOrderItemMapper.insert(item); } } }这里加Transactional非常必要。如果明细插入过程中出现异常订单主表也要回滚否则会出现“主表存在、明细缺失”的脏数据。4.6 编写 Controller 层Controller 负责接收请求、调用 Service、返回页面或 JSON 数据。文件路径src/main/java/com/example/purchase/controller/OrderController.javapackage com.example.purchase.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.purchase.entity.PurchaseOrder; import com.example.purchase.service.PurchaseOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; Controller RequestMapping(/order) public class OrderController { Autowired private PurchaseOrderService purchaseOrderService; GetMapping(/list) public String list(Model model) { QueryWrapperPurchaseOrder queryWrapper new QueryWrapper(); queryWrapper.orderByDesc(create_time); ListPurchaseOrder orderList purchaseOrderService.list(queryWrapper); model.addAttribute(orderList, orderList); return order/list; } GetMapping(/add) public String addPage() { return order/add; } }如果是前后端分离模式可以把Controller换成RestController页面交给 Vue 或 React 处理。本项目为降低学习成本推荐使用 Thymeleaf 模板渲染。4.7 前端页面简化示例在src/main/resources/templates/order/list.html中使用 Thymeleaf 渲染订单列表!DOCTYPE html html langzh xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title采购订单列表/title style table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ccc; padding: 8px; text-align: center; } /style /head body h2采购订单列表/h2 table thead tr th订单编号/th th供应商名称/th th采购日期/th th总金额/th th状态/th /tr /thead tbody tr th:eachorder : ${orderList} td th:text${order.orderNo}PO202401010000/td td th:text${order.supplierName}XX供应商/td td th:text${order.orderDate}2024-01-01/td td th:text${order.totalAmount}0.00/td td th:text${order.status 0 ? 待审核 : order.status 1 ? 已审核 : order.status 2 ? 已入库 : 已取消}待审核/td /tr /tbody /table /body /html这里的th:each是 Thymeleaf 的循环语法功能类似 JSP 的c:forEach。状态字段通过三元表达式转换为中文这种方式简单直观。5. 完整业务流程订单审核与入库单纯做增删改查还不够采购管理系统最核心的业务闭环是“审核 → 入库 → 更新库存”。下面补上这个关键流程。5.1 订单审核逻辑文件路径com/example/purchase/service/impl/PurchaseOrderServiceImpl.java中追加方法Override Transactional(rollbackFor Exception.class) public void auditOrder(Long orderId) { PurchaseOrder order this.getById(orderId); if (order null) { throw new RuntimeException(订单不存在); } if (order.getStatus() ! 0) { throw new RuntimeException(只有待审核状态的订单才能审核); } order.setStatus(1); this.updateById(order); }这种写法体现了状态校验的逻辑。如果直接无条件更新状态很容易出现“已入库订单又被审核”的并发问题。实际开发中还可以用乐观锁或状态条件更新来保证并发安全。5.2 入库操作与库存更新入库操作涉及两张表的修改采购订单状态更新、商品库存增加。这两步必须放在同一个事务中。Override Transactional(rollbackFor Exception.class) public void stockIn(Long orderId) { PurchaseOrder order this.getById(orderId); if (order null) { throw new RuntimeException(订单不存在); } if (order.getStatus() ! 1) { throw new RuntimeException(只有已审核状态的订单才能入库); } // 查询订单明细 QueryWrapperPurchaseOrderItem itemQuery new QueryWrapper(); itemQuery.eq(order_id, orderId); ListPurchaseOrderItem items purchaseOrderItemMapper.selectList(itemQuery); // 更新商品库存 for (PurchaseOrderItem item : items) { Product product productMapper.selectById(item.getProductId()); if (product null) { throw new RuntimeException(商品不存在 item.getProductName()); } int newStock product.getStockQuantity() item.getQuantity(); product.setStockQuantity(newStock); productMapper.updateById(product); } // 更新订单状态为已入库 order.setStatus(2); this.updateById(order); }这段代码体现了两个高频面试考点事务的一致性和业务状态校验。当面试官问“库存怎么保证不超卖”时你可以顺着这个逻辑讲解。6. 运行项目与验证6.1 初始化数据库在 MySQL 中执行前面提供的建表 SQL并把连接信息改成自己的账号密码。建议执行以下命令确认表结构SHOW TABLES;确保能看到supplier、product、purchase_order、purchase_order_item、sys_user五张表。6.2 启动项目在 IDEA 中运行启动类PurchaseApplication.java启动类代码如下package com.example.purchase; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class PurchaseApplication { public static void main(String[] args) { SpringApplication.run(PurchaseApplication.class, args); } }控制台出现以下日志说明启动成功Tomcat started on port(s): 8080 (http) with context path Started PurchaseApplication in 2.5 seconds (process running for 2.8)浏览器访问http://localhost:8080/order/list如果能看到订单列表页面说明项目已经跑通。6.3 接口自测如果你把 Controller 改成了前后端分离模式可以用 Postman 或 curl 自测接口。以查询订单列表为例curl http://localhost:8080/api/order/list返回 JSON 示例{ code: 200, data: [ { id: 1, orderNo: PO20240615103000, supplierName: 上海华兴电子, totalAmount: 35600.00, status: 1 } ] }如果开启了我的 MyBatis SQL 日志控制台会打印出执行的 SQL 语句配合日志能快速定位 SQL 问题。7. 常见问题与排查思路初学者在搭建和运行 SpringBoot 项目时大概率会遇到下面这些问题。我整理了出现频率最高的几类对应给出解决思路。问题现象常见原因解决思路java: invalid source release: 17IDEA 未配置 JDK17 编译环境检查 Project Structure 中的 SDK 和 Language Level调为 17Failed to configure a DataSource数据库配置缺失或连接失败检查application.yml的 url、用户名、密码确认 MySQL 已启动Access denied for user rootlocalhost密码错误或权限不足核对数据库密码为项目单独创建最小权限账号Unknown database purchase_system数据库未提前创建在 MySQL 中执行CREATE DATABASE purchase_system启动报ClassNotFoundException: jakarta.servlet.*Spring Boot 3 与旧版 Tomcat/JSP 依赖冲突不要引入javax.servlet旧包统一使用 jakarta 依赖Port 8080 was already in use8080 端口被其他进程占用换端口在 yml 中修改 server.port或查找占用进程并关闭Maven 依赖下载慢网络问题或未配置国内镜像配置阿里云 Maven 镜像检查 idea 代理设置SQL 语句没有打印MyBatis 日志配置缺失确认mybatis-plus.configuration.log-impl配置为 StdOutImpl下面针对几个问题展开细说。7.1 端口被占用这是最常见的启动失败问题。在 Windows 下可以使用命令查找占用进程netstat -ano | findstr 8080通过最后一列的 PID打开任务管理器找到对应进程并结束。如果不想结束进程最简单的方式是修改端口server: port: 80817.2 JDK 版本不匹配有些同学电脑上同时存在 JDK8 和 JDK17IDEA 仍然默认使用旧版本。解决办法点击菜单File → Project Structure → SDKs添加 JDK17 路径。点击Project把 Project SDK 和 Language Level 都改为 17。在Modules中确认每个模块的 Language Level 也是 17。检查 Maven 的pom.xml中java.version是否设置为 17。7.3 SpringBoot 版本太高导致依赖不兼容如果代码里使用了 MyBatis-Plus 旧版启动器Spring Boot 3 启动时会报Invalid value type for attribute factoryBeanObjectType: java.lang.String解决办法将mybatis-plus-boot-starter换成mybatis-plus-spring-boot3-starter版本使用 3.5.3 以上。8. 最佳实践与工程建议8.1 简历上如何写这个项目很多同学项目做完了但简历写得跟“仓库目录说明书”一样这是很吃亏的。比如这样写面试官看了也不会留下印象采购管理系统使用 SpringBoot MyBatis MySQL实现了供应商、订单、商品的管理。建议改成“问题导向 技术亮点”的写法采购管理系统 技术栈SpringBoot 3.2 / JDK17 / MyBatis-Plus / MySQL 8.0 / Thymeleaf 负责内容 1. 设计供应商、采购订单、订单明细、商品库存等 5 张核心表并通过外键冗余方式优化报表查询性能。 2. 基于 MyBatis-Plus 实现订单主表和明细表批量保存使用 Transactional 保证订单创建、审核、入库流程的数据一致性。 3. 实现订单状态机待审核 → 已审核 → 已入库在审核和入库接口中加入状态校验避免非法状态流转。 4. 使用 HikariCP 连接池配置 SQL 日志解决 MySQL 连接超时与慢查询定位问题。这种写法有数据量、有技术点、有业务设计面试官问起来你也有话可讲。8.2 项目迭代建议如果时间充裕可以继续给项目增加亮点Redis 缓存使用 Spring Data Redis 缓存供应商列表降低数据库压力。Spring Security把登录功能升级为基于 JWT 的认证授权。异步处理订单创建成功后发送通知消息使用 Spring 的事件机制或 MQ。Excel 导出使用 EasyExcel 导出采购订单报表。接口文档集成 Knife4j 或 SpringDoc自动生成 Swagger 接口文档。8.3 数据库操作安全底线在做这个项目时要养成好习惯删除数据前先备份生产环境不要随便执行DELETE FROM。更新操作必须带 WHERE避免全表更新。数据库账号遵循最小权限原则项目连接用户只授予需要的数据库权限。密码明文存储只适合教学项目真实项目必须使用 BCrypt 等加密算法。9. 总结与下一步学习方向本文围绕采购管理系统这个完整的 JavaWeb 项目讲清楚了从环境准备到代码实现的整条链路。核心内容包括JDK17 安装与 SpringBoot 3.x 的版本关系。供应商表、订单表、明细表、商品表、用户表的数据库设计。SpringBoot 项目的三层架构代码组织方式。事务控制、状态流转、库存更新的业务实现。常见启动报错的排查思路。简历项目描述的优化技巧。这个项目做下来你至少能收获一份可运行、可演示、可讲解的完整代码这也是春招秋招简历上比较实用的储备。建议你拿到代码后不要只满足于“跑起来”而是自己重新写一遍订单审核和入库的逻辑尝试改 bug、加功能只有亲手踩过坑面试时才能讲得深入。如果你的目标是进后端开发岗下一步可以继续学习 Spring Security 权限控制、Redis 缓存、消息队列、Docker 部署这些方向。从“能跑”到“能上生产”中间差的不是某个复杂框架而是一整套工程化思维。如果你在搭建过程中遇到了本文没有覆盖的报错欢迎评论区带上完整日志提问我看到后会及时回复也会把高频问题补充进后续的排错文章中。收藏本文动手实践这是最快掌握 SpringBoot 开发的方式。