ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot汽配销售系统实战:版本选型、MyBatis-Plus与事务并发控制

2026/9/18 2:32:19 拓冰建站 浏览量
Spring Boot汽配销售系统实战:版本选型、MyBatis-Plus与事务并发控制 简介Springboot汽车配件销售管理系统毕业论文.doc是一份面向高校计算机相关专业毕业设计场景的完整论文资料围绕汽车配件在线销售系统的需求分析、系统设计、数据库设计与实现展开适合正在做Springboot课题或需要参考B/S架构项目文档的学生使用。论文以JavaSpringbootMySQL为主要技术栈系统采用浏览器/服务器模式详细阐述了商品管理、订单处理、用户管理等核心模块以及系统安全性和数据保护设计。文档结构完整包含中英文摘要、目录、绪论、关键技术介绍和具体实现章节可辅助读者理解从选题、需求分析到技术选型与编码实现的完整流程并可直接作为论文格式与章节组织的参照。资源包仅含1个doc文档大小4.97MB体量精简便于阅读。已有78人下载学习适合用于毕业设计选题参考、系统方案验证或论文结构梳理。1. Spring Boot 汽车配件销售管理系统这个题难点不在增删改查汽车配件销售管理系统在 Java 后端方向的毕业设计里出现频率很高原因不是功能复杂而是它把管理系统最典型的节点都覆盖全了配件主数据、进销存、订单、客户等级和销售统计。论文答辩时被追问最多的往往不是页面长什么样而是「Spring Boot 是怎么把这一套组织起来的」「下单时怎么保证库存不会变成负数」。下面按实际开发顺序走一遍先解决 Spring Boot 版本和工程骨架再落到数据表设计与 MyBatis-Plus 条件查询然后处理订单与库存联动的事务和并发控制最后给出答辩前的验证与统计报表技巧。代码都能直接照着改适合正在做毕设的读者也适合要把 SpringMVC 旧工程改造到 Spring Boot 上的从业者。2. 初始化 Spring Boot 工程版本、JDK 与自动装配的取舍2.1 Spring Boot 版本太高怎么办2.7.18 与 3.x 怎么选最近被问得最多的问题就是「idea 不能创建 springboot 项目不能使用 jdk1.8」本质是同一个坑Spring Initializr 现在默认生成 Spring Boot 3.x而 3.x 强制 JDK 17 以上Servlet 相关包名也从 javax.* 改成了 jakarta.*。实验室或教师模板如果还停在 JDK 1.8直接跑 3.x 工程会碰上一堆编译错误。热词里频繁出现的「springboot 版本太高」说的就是这件事。我一般会把版本选在 Spring Boot 2.7.18它是 2.x 分支最后一个版本支持 JDK 8/11/17教科书代码和网上大量老教程不需要改 import 就能跑做毕设的兼容性最好。3.x 不是不能用要看周边依赖是否同步升级比如 MyBatis-Plus 在 Boot 3 下要引入 mybatis-plus-spring-boot3-starter 而不是 mybatis-plus-boot-starter光是这一点就够折腾半天。选型对比如下对比点Spring Boot 2.7.18Spring Boot 3.2.x最低 JDK817Servlet 包名javax.*jakarta.*与老教材/老代码兼容性好差import 要批量改官方开源维护2023 年底结束持续维护选型结论如果导师或运行环境锁死 JDK 1.8就选 2.7.18如果是新机器可以自由装 JDK再考虑 3.x。这个题是单数据源、单模块结构2.7 完全够用不用为了追新把时间花在踩版本坑上。后面如果导师要求演示多数据源再引入 dynamic-datasource-spring-boot-starter那不是这个题默认要做的内容。2.2 IDEA 创建 Spring Boot 工程的最小步骤创建步骤按下面的顺序执行能少踩一半的坑IDEA 里 New Project左侧选 Spring InitializrType 选 MavenPackaging 选 Jar。Server URL 默认 start.spring.io 拿到的模板基本是 3.x想要 2.x把 Server URL 换成阿里云镜像 https://start.aliyun.com。JDK 下拉框选本机的 1.8列表里没有就进 Project Structure 先添加本地 JDK 路径。依赖先只勾 Spring Web其余后面手动加避免生成一堆用不到的 starter。说明一下第 2 步镜像源只影响模板生成不影响你后面手动改 pom。就算生成的还是 3.x把 parent 版本改成 2.7.18 再 Maven Reload 一样可以跑所以这一步卡住不用慌。第 3 步对应的是「idea 不能创建 springboot 项目不能使用 jdk1.8」的常见场景本质是 IDEA 没识别到本地 JDK而不是项目创建失败。2.3 最小可运行的 pom 依赖与 application.yml 配置pom 里除了 parent真正要的依赖其实很少parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesstarter-web 提供内嵌 Tomcat、Spring MVC 和 Jackson 序列化mybatis-plus-boot-starter 是持久层3.5.3.1 与 Boot 2.7 兼容稳定常规 CRUD 不需要自己写 XMLmysql-connector-java 不写版本号由 Spring Boot parent 统一管理成 8.0.33lombok 用来省掉实体类 getter/setter。然后是配置文件server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/parts_sales?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0连接串里的 parts_sales 要先建好建库语句一行搞定CREATE DATABASE parts_sales DEFAULT CHARSET utf8mb4。serverTimezoneAsia/Shanghai 是 MySQL 8 驱动的常见要求不加启动时会报时区相关的连接错误map-underscore-to-camel-case 让 part_code 自动映射到实体字段 partCode不用给每个字段写 TableFieldlog-impl 打开后每条 SQL 都打印在控制台答辩演示时直接能看见数据是怎么查的logic-delete-field 配合第 3 章的实体注解做逻辑删除代码里就不会出现物理删除语句。2.4 启动类与自动装配原理的验证主类三行代码SpringBootApplication public class PartsSalesApplication { public static void main(String[] args) { SpringApplication.run(PartsSalesApplication.class, args); } }SpringBootApplication 是三个注解的合体SpringBootConfiguration 标记配置类EnableAutoConfiguration 开启自动装配ComponentScan 扫描当前包及子包的 Bean。自动装配的原理链是启动时读取自动配置类名单2.7 同时兼容 META-INF/spring.factories 和新的 AutoConfiguration.imports 两种文件3.0 起只认后者拿到名单后逐个检查 ConditionalOnClass、ConditionalOnProperty 这类条件注解classpath 和配置里有对应依赖才创建 Bean。答辩被问「springboot 自动装配原理」时按「读取名单 条件判断 注册 Bean」这个链路讲比背概念有用。跑起来后控制台出现 Tomcat started on port 8080工程骨架就算通了。3. 配件主数据与 MyBatis-Plus表设计、实体映射和分页查询3.1 覆盖进销存闭环的 6 张核心表先想清楚表再写代码。我把这个题的库拆成 6 张表part 配件表负责主数据和当前库存supplier 供应商表和 customer 客户表是两个字典表sales_order 订单主表和 order_item 订单明细表拆开是因为一单可以买多个配件而统计「单个配件销量」和「单张订单金额」的查询路径完全不同拆开之后各查各的stock_log 库存流水表记录每一次出入库论文里的库存审计章节靠它撑起来。表名职责关键字段part 配件表配件主数据与当前库存part_code、part_name、sale_price、stocksupplier 供应商表采购来源supplier_name、contact_phonecustomer 客户表销售对象customer_name、levelsales_order 订单主表一次销售行为order_no、customer_id、total_amount、statusorder_item 订单明细表一单买了哪些配件order_id、part_id、quantity、unit_pricestock_log 库存流水表出入库审计part_id、change_type、quantity、order_id订单状态 status 用 int 存0 待付款、1 待发货、2 已完成、3 已取消不要在数据库里直接存「已发货」这种中文字符串枚举值在 Java 里定义展示层再去翻译。这样的设计在论文「数据库设计」章节里能写清楚每个字段的职责和关联关系。3.2 配件表的建表 DDL 与字段设计理由CREATE TABLE part ( id bigint NOT NULL AUTO_INCREMENT, part_code varchar(32) NOT NULL COMMENT 配件编码, part_name varchar(64) NOT NULL COMMENT 配件名称, brand varchar(32) DEFAULT NULL COMMENT 品牌, model varchar(64) DEFAULT NULL COMMENT 适用车型型号, spec varchar(64) DEFAULT NULL COMMENT 规格参数, sale_price decimal(10,2) NOT NULL COMMENT 销售单价, purchase_price decimal(10,2) DEFAULT NULL COMMENT 进货价, stock int NOT NULL DEFAULT 0 COMMENT 当前库存, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除标记 0正常 1删除, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_part_code (part_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT配件表;价格必须用 decimal(10,2) 而不是 float/double二进制浮点数在金额累加时会出现精度误差金额是敏感数据只能用定点数。part_code 加唯一索引既防止重复录入也服务按编码查配件的接口。存储引擎用 InnoDB因为第 4 章的行锁和事务只有 InnoDB 支持utf8mb4 保证品牌名里的生僻字不变成问号。create_time 交给数据库默认值应用层插入时不传MyBatis-Plus 默认只把非 null 字段拼进 INSERT 语句所以这个字段不会变成 null。3.3 实体类、Mapper 与 Service 的标准三层实体类用 Lombok 和 MyBatis-Plus 注解Data TableName(part) public class Part { TableId(type IdType.AUTO) private Long id; private String partCode; private String partName; private String brand; private BigDecimal salePrice; private Integer stock; TableLogic private Integer deleted; }TableName 指定表名TableId 声明主键自增deleted 字段加 TableLogic 后所有 select 自动追加 where deleted 0所有 delete 自动变成 update 置 1配合 2.3 里 global-config 的配置代码里再也见不到物理删除。字段名 part_code 到 partCode 的映射由 camel-case 配置完成。接着是 Mapper 和 Servicepublic interface PartMapper extends BaseMapperPart { } public interface PartService extends IServicePart { } Service public class PartServiceImpl extends ServiceImplPartMapper, Part implements PartService { }BaseMapper 自带 insert、updateById、selectById、selectPage 等十几个方法常规单表 CRUD 到这里就够Service 层继承 IService 是为了拿 saveBatch 和 lambdaQuery前者在 Excel 批量导入配件时有用后者让条件查询不用手写字符串列名编译期就能发现字段拼写错误。3.4 用 LambdaQueryWrapper 做多条件分页查询配件管理页面最常见的三个筛选条件是配件名称模糊、品牌精确、编码前缀匹配Controller 里这样写GetMapping(/page) public ResultIPagePart page(RequestParam(defaultValue 1) Integer current, RequestParam(defaultValue 10) Integer size, String partName, String brand, String partCode) { LambdaQueryWrapperPart wrapper Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(partName), Part::getPartName, partName) .eq(StringUtils.hasText(brand), Part::getBrand, brand) .likeRight(StringUtils.hasText(partCode), Part::getPartCode, partCode) .orderByDesc(Part::getId); return Result.success(partService.page(new Page(current, size), wrapper)); }eq 和 like 的第一个 boolean 参数是开关条件为空时整段 SQL 条件直接跳过不需要手写 if 分支。这里的 StringUtils 是 Spring 的 org.springframework.util.StringUtilshasText 能同时过滤 null 和纯空格。likeRight 生成 like ABC% 前缀匹配配件编码如果设计成有规则的这种查询能走索引。Result 是统一返回体按 {code, msg, data} 的结构自己封装即可。注意MyBatis-Plus 的分页不是开箱即用必须注册拦截器否则 page 对象里的 total 永远是 0SQL 也没有 LIMIT。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个配置漏掉是 MyBatis-Plus 使用者最常踩的坑答辩时如果老师问「分页怎么实现的」答「MybatisPlusInterceptor 里的 PaginationInnerInterceptor 在语句执行前拼接 LIMIT」就可以了不用扯太多底层。4. 下单扣库存事务、行锁与库存流水是核心业务闭环4.1 为什么订单和库存必须在一个事务里订单流程拆开看是四步校验库存、计算总价、写订单主表和明细、扣减库存。如果这四步不在一个事务里就会出现「订单写进去了库存没扣」或者反过来。Transactional(rollbackFor Exception.class) 里的 rollbackFor 不能省默认行为是只对 RuntimeException 回滚自定义的检查异常不会触发回滚。事务的边界是方法入口方法内任何一个步骤抛异常前面所有写操作全部撤销。4.2 一段可以直接复制的下单方法Override Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateReq req) { BigDecimal total BigDecimal.ZERO; ListOrderItem items new ArrayList(); for (OrderItemReq item : req.getItems()) { Part part partMapper.selectByIdForUpdate(item.getPartId()); if (part null || part.getStock() item.getQuantity()) { throw new BizException(配件库存不足 item.getPartId()); } OrderItem od new OrderItem(); od.setPartId(item.getPartId()); od.setQuantity(item.getQuantity()); od.setUnitPrice(part.getSalePrice()); items.add(od); total total.add(part.getSalePrice() .multiply(BigDecimal.valueOf(item.getQuantity()))); } SalesOrder order new SalesOrder(); order.setOrderNo(SO System.currentTimeMillis()); order.setCustomerId(req.getCustomerId()); order.setTotalAmount(total); order.setStatus(0); salesOrderMapper.insert(order); for (OrderItem od : items) { od.setOrderId(order.getId()); orderItemMapper.insert(od); partMapper.deductStock(od.getPartId(), od.getQuantity()); } return order.getId(); }对应的两个 Mapper 方法用注解写 SQL不用建 XMLSelect(SELECT * FROM part WHERE id #{id} FOR UPDATE) Part selectByIdForUpdate(Long id); Update(UPDATE part SET stock stock - #{qty} WHERE id #{id} AND stock #{qty}) int deductStock(Long id, Integer qty);OrderCreateReq 里放 customerId 和 items 列表OrderItemReq 里放 partId 和 quantity。selectByIdForUpdate 里的 FOR UPDATE 是行级排他锁事务提交前其他事务对同一行的这类查询会阻塞等待所以第 1 步校验和第 3 步扣减之间不可能插进并发下单。deductStock 的 where 条件里带 stock #{qty} 是第二道保险返回影响行数为 0 就说明库存已经被别的事务改掉了。金额计算全程用 BigDecimaltotal 从 BigDecimal.ZERO 开始累加订单号先用时间戳论文阶段够用。BizException 是自定义业务异常配一个 RestControllerAdvice 统一返回错误信息即可。提示FOR UPDATE 只有在事务里才有效。方法没加 Transactional 时查询一结束排他锁就释放后面的扣减等于裸奔这是并发 bug 最常见的来源。4.3 三种并发方案的对比与答辩话术毕设答辩里「库存扣成负数怎么防」是必问题把方案对比说清楚比背答案加分方案是否阻塞适用场景局限synchronized 锁 Service 方法阻塞单机演示集群部署就失效SELECT ... FOR UPDATE 行锁阻塞单库订单系统并发高时串行化乐观锁条件更新不阻塞高并发抢购失败要重试这里选 FOR UPDATE 的理由可以这样讲行锁由数据库保证和部署几台机器无关synchronized 锁在单个 JVM 进程内集群或换容器后锁就没了。乐观锁适合「失败重试成本低」的场景订单系统里每单要写主表、明细、流水好几个表重试成本高阻塞方案更稳。4.4 库存流水与 Redis 读缓存每次扣减库存时顺带往 stock_log 写一条流水change_type 1 出库、2 入库带 order_id 关联订单。对账时用一条聚合 SQL 计算流水账面库存SELECT part_id, SUM(CASE WHEN change_type 1 THEN - quantity ELSE quantity END) AS stock_balance FROM stock_log GROUP BY part_id;把这条 SQL 的结果和 part.stock 对比能验证程序里所有出入库是否一致答辩现场演示这个对账比口头解释有说服力。至于 redis 在 springboot 中的使用读多写少的配件详情接口可以做一层缓存查询时先读 StringRedisTemplate 里的 part:detail:{id}没有则查库并写缓存 30 分钟下单扣库存成功后再把对应 key 删掉避免读到旧库存。缓存是 DB 正确性之上的优化先把 4.2 的事务做对再谈缓存这个顺序本身就是答辩想听的完整思路。5. 答辩前要跑通的验证闭环接口用例、统计 SQL 和版本回退5.1 用一组 Postman 用例覆盖核心链路提交前把系统当接口项目验证一遍用例固定成五步登录接口拿 token 并放到 Authorization 请求头查询配件分页接口创建一个两件配件的订单再查一次配件确认 stock 减少了用超出库存的数量下单断言返回「库存不足」提示。这五步跑通查、增、改、并发保护、异常路径就都覆盖了。登录校验用拦截器实现拦截器里解析 token 并把当前用户放进 ThreadLocalController 里随时取操作人这套写法在管理系统里最常见。调试时打开 2.3 里的 StdOutImpl控制台能直接看每一条 SQL 的执行顺序比断点调试更直观。5.2 销售统计一条聚合 SQL 直接对接 ECharts统计页面的销售额和订单数按月份聚合即可SELECT DATE_FORMAT(o.create_time, %Y-%m) AS month, COUNT(DISTINCT o.id) AS order_count, SUM(o.total_amount) AS sales_amount FROM sales_order o WHERE o.status IN (1, 2) GROUP BY DATE_FORMAT(o.create_time, %Y-%m) ORDER BY month;月份格式化成 %Y-%m柱状图 x 轴直接用WHERE 里限定 status 只统计已发货和已完成避免把取消订单算进销售额。后端返回 ListSpring Boot 自动转 JSON前端如果用了 Vue 加 ECharts直接把数组塞给 series.data 就行。前后端分离场景下接口边界就停在这里不要在 SQL 里做前端该做的事。5.3 版本回退的兜底操作如果机器只有 JDK 21 而论文要求 1.8回退动作按顺序执行pom 的 parent 版本改回 2.7.18全局搜索 import javax. 开头的语句3.x 工程要改成 jakarta.IDEA 里 Project Structure 把 Project SDK 和 Modules 的 Language level 都切成 1.8最后 Maven Reload。改完先跑 mvn clean compile把编译错误一次性列出来再逐个修这一步能省掉反复启动应用的时间。数据库初始化脚本放进 resources/db 目录答辩前用一条命令删库重建保证演示环境干净可复现也能在老师要求换一台机器演示时最快恢复现场。本文还有配套的精品资源点击获取