ARTICLE DETAIL

建站实战干货

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

Java超市购物系统实战:从三层架构到高并发订单处理

2026/9/5 13:37:06 拓冰建站 浏览量
Java超市购物系统实战:从三层架构到高并发订单处理 简介本资源是一套完整的Java超市购物系统实现方案面向Java初学者、课程设计学生及中小型项目开发者解决从需求分析到数据库落地、代码实现与文档配套的全流程学习需求。压缩包共165个文件含31个核心Java源码文件涵盖商品管理、订单处理、库存操作等业务逻辑、105个编译后class文件、8个界面图标PNG、5个依赖JAR包以及数据库文件mdf/ldf和详细文档doc、xls、html等整体大小2.48MB结构清晰便于按模块理解MVC分层与ORM集成实践。已有349人学习下载资源附带完整可运行工程包含登录主界面、商品入库/出库、销售结算、库存查询等典型功能类如MainFrame.class、Stock_Dialog.class、PaymentList.class并提供需求分析、数据库ER图说明、API接口说明及用户操作手册等多维度文档助读者快速掌握系统架构设计与Java企业级开发规范。1. 项目缘起为什么从零构建一个超市购物系统如果你正在学习Java或者准备面试大概率会听到一个建议“做个项目吧”。而“超市购物系统”几乎是每个Java初学者都会遇到的一个经典练手项目。它听起来简单无非是商品、购物车、订单、用户但真要动手做起来从数据库设计到业务逻辑实现再到前后端交互每一个环节都能让你踩到不少坑。我当年也是从这个项目入门的后来在带新人和面试时也看过无数个版本。今天我就以一个过来人的身份把这个项目掰开了、揉碎了从头到尾讲清楚。这不仅仅是一个能写到简历里的“项目经验”更是一个帮你串联起Java SE、数据库、Web开发核心知识的绝佳实践。很多人做这个项目最后就只得到一个能“跑起来”的界面数据库表设计得乱七八糟代码结构毫无章法文档更是无从谈起。这样的项目在面试官眼里价值几乎为零。我们这次的目标是构建一个结构清晰、代码规范、文档齐全、可扩展的超市购物系统。我会重点讲解那些容易被忽略的细节比如如何设计一个健壮的商品库存扣减逻辑如何处理高并发下的订单创建以及如何编写让后续维护者包括未来的你自己感恩戴德的详细文档。2. 核心架构设计三层架构与数据库选型在动手写第一行代码之前我们必须把架构想清楚。一个混乱的架构会让项目后期变成“屎山”加一点功能都困难重重。对于超市购物系统这类典型的业务管理系统经典的三层架构表现层、业务逻辑层、数据访问层依然是最实用、最清晰的选择。2.1 为什么是三层架构三层架构的核心思想是“分离关注点”。每一层只负责自己的事情层与层之间通过接口或定义好的模型进行通信。这样做的好处太多了易于维护和测试业务逻辑Service层独立于Web框架和数据库你可以单独对某个Service方法进行单元测试而不需要启动整个Web服务器。代码复用性高数据访问层DAO层封装了所有数据库操作任何需要操作商品表的地方都调用同一个ProductDao避免了SQL代码散落各处。团队协作清晰前端同学关心Controller和返回的JSON格式后端业务同学专注Service逻辑DBA或对性能有要求的同学可以优化DAO层的SQL。对于我们的超市系统分层可以这样规划表现层 (Controller)接收HTTP请求如/product/list解析参数调用对应的Service方法并将结果封装成JSON返回给前端如Vue、React或直接渲染视图如JSP、Thymeleaf。这一层应该很“薄”只做参数校验和格式转换。业务逻辑层 (Service)这是系统的“大脑”。它包含了所有的业务规则比如“用户下单时需要检查库存是否充足”、“计算订单总价时需要应用会员折扣”。Service层调用一个或多个DAO来完成业务并处理事务。事务管理通常在这一层声明确保比如“扣库存”和“生成订单”两个数据库操作要么一起成功要么一起失败。数据访问层 (DAO/Mapper)负责与数据库直接对话执行CRUD增删改查操作。我们使用MyBatis或JPA来简化这里的代码。这一层的方法通常非常直接一个方法对应一条SQL。2.2 数据库设计表结构是项目的基石数据库设计是后端项目的灵魂。一个糟糕的表设计会让后续所有开发都举步维艰。我们基于超市的核心业务流程来设计表以下是最核心的几张表1. 商品表 (product)这是系统的核心。设计时需要考虑扩展性。CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 商品ID主键, product_no varchar(32) NOT NULL COMMENT 商品编号唯一用于业务标识, name varchar(128) NOT NULL COMMENT 商品名称, category_id bigint(20) DEFAULT NULL COMMENT 分类ID, price decimal(10,2) NOT NULL COMMENT 销售单价, cost_price decimal(10,2) DEFAULT NULL COMMENT 成本价用于计算毛利, stock int(11) NOT NULL DEFAULT 0 COMMENT 当前库存, stock_warn int(11) DEFAULT 10 COMMENT 库存预警值, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1-上架0-下架, image_url varchar(512) DEFAULT NULL COMMENT 主图URL, description text COMMENT 商品描述, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_product_no (product_no), KEY idx_category_status (category_id,status), KEY idx_update_time (update_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;设计要点id是主键用于内部关联和索引。product_no是业务编号唯一用于前台展示、扫码等场景。主键和业务ID分离是良好实践。价格字段使用decimal类型避免浮点数精度问题。stock和stock_warn用于库存管理。在Service层每次下单后都需要更新此字段。添加了category_id,status,update_time的联合索引或独立索引这是根据查询场景如按分类和状态查商品、按更新时间排序来优化的。使用utf8mb4字符集支持存储Emoji等所有Unicode字符。2. 商品分类表 (product_category)简单的树状结构用于管理商品分类。CREATE TABLE product_category ( id bigint(20) NOT NULL AUTO_INCREMENT, parent_id bigint(20) DEFAULT 0 COMMENT 父分类ID0表示根分类, name varchar(64) NOT NULL, sort_order int(11) DEFAULT 0 COMMENT 排序值, status tinyint(4) DEFAULT 1, PRIMARY KEY (id), KEY idx_parent_id (parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表;3. 用户表 (user)存储系统用户信息包括顾客和管理员可通过user_type字段区分。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(64) NOT NULL COMMENT 登录用户名, password varchar(128) NOT NULL COMMENT 加密后的密码, nickname varchar(64) DEFAULT NULL, phone varchar(20) DEFAULT NULL COMMENT 手机号可用于登录, email varchar(128) DEFAULT NULL, avatar varchar(512) DEFAULT NULL COMMENT 头像, user_type tinyint(4) NOT NULL DEFAULT 1 COMMENT 用户类型1-普通顾客2-后台管理员, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态1-正常0-禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;安全提示密码字段切勿明文存储应使用BCrypt或PBKDF2等强哈希算法加盐后存储。varchar(128)是为哈希值预留的足够长度。4. 购物车表 (cart_item)记录用户添加到购物车的商品。注意购物车是用户维度的。CREATE TABLE cart_item ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, product_id bigint(20) NOT NULL COMMENT 商品ID, quantity int(11) NOT NULL COMMENT 购买数量, selected tinyint(1) NOT NULL DEFAULT 1 COMMENT 是否选中1-是0-否, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_product (user_id,product_id) COMMENT 同一用户同一商品只存一条记录, KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表;设计要点uk_user_product唯一索引是关键。它保证了同一个用户对同一个商品只会有一条购物车记录当用户再次添加时我们只需要更新quantity字段即可而不是新增一条。这避免了数据冗余和更新混乱。5. 订单主表 (order)与订单明细表 (order_item)这是最复杂的部分必须拆分成主表和明细表。这叫“水平拆分”主表存订单整体信息明细表存每个商品项。-- 订单主表 CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 订单ID主键内部使用, order_no varchar(32) NOT NULL COMMENT 订单号业务唯一展示给用户, user_id bigint(20) NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实际支付金额, pay_type tinyint(4) DEFAULT NULL COMMENT 支付方式1-微信2-支付宝, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待付款1-已付款/待发货2-已发货3-已完成4-已取消, delivery_address varchar(512) DEFAULT NULL COMMENT 配送地址, remark varchar(512) DEFAULT NULL COMMENT 用户备注, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time datetime DEFAULT NULL COMMENT 支付时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id_status (user_id,status), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细表 CREATE TABLE order_item ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL COMMENT 订单ID, order_no varchar(32) NOT NULL COMMENT 订单号冗余方便查询, product_id bigint(20) NOT NULL COMMENT 商品ID, product_name varchar(128) NOT NULL COMMENT 商品名称下单时的快照, product_image varchar(512) DEFAULT NULL COMMENT 商品图片快照, price decimal(10,2) NOT NULL COMMENT 下单时单价, quantity int(11) NOT NULL COMMENT 购买数量, total_price decimal(10,2) NOT NULL COMMENT 商品项总价, PRIMARY KEY (id), KEY idx_order_id (order_id), KEY idx_product_id (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;设计要点订单号生成order_no不能使用数据库自增ID必须用独立的业务规则生成如时间戳随机数或雪花算法。自增ID会暴露业务量且可能在分库分表时冲突。数据冗余在order_item中我们冗余存储了product_name,product_image,price。这是至关重要的商品信息名称、价格可能会变但订单作为历史凭证必须记录下单那一刻的信息。如果只存product_id当商品信息变更后历史订单就无法正确展示了。状态设计订单状态流转是核心业务逻辑需要清晰定义每个状态的含义和可转换到的下一个状态。3. 核心业务逻辑实现与避坑指南有了清晰的表结构我们就可以开始编写业务代码了。这里我挑几个最容易出问题的核心流程来讲这些也是面试中常被深挖的点。3.1 商品加入购物车并发更新的陷阱这个功能看似简单查询用户购物车是否已有该商品有则更新数量无则新增。但在并发场景下会出大问题。错误示范伪代码// CartService.java public void addToCart(Long userId, Long productId, Integer addQuantity) { CartItem item cartDao.selectByUserAndProduct(userId, productId); if (item null) { // 新增 item new CartItem(userId, productId, addQuantity); cartDao.insert(item); } else { // 更新 item.setQuantity(item.getQuantity() addQuantity); cartDao.updateById(item); } }问题如果用户快速连续点击两次“加入购物车”两个请求可能同时执行到selectByUserAndProduct都发现item为null然后都执行了insert操作。由于我们表里有唯一索引uk_user_product第二个insert会因唯一键冲突而失败或者更糟在更新库存时后面会讲造成超卖。解决方案使用数据库的“原子操作”来避免并发问题。public void addToCart(Long userId, Long productId, Integer addQuantity) { // 方法1使用 INSERT ... ON DUPLICATE KEY UPDATE (MySQL语法) // cartDao.insertOrUpdate(userId, productId, addQuantity); // 方法2先尝试更新如果影响行数为0再插入更通用 int updatedRows cartDao.updateQuantity(userId, productId, addQuantity); if (updatedRows 0) { // 更新0行说明记录不存在需要插入 // 注意这里仍有极小概率在update和insert之间被其他线程插入需要唯一索引兜底 try { CartItem newItem new CartItem(userId, productId, addQuantity); cartDao.insert(newItem); } catch (DuplicateKeyException e) { // 捕获唯一键冲突异常说明其他线程已经插入重试更新一次 cartDao.updateQuantity(userId, productId, addQuantity); } } }对应的Mapper SQL!-- 更新购物车商品数量 -- update idupdateQuantity UPDATE cart_item SET quantity quantity #{addQuantity}, update_time NOW() WHERE user_id #{userId} AND product_id #{productId} /update这个UPDATE语句是原子的数据库会保证同时只有一个请求能成功修改同一行数据。updatedRows返回1表示更新成功返回0表示记录不存在。这才是处理这类“存在则更新不存在则新增”场景的正确姿势。3.2 下单与库存扣减分布式锁与事务的权衡这是整个系统最核心、最复杂的部分。流程大致是校验商品状态和库存 - 扣减库存 - 生成订单 - 清空购物车选中项。其中“扣减库存”和“生成订单”必须在一个事务里否则可能出现库存扣了但订单没生成系统异常导致库存虚减。初级事务方案Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, OrderCreateRequest request) { // 1. 校验及计算无数据库写操作 ListOrderItemDTO itemList validateAndGetItems(request); BigDecimal totalAmount calculateTotal(itemList); // 2. 扣减库存悲观锁 for (OrderItemDTO item : itemList) { int rows productDao.decreaseStock(item.getProductId(), item.getQuantity()); if (rows 0) { throw new BusinessException(商品[ item.getProductName() ]库存不足); } } // 3. 生成订单写入order和order_item Order order buildOrder(userId, totalAmount, request); orderDao.insert(order); for (OrderItemDTO item : itemList) { OrderItem orderItem buildOrderItem(order, item); orderItemDao.insert(orderItem); } // 4. 清空购物车 cartDao.deleteSelectedItems(userId); return order; }问题分析性能瓶颈在for循环中逐条扣减库存会产生多条UPDATE语句且如果商品数量多事务持有锁的时间会很长影响并发。热点商品超卖即使decreaseStock方法使用了UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}这种“原子扣减”方式在高并发抢购同一商品时大量事务同时竞争这一行数据的锁行锁会导致数据库连接池被占满大量请求超时。虽然最终库存不会超卖数据库行锁保证但系统吞吐量会急剧下降用户体验极差。优化方案库存预扣与异步处理对于秒杀等高并发场景上述方案不可行。更成熟的方案是引入“库存预扣”和“异步下单”的概念。库存预扣 (Stock Deduction)在用户下单前先将库存从“总库存”扣到“预扣库存”中。这通常在Redis中完成利用Redis的高性能和原子操作如DECRBY来承受高并发。异步下单库存预扣成功后将下单请求放入消息队列如RabbitMQ、Kafka由后台消费者异步地执行真正的数据库订单创建流程。这样就把耗时的数据库操作与高并发的请求入口分离开。折中优化方案适用于一般并发 如果项目不需要应对秒杀我们可以对上述初级方案进行优化批量扣减库存将所有需要扣减库存的商品ID和数量组装成一条CASE WHEN的SQL一次执行。UPDATE product SET stock CASE id WHEN 1001 THEN stock - 2 WHEN 1002 THEN stock - 1 ... END, update_time NOW() WHERE id IN (1001, 1002, ...) AND (stock CASE id WHEN 1001 THEN 2 WHEN 1002 THEN 1 ... END)执行后检查影响的行数是否等于商品种类数如果不等说明有商品库存不足。缩短事务时间将一些非核心的、后续可补偿的操作移出事务比如“清空购物车”。可以在订单生成成功后异步或非事务地去清理。即使清理失败也不影响主流程可以通过定时任务或手动方式补偿。使用分布式锁在事务开始前针对用户ID或订单唯一键加一个分布式锁如基于Redis防止同一用户重复提交订单。这可以避免前端重复点击带来的重复请求问题。3.3 订单状态流转与幂等性设计订单状态status的变化必须严谨。通常我们会在Service层定义一个状态机。例如待付款的订单可以变为已付款或已取消已发货的订单不能变回待付款。实操建议在更新订单状态时一定要使用乐观锁或带旧状态的更新防止状态被意外覆盖。// OrderService.java public boolean payOrder(Long orderId, String payNo) { // 带状态校验的更新 int rows orderDao.updateStatusToPaid(orderId, OrderStatus.UNPAID.getValue(), payNo); return rows 0; }对应的SQLupdate idupdateStatusToPaid UPDATE order SET status #{newStatus}, pay_type #{payType}, pay_time NOW(), update_time NOW() WHERE id #{orderId} AND status #{oldStatus} !-- 乐观锁条件只有当前状态是待付款时才更新 -- /update幂等性支付回调接口等可能被多次调用的接口必须实现幂等。可以在支付回调时先根据第三方支付流水号payNo查询是否已处理过该订单如果已处理则直接返回成功避免重复更新订单状态和库存。4. 项目文档让价值倍增的关键一个只有代码的项目是不完整的。好的文档能让你几个月后还能快速理解自己的代码更能让面试官眼前一亮。文档不应该事后补而应该与开发同步进行。4.1 数据库设计文档 (Database Schema Documentation)使用工具如PDManer、MySQL Workbench生成ER图并附上详细的表结构说明。每个字段的注释COMMENT要写清楚这在SQL中已经体现了。可以额外补充一份Markdown文档说明核心表之间的关系和主要查询场景。4.2 API接口文档 (API Documentation)这是前后端联调的桥梁。强烈推荐使用Swagger/OpenAPI来自动生成。在Controller方法上通过注解来定义接口说明、参数、返回值。RestController RequestMapping(/api/product) Api(tags 商品管理) public class ProductController { GetMapping(/list) ApiOperation(分页查询商品列表) public CommonPageProductVO list( ApiParam(分类ID) RequestParam(required false) Long categoryId, ApiParam(页码) RequestParam(defaultValue 1) Integer pageNum, ApiParam(每页数量) RequestParam(defaultValue 10) Integer pageSize) { // ... 业务逻辑 } }启动项目后访问/swagger-ui.html就能看到所有接口的交互式文档前端同学可以直接在上面测试省去大量沟通成本。4.3 部署文档 (Deployment Guide)详细说明如何让这个系统跑起来。环境要求JDK 17、MySQL 8.0、Maven 3.6。数据库初始化提供schema.sql建表语句和可选的data.sql初始数据。配置修改指明application.yml或application.properties中需要修改的关键配置如数据库连接、Redis地址、文件上传路径等。构建与运行# 克隆代码 git clone [项目地址] # 导入数据库 mysql -u root -p sql/schema.sql # 修改配置文件 vim src/main/resources/application-dev.yml # 打包 mvn clean package -DskipTests # 运行 java -jar target/supermarket-system-1.0.0.jar访问地址http://localhost:8080 Swagger文档地址http://localhost:8080/swagger-ui.html。4.4 核心业务流程说明 (Core Business Flow)用文字或流程图如PlantUML描述关键流程如“用户下单时序图”、“库存扣减数据流图”。这能帮助读者快速理解系统核心运作机制。5. 进阶思考与扩展方向一个基本的购物系统完成后你可以从以下几个方向深化这会让你的项目从“练手”级别提升到“可面试”甚至“可实用”级别。5.1 引入缓存优化性能商品列表、分类信息这些读多写少的数据非常适合放入缓存如Redis。策略查询时先查缓存命中则返回未命中则查数据库并将结果写入缓存设置一个合理的过期时间如5分钟。更新商品信息时在更新数据库后删除对应的缓存Cache-Aside Pattern下次查询时自动回源并重建缓存。注意缓存穿透、击穿、雪崩对于不存在的商品ID缓存穿透可以将null值也缓存一小段时间。对于热点key失效缓存击穿可以使用互斥锁Redis的SETNX只让一个线程回源数据库。5.2 实现简单的搜索功能最初你可能用LIKE做商品名搜索但这在数据量大时效率极低。可以集成Elasticsearch或使用MySQL的全文索引FULLTEXT INDEX来实现更高效的搜索并支持分词、高亮、排序等功能。5.3 构建管理后台使用现成的Admin模板如若依、ELAdmin或前端框架如Ant Design Pro、Vue Element Admin快速搭建一个管理后台实现商品上架下架、订单管理、用户管理等功能。这能让你练习前后端分离项目的完整协作流程。5.4 容器化与部署学习使用Docker将你的应用、MySQL、Redis等打包成容器并用docker-compose.yml定义它们之间的关系。这不仅能让你本地环境一键启动更是现代微服务部署的必备技能。你可以尝试将项目部署到云服务器并配置Nginx反向代理和HTTPS。把这个超市购物系统做深做透其价值远超简单完成一个功能。它涉及了数据库设计、并发控制、事务管理、API设计、缓存、文档编写等后端工程师的日常工作核心。当你下次被问到“你做过什么项目”时你可以从容地从架构设计讲到细节实现从遇到的问题讲到解决方案这才是面试官想听到的“项目经验”。本文还有配套的精品资源点击获取