ARTICLE DETAIL

资讯详情

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

Spring Boot+MySQL家具电商平台:从建表到下单全链路实战解析

Spring Boot+MySQL家具电商平台:从建表到下单全链路实战解析 简介这套基于Spring Boot和MySQL的家具销售电商平台项目配套完整源码与设计文档面向毕业设计、课程设计以及Java Web学习者可快速掌握电商系统的需求分析、数据库设计与前后端交互方式。压缩包大小约24.89MB内含Spring Boot典型分层源码、设计说明文档与演示文稿、readme说明、开发环境配置清单目录划分清楚适合按模块阅读和二次开发。目前已有537人学习浏览是同类课程设计中较常被参考的项目之一。项目覆盖用户注册登录、家具商品浏览检索、购物车管理、订单提交处理以及后台商品与订单维护等核心功能。文档中提供系统需求分析、数据库表结构、接口设计等内容可深入了解Spring Boot的自动配置与业务逻辑封装以及MySQL对用户、商品、订单等数据的持久化方案。跟着项目完整走一遍既能锻炼电商平台的整体架构能力也为课程答辩和后续扩展提供了实用范本。1. 基于Spring BootMySQL的家具销售电商平台一张订单表背后藏着完整Web开发链路Spring BootMySQL的家具销售电商平台在毕业设计里属于“铁打的选题”那一类需求好解释模块数量适中做完能覆盖Web开发常用的建表、查询、事务、文件上传、权限控制全链路。它解决的问题也很直接——用户进前台浏览家具、搜索型号、加购物车、下订单管理员进后台维护商品、处理订单状态数据库用MySQL存商品和订单后端用Spring Boot把这一切串成接口。难点不在单个功能而在于模块之间的数据一致性比如并发下库存会不会超卖、订单生成一半失败了怎么办。下面按我实际做过的顺序来讲先建表、再搭工程、写核心交易、列常见的翻车点最后给一份可以直接照做的验证清单。想拿这套源码和文档练手或者准备答辩的照着推就能跑通。2. 系统设计先行五张核心表怎么建Spring Boot工程怎么搭2.1 为什么这个选题偏好Spring Boot MySQL选型逻辑与版本坑Spring Boot在这个选题里的角色很明确它把“写接口”这件事的成本降到最低。内嵌Tomcat意味着不用单独装容器starter依赖帮你把数据源、JSON处理、参数校验这些常用组件自动装配好一个main方法启动就能跑。对以“做一个能稳定演示的系统”为目标的场景来说这个特性比性能更重要毕竟课堂上没人关心并发上限大家关心的是“别在我演示的时候崩”。MySQL负责把数据落下来。家具、用户、购物车、订单这几类数据结构稳定、关系清晰用关系型数据库存天然合适。更重要的是社区资料极其丰富小到mysql安装教程大到MySQL 5.7.44安装过程的详细步骤都能搜到出问题不愁没人踩过。相比NoSQLMySQL的SQL语法是通用技能文档里写“系统采用MySQL存储业务数据”也更好答辩。组合本身不难版本配对才是第一个坑。如果你用IntelliJ IDEA社区版做Spring Boot开发它没有Spring Initializr入口需要去start.spring.io生成工程再导入这不影响后续开发。真正影响的是JDK和Boot版本JDK8配Spring Boot 2.7.xJDK17以上配Spring Boot 3.xMySQL连接驱动也要和服务端版本对应。很多旧教程里的驱动类名是com.mysql.jdbc.Driver那是5.x的老写法连MySQL 8.0会直接报认证相关错误。这条展开说就是黑匣子所以我一般直接用2.7.18 mysql-connector-j 8.x这套组合报错都能搜到答案。2.2 五张核心表家具、用户、购物车、订单、订单明细这样建先建库。字符集一定用utf8mb4别用utf8否则商品名称里带个特殊符号就会在插入时报错。引擎用InnoDB只有它能撑住后面的事务回滚。建表SQL是这套系统的地基一次建对能省后面大量改表时间。CREATE DATABASE IF NOT EXISTS furniture_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE furniture_mall; CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 建议存BCrypt密文别存明文, phone VARCHAR(20), address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE furniture ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image VARCHAR(255) COMMENT 图片URL或磁盘上传路径, description TEXT, sales INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE cart_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, furniture_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, checked TINYINT DEFAULT 1 COMMENT 是否勾选下单, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_furniture (user_id, furniture_id) ) ENGINEInnoDB; CREATE TABLE orders ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, receiver_name VARCHAR(50), receiver_phone VARCHAR(20), receiver_address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME ) ENGINEInnoDB; CREATE TABLE order_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, furniture_id BIGINT NOT NULL, furniture_name VARCHAR(100), price DECIMAL(10,2) COMMENT 下单时的成交价快照, quantity INT NOT NULL, subtotal DECIMAL(10,2) ) ENGINEInnoDB;逻辑说明这五张表是电商系统最常见的划分方式。user存账号和收货信息furniture存商品cart_item承载“用户和家具”的多对多关联orders是订单主表记录整单金额、状态、收货地址order_item是订单明细表记录每个家具的下单信息。两个细节要单独讲。第一order_item里为什么冗余furniture_name和price家具名称和价格以后都可能改但历史订单必须保留成交的那一刻信息这叫订单快照。如果只存furniture_id商品改名改价后你的订单数据也跟着变这在数据一致性上是硬伤。第二orders为什么用status整数字段而不是一堆布尔值订单至少有“待支付、已支付、已发货、已完成、已取消”五个状态一个int字段既能按状态筛选也方便以后扩展成状态机。这种字段设计写在文档里比单纯贴需求有说服力得多。参数说明price和total_amount这类型必须用DECIMAL(10,2)意思是最大10位数字、小数占2位刚好覆盖万元级家具价格。stock用INT并给默认值0避免空指针。create_time用DATETIME DEFAULT CURRENT_TIMESTAMP这样插入数据时不用手动塞时间。另外外键我建议不要建物理外键用逻辑外键就够了——毕设阶段物理外键会让删除操作变麻烦初始化导入数据时还容易因外键约束失败。表之间靠user_id、furniture_id、order_id这些字段关联就行。2.3 工程骨架pom.xml与application.yml一次性配好建表后搭工程。常见做法是去start.spring.io生成Maven工程依赖选Spring Web、MyBatis Framework、MySQL Driver三个生成后直接导入IDEA。dependency部分建议写成这样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 groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies逻辑说明Spring Boot 2.7.x的父依赖已经管理了mysql-connector-j的版本所以不用写version。注意artifactId是mysql-connector-j而不是mysql-connector-java后者是8.0.31之前的老坐标新项目直接用前者。mybatis-spring-boot-starter不归Boot管理必须自己写版本号2.3.2配Boot 2.7.x是经过验证的组合换3.x版本时这个starter也要跟着升到3.x。接下来是application.yml这是整个项目的“总闸门”配置错了应用直接起不来。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/furniture_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.furniture.entity参数说明serverTimezoneAsia/Shanghai解决MySQL 8.0时区问题不加的话数据库时间会比北京时间差8小时useSSLfalse避免本机连接时SSL握手警告allowPublicKeyRetrievaltrue是MySQL 8.0的caching_sha2_password认证必须项不加会直接报Public Key Retrieval is not allowed。机器上MySQL端口不是3306时url里顺手改掉。multipart配置是给家具图片上传预留的限制单文件10MB。mybatis.mapper-locations这行提前配好否则后面写XML时MyBatis默认扫描不到接口全报Invalid bound statement。验证骨架直接运行主类看到Tomcat started on port 8080就是成功如果报Access denied优先检查用户名密码如果报Public Key检查url那三个参数是否齐全。到这里数据层和运行环境就绪可以开始写业务链路了。3. 核心交易链路商品浏览、购物车、下单扣库存的代码实现3.1 商品列表从Mapper到Controller的最小查询链路商品模块是前台的门面。典型接口是分页查询传pageNum、pageSize和可选关键词返回家具列表。常见做法是Controller→Service→Mapper三层Mapper用XML写SQL比起注解方式更便于后期加复杂查询。先建实体类Furniture字段与表对齐价格用BigDecimal。Mapper接口这样写Mapper public interface FurnitureMapper { ListFurniture selectPage(Param(keyword) String keyword, Param(offset) int offset, Param(pageSize) int pageSize); Furniture selectById(Param(id) Long id); }XML里对应SQLselect idselectPage resultTypecom.example.furniture.entity.Furniture SELECT id, name, category, price, stock, image, description, sales, status FROM furniture where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if AND status 1 /where ORDER BY sales DESC LIMIT #{offset}, #{pageSize} /select逻辑说明where标签会自动处理条件拼接keyword为空时把AND去掉ORDER BY sales DESC是电商列表的默认排序销量高排前面这也是MySQL排序在业务里的常见用法LIMIT做物理分页offset计算是(pageNum-1)*pageSize。条件值全部用#{}预编译占位符避免SQL注入如果写成${}直接拼接用户传keyword时就能注入恶意SQL这个点经常在答辩时被追问。Controller接口RestController RequestMapping(/api/furniture) public class FurnitureController { Resource private FurnitureService furnitureService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword) { return Result.success(furnitureService.queryPage(pageNum, pageSize, keyword)); } }参数说明defaultValue让前端不传页码也能跑keyword用requiredfalse表示搜索词可选Result是统一返回包装类里面固定code、msg、data三个字段接口结构稳定后前端对接不用猜。到这里浏览器里直接访问/api/furniture/list?keyword沙发就能验证列表。前台页面如果不想单独写前端用Thymeleaf模板渲染也行但现代毕设更常见的是前端用Vue或原生HTML调JSON接口后端只管出数据。我习惯保留Thymeleaf只做后台管理页前台全部走JSON这样文档里能多写一节“前后端交互设计”。3.2 购物车操作先查再插不如一条SQL解决购物车存储有两种常见做法。一是Redis以userId为key存家具id和数量读写快、过期自动清理二是直接落MySQL用cart_item表。对这套系统我的建议是落库。理由很现实源码里多一张表的设计能写进系统设计文档还能演示关联查询最关键的是不用额外部署Redis降低运行环境复杂度。如果导师要求体现“新技术”再单独把Redis作为升级点写一章那是加分项而不是必选项。加购接口最常见的问题是重复加购同款家具。教科书写法是先SELECT判断再决定UPDATE还是INSERT但这条路径在并发下可能插出重复记录。更稳的写法是靠建表时的唯一索引配合一条MySQL语法INSERT INTO cart_item(user_id, furniture_id, quantity) VALUES(#{userId}, #{furnitureId}, #{quantity}) ON DUPLICATE KEY UPDATE quantity quantity VALUES(quantity)逻辑说明user_id和furniture_id有联合唯一索引第一次插入正常执行再买同款时触发唯一键冲突MySQL自动执行UPDATE把数量累加。从两条SQL变成一条同时消除了“查询到插入”之间的时间差这是并发场景下非常典型的优化思路。查购物车时不能只返回cart_item裸数据要连表把家具名称、图片、单价一起取出来前端才能直接渲染SELECT c.id, c.furniture_id, c.quantity, f.name, f.price, f.image FROM cart_item c JOIN furniture f ON c.furniture_id f.id WHERE c.user_id #{userId}参数说明JOIN查询避免逐条查商品的N1问题一次拿全WHERE用user_id筛当前用户。删除购物车项时接口要带上user_id条件防止用户A通过猜测id删掉用户B的数据这是接口越权的基础防范。前端每次勾选商品时同步更新checked字段下单逻辑只处理勾选中的条目。这个字段看着简单实际是把“交互状态”落进数据模型系统设计的文档里写一笔会加分。3.3 下单与扣库存Transactional事务这样写才不回滚下单是整套系统里最需要谨慎的接口它牵扯四件事校验购物车、扣库存、生成订单、清空购物车。任何一步失败前面步骤都必须撤销否则会出现“订单没生成但库存被扣”或者“订单生成了但库存没扣”的数据不一致。这就要靠数据库事务Spring里用Transactional声明。Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId) { ListCartItemVO items cartMapper.selectCheckedItems(userId); if (items.isEmpty()) { throw new BusinessException(购物车中没有选中商品); } String orderNo F System.currentTimeMillis() userId; BigDecimal total BigDecimal.ZERO; for (CartItemVO item : items) { int rows furnitureMapper.deductStock(item.getFurnitureId(), item.getQuantity()); if (rows 0) { throw new BusinessException(商品[ item.getFurnitureName() ]库存不足); } total total.add(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); } orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); orderItemMapper.batchInsert(order.getId(), items); cartMapper.clearCheckedItems(userId); return new OrderVO(orderNo, total); }库存扣减SQL是重点update iddeductStock UPDATE furniture SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity} /update逻辑说明deductStock里AND stock #{quantity}把“检查库存”和“扣库存”合并成一个原子操作。MySQL执行UPDATE时会对命中的行加行锁其他线程的扣减必须等锁释放这就从机制上保证了并发下不会超卖。如果库存不够UPDATE影响行数为0代码里rows0就抛异常触发回滚。关于MySQL锁的分类行锁、间隙锁在面试里会被问但实现层面InnoDB在UPDATE时已经自动加了行锁课设阶段不需要手动SELECT FOR UPDATE写多了反而影响并发性能。事务要生效有三件事必须检查。第一createOrder方法不能被同类内部调用比如Controller调OrderService.createOrder没问题但OrderService另一个方法里调this.createOrder()Transactional就失效了因为Spring代理对象没介入第二异常不能被try/catch吞掉有人习惯在方法里catch Exception打日志事务感知不到失败就直接提交了数据对不上查半天才发现第三rollbackFor Exception.class必须写明因为Spring默认只回滚RuntimeException如果你自定义的BusinessException是受检异常默认情况下抛出去也不会回滚。这三点是血泪经验尤其第二条踩过一次就长记性。订单号用时间戳加userId拼接够课设级别但同一毫秒并发下单可能重复稳妥做法是加随机数或走Redis自增。这里不展开知道有这个隐患即可。4. 避坑排查Spring BootMySQL项目最容易翻车的5个问题这套系统里报错最多的不是业务逻辑而是环境、字段、SQL关键字这一类“看起来很小但能卡半天”的问题。下面五条按出现频率排每一条都给出现象、原因和解决步骤。4.1 启动就连不上数据库Access denied还是Public Key Retrieval现象应用启动时控制台报Access denied for user rootlocalhost或者报Public Key Retrieval is not allowed。原因Access denied说明用户名密码不对或MySQL 8.0默认使用caching_sha2_password认证而驱动版本与账号认证方式不匹配。Public Key Retrieval is not allowed则是url里没加allowPublicKeyRetrievaltrue。很多人用Docker起MySQL时还会遇到端口冲突宿主机3306被本地服务占着容器端口映射失败也会造成连不上。解决先确认密码本身是否正确注意按mysql安装教程配置root时可能设过特殊字符密码再确认连接的端口是不是本地实例的端口。认证方式问题可以在MySQL命令行执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;同时把url里的allowPublicKeyRetrievaltrue和serverTimezoneAsia/Shanghai一次配好两招一起用基本能根治。4.2 时间差8小时、金额多出一串小数时区、精度与字段类型现象数据库里create_time比实际时间晚8小时计算总价后出现0.30000000000000004这类浮点数尾巴。原因数据源url没指定serverTimezone驱动取了系统默认时区金额字段用了float或double浮点数二进制存储天然存在精度损失。解决url加serverTimezoneAsia/Shanghai即可修正时区。金额一律用BigDecimal接收数据库字段用DECIMAL(10,2)实体类属性也是BigDecimal从数据库到Java类型全程不用浮点类型。时间字段建议用LocalDateTime不要用java.util.DateMyBatis对LocalDateTime的映射更干净。MySQL 5.7和8.0在DATETIME默认值行为上也有差异8.0支持DEFAULT CURRENT_TIMESTAMP更友好这块在文档里可以提一句体现对比。4.3 表名和关键字撞车order、desc这种“合法”的表名现象建表语句不报错执行SELECT时却在order附近报语法错误提示You have an error in your SQL syntax。原因order是SQL的ORDER BY关键字直接当表名或字段名用MySQL解析时就会出错。desc、group也是高频撞车的名字。解决最省事的方案是建表时统一加前缀或直接避开关键字我在这套系统里把订单表命名为orders完美避开。如果已经在代码里大量使用orderSQL里可以用反引号包裹SELECT * FROM order;但反引号要贯穿所有SQL稍微漏一个就报错所以我建议直接改名代价最小。审查建表脚本时顺便把keyword这类字段名也扫一遍避免后天返工。4.4 加购同款家具出现重复行唯一索引与ON DUPLICATE KEY UPDATE现象同一用户把同一件家具加购两次购物车列表出现两条记录数量分别是1和1而不是一条数量为2的记录。原因代码实现是“先SELECT再INSERT”并发请求时两个请求都查到“不存在”然后各自插入成功就产生了重复数据。这是典型的竞态条件。解决建表时给(user_id, furniture_id)加唯一索引插入改用INSERT ... ON DUPLICATE KEY UPDATE从数据库层面兜底。如果线上已经产生脏数据先查出来再清理SELECT user_id, furniture_id, COUNT(*) FROM cart_item GROUP BY user_id, furniture_id HAVING COUNT(*) 1;把重复行合并成一条并更新数量再补唯一索引防止再次发生。这套“先清后堵”的思路在工作中同样适用。4.5 上传图片本地正常、打包后404静态资源映射的玄学现象IDEA里运行上传商品图片后能正常显示用mvn package打成jar再运行上传的图片刷新页面就404。原因Spring Boot默认静态资源目录是classpath:/static/本地运行时上传到项目target目录还能访问但jar包运行时classpath是只读的上传目录写不进jar默认映射也不覆盖外部磁盘路径。解决把图片上传到绝对路径比如/Users/xxx/furniture-mall/upload再注册一个资源映射配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }uploadPath从application.yml里读取换机器只改配置。注意addResourceHandler和addResourceLocations末尾的斜杠不能丢丢了就是这个配置静默失效属于典型的“看着没错但就是404”。这类问题排查时先看控制台有没有No mapping for GET日志有就优先怀疑映射配置。5. 从能跑到能演示验证清单、打包部署与三个升级方向5.1 一份可以直接照着点的验证清单模块操作步骤预期结果注册登录注册新账户并登录密码以密文入库登录成功进入前台商品浏览首页列表按销量排序搜索“沙发”能搜到结果且分页正常购物车同一件家具连续加购两次数量累加为2列表无重复行下单支付勾选商品去结算模拟支付库存扣减订单状态变为已支付后台管理下架一件商品前台列表立即看不到该商品订单流转后台发货前台查看订单订单状态从已支付变为已发货这套清单适合演示前5分钟快速过完任何一步不通说明链路里还有没合上的地方。5.2 打包部署从IDEA到java -jarmvn clean package -DskipTests java -jar target/furniture-mall-0.0.1-SNAPSHOT.jar-DskipTests跳过测试类避免有些环境test编译失败jar包名按pom里artifactId和version拼接。如果生产机用的是MySQL 5.7.44安装的实例驱动用mysql-connector-j 8.x一样兼容但url里驱动类名必须保持com.mysql.cj.jdbc.Driver不要改回老的com.mysql.jdbc.Driver。部署后验证一个接口curl http://localhost:8080/api/furniture/list?pageNum1pageSize10返回JSON就说明工程正常。5.3 三个值得做的升级方向第一个升级是登录认证。很多模板用session存登录态前后端分离时跨域处理很别扭可以换Spring Security或拦截器加JWT接口权限立刻清晰。第二个升级是性能。商品详情和首页列表加Redis缓存热点商品库存用Redis预热再用spring boot admin做监控看一眼内存和接口耗时就能定位瓶颈。第三个升级是前端重构把Thymeleaf模板换成Vue3Element Plus走真正的前后端分离接口文档和代码结构会更接近企业项目。最后说个教训做这类系统第一次跑通核心链路后立刻做完整备份源码和数据库都导出。我当年改购物车逻辑加字段时一个DROP语句把表删了幸好有备份否则一整天的调优全白费。用Git做本地提交也行这是成本最低的后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表