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 项目技术栈清单
本项目采用的技术栈如下,版本按实际环境调整:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 17+ | 必须 JDK17 及以上,Spring Boot 3.x 强制要求 |
| 构建工具 | Maven 3.8+ | 也可用 Gradle,本文以 Maven 为例 |
| 开发工具 | IntelliJ IDEA | 社区版/专业版均可,推荐 2023 以上 |
| Spring Boot | 3.2.x | 稳定版本,支持 JDK17 |
| MyBatis-Plus | 3.5.x | 极大的减少 SQL 编写量 |
| MySQL | 8.0 | 5.7 也能跑,推荐 8.0 |
| 前端模板 | Thymeleaf | SpringBoot 默认模板引擎,前后端未分离 |
| 数据库连接池 | HikariCP | SpringBoot 默认集成 |
版本需要根据你的项目实际情况调整,本文示例以 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_HOME=C:\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.10+11-LTS-120) Java HotSpot(TM) 64-Bit Server VM (build 17.0.10+11-LTS-120, mixed mode, sharing)Mac 下安装 JDK17
推荐使用 Homebrew:
brew install openjdk@17安装完成后需要做符号链接,否则java -version可能读不到:
sudo ln -sfn $(brew --prefix)/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk多版本切换
如果你电脑上同时有 JDK8 和 JDK17,建议使用 JEnv(Mac/Linux)或手动切换 JAVA_HOME(Windows)。不要盲目卸载旧版本,有些老旧项目仍然需要 JDK8 才能编译。
3. 系统功能与数据库设计
3.1 功能模块梳理
采购管理系统这个名字比较宽泛,不同场景有不同的功能范围。为了做一个“能答辩”的项目,我建议覆盖以下核心模块:
- 供应商管理:维护供应商基本信息,包括联系人、电话、地址、合作状态。
- 采购订单管理:创建采购订单,填写订单编号、供应商、采购日期、订单状态。
- 订单明细管理:每个订单关联多个采购商品,记录商品名称、数量、单价、小计。
- 商品管理:维护商品档案,包括商品编码、名称、规格、单位。
- 入库管理:采购订单到货后执行入库操作,增加库存数量。
- 库存管理:查看当前库存数量,支持库存预警提示。
- 用户登录与权限:简单的登录功能,区分管理员和普通操作员。
业务逻辑上,核心的流程是:
创建供应商 → 创建商品档案 → 创建采购订单 → 添加订单明细 → 审核订单 → 收货入库 → 更新库存这样的一组功能闭环,比单纯堆 CRUD 更有说服力。面试时,你可以围绕“订单状态如何流转”“入库时如何防止超卖”这类问题展开。
3.2 数据库表设计
数据库设计是面试高频考点,这个项目至少要保证三张核心表之间的外键关系清晰:供应商表、采购订单表、采购订单明细表。再加上商品表和用户表,组成一套完整业务模型。
下面给出核心表结构的 SQL,这里使用 MySQL 为例。
供应商表supplier
CREATE 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 '供应商表';商品表product
CREATE 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_order
CREATE 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_item
CREATE 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_user
CREATE 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.example,Artifact 为purchase。 - 指定 Java 版本为 17。
- 选择依赖:Spring Web、Thymeleaf、MyBatis Framework、MySQL Driver、Lombok。
如果你是使用 Maven 手工创建,可以在pom.xml中加入以下核心依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这里特别说明一下 MyBatis-Plus 的坐标:Spring Boot 3 需要引入mybatis-plus-spring-boot3-starter,而不是旧版的mybatis-plus-boot-starter,否则启动时会因为包版本问题抛异常。
4.2 编写核心配置文件
文件路径:src/main/resources/application.yml
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/purchase_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/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设置为 StdOutImpl,SQL 会打印在控制台,方便调试。- 密码和具体数据库名需要换成你自己的。
4.3 编写实体类
以订单和订单明细为例,使用 Lombok 简化 getter/setter。
文件路径:src/main/java/com/example/purchase/entity/PurchaseOrder.java
package 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.java
package 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.java
package 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 BaseMapper<PurchaseOrder> { }同样的方式创建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") List<Map<String, Object>> selectSupplierTotal();4.5 编写 Service 层
Service 层是核心业务逻辑所在。以订单创建为例,这里需要做的事:
- 生成订单编号。
- 保存订单主表。
- 遍历明细列表,保存每个明细。
- 计算订单总金额并更新。
文件路径:src/main/java/com/example/purchase/service/impl/PurchaseOrderServiceImpl.java
package 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 ServiceImpl<PurchaseOrderMapper, PurchaseOrder> implements PurchaseOrderService { @Autowired private PurchaseOrderItemMapper purchaseOrderItemMapper; @Override @Transactional(rollbackFor = Exception.class) public void createOrder(PurchaseOrder order, List<PurchaseOrderItem> 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.java
package 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) { QueryWrapper<PurchaseOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("create_time"); List<PurchaseOrder> 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 lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-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:each="order : ${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("只有已审核状态的订单才能入库"); } // 查询订单明细 QueryWrapper<PurchaseOrderItem> itemQuery = new QueryWrapper<>(); itemQuery.eq("order_id", orderId); List<PurchaseOrderItem> 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: 17 | IDEA 未配置 JDK17 编译环境 | 检查 Project Structure 中的 SDK 和 Language Level,调为 17 |
Failed to configure a DataSource | 数据库配置缺失或连接失败 | 检查application.yml的 url、用户名、密码,确认 MySQL 已启动 |
Access denied for user 'root'@'localhost' | 密码错误或权限不足 | 核对数据库密码;为项目单独创建最小权限账号 |
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 use | 8080 端口被其他进程占用 | 换端口,在 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 和 JDK17,IDEA 仍然默认使用旧版本。解决办法:
- 点击菜单
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 开发的方式。