ARTICLE DETAIL

资讯详情

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

Java超市管理系统毕业设计实战:从技术选型到库存扣减与权限控制

Java超市管理系统毕业设计实战:从技术选型到库存扣减与权限控制 简介这是一份面向高校计算机相关专业毕业设计场景的Java Web项目源码包主题为超市管理系统适合正在准备毕设、需要完整可运行案例进行参考或二次开发的学生与初级开发者。项目采用IntelliJ IDEA作为开发工具以Java为开发语言使用Maven进行依赖与构建管理并部署运行于Tomcat服务器技术栈贴近企业级Web开发入门路径。压缩包共包含290个文件整体约2.27MB其中29个java源文件与29个class编译文件构成核心业务逻辑16个jsp页面与6个html文件负责前端展示33个js脚本、5个css样式及151个gif图片资源共同支撑界面交互另有6个jar依赖包、xml配置与开发思路说明文档便于快速理解项目结构。目前已有594人学习关注。读者可从中获取完整的超市管理系统实现方案涵盖商品、用户、订单等模块的Servlet与DAO分层设计借助开发思路文档与源码对照能够梳理Maven项目搭建、数据库工具类封装及前后端交互流程为毕设选题、代码复现与功能扩展提供可直接参考的实践素材。1. 超市管理系统拆开看一个 Java 毕业设计到底要落地哪些模块很多同学拿到「基于 Java 的超市管理系统」这个题目第一反应是去搜一套现成源码跑起来交差。但真正动手才发现能跑起来的代码和能通过答辩的系统之间差着库存扣减、权限隔离、并发收银这几道坎。这个题目本质上是一个典型的进销存采购、销售、库存业务闭环用 Java 技术栈实现商品管理、收银结算、库存预警、会员积分和报表统计。它适合计算机毕业设计选题阶段需要一套完整 CRUD 加业务逻辑练手的同学也适合刚学完 Java 基础想找一个能写进简历的项目。核心难点不在界面好不好看而在库存扣减的原子性、收银流水的数据一致性以及权限控制能不能扛住答辩老师的追问。下面按「选型 → 建库 → 写核心业务 → 避坑 → 进阶」的顺序把这条路走一遍。2. 技术选型与工程骨架为什么 Spring Boot MyBatis 是毕业设计的稳妥牌2.1 选型逻辑别在架构上给自己挖坑毕业设计的时间窗口通常只有两到三个月其中还要写论文。技术选型的第一原则是「生态成熟、报错能搜到、答辩老师认识」。Spring Boot MyBatis MySQL Thymeleaf或 Vue 前后端分离是当前高校毕设里最主流的组合原因很实际Spring Boot 把 Tomcat 内嵌了不用配 web.xmlMyBatis 的 XML 映射对新手比 JPA 的注解更直观SQL 写错了能直接看到MySQL 免费且资料多。有同学会问要不要上 Spring Cloud 微服务。我的建议是不要。超市管理系统的业务体量根本用不到服务拆分硬上微服务只会让你在 Nacos 注册中心和 Feign 远程调用上耗掉一半时间答辩时老师问「为什么拆服务」你也答不出业务理由。单体应用加清晰的分层Controller → Service → Mapper足够了。前端方面如果时间紧Thymeleaf 服务端渲染最快出效果如果想让简历好看一点用 Vue3 Element Plus 做前后端分离后端只提供 REST 接口。两条路都行但不要混着用。2.2 工程骨架搭建从零到能启动下面是一个最小可运行的工程结构用 Maven 管理依赖。先建表再搭工程。-- 商品表超市系统的核心主数据 CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(32) NOT NULL UNIQUE COMMENT 条形码收银扫码用, name VARCHAR(128) NOT NULL COMMENT 商品名称, category_id BIGINT COMMENT 分类ID, purchase_price DECIMAL(10,2) COMMENT 进价, sale_price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存, stock_warn INT DEFAULT 10 COMMENT 库存预警阈值, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 销售订单表 CREATE TABLE sale_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号, total_amount DECIMAL(10,2) NOT NULL, pay_method TINYINT COMMENT 1现金 2微信 3支付宝, cashier_id BIGINT COMMENT 收银员ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE sale_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL, subtotal DECIMAL(10,2) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有两个参数值得注意。stock字段用 INT 而不是 VARCHAR因为后面要做stock stock - ?的原子更新字符串类型没法直接算。barcode加了 UNIQUE 约束收银扫码时靠它定位商品重复条码会导致扫出两个商品。utf8mb4字符集是为了支持商品名里的生僻字和 emoji用 utf8 在某些 MySQL 版本下会截断。工程依赖的 pom.xml 核心部分dependencies !-- Web 层提供 REST 接口和页面路由 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatisSQL 映射比 JPA 更适合需要手写复杂查询的场景 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok减少 getter/setter 样板代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesMyBatis 的版本要和 Spring Boot 版本匹配。如果用 Spring Boot 3.xMyBatis Starter 必须用 3.0.x用 Spring Boot 2.7则用 2.3.x。版本不匹配的典型报错是NoSuchMethodError: org.mybatis.spring.SqlSessionFactoryBean遇到这个先查版本对应关系别急着改代码。配置文件 application.yml 里数据源和 MyBatis 的关键参数spring: datasource: url: jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.supermarket.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这个配置能省掉大量resultMap手写映射数据库的sale_price会自动映射到 Java 的salePrice。serverTimezone必须设否则 MySQL 8 会报时区错误。3. 核心业务落地收银结算与库存扣减怎么写才不出事3.1 收银流程的 Service 层实现收银是整个系统里唯一有并发压力的地方。两台收银机同时卖同一件商品如果库存扣减写得不对就会出现超卖。下面是一个能扛住基本并发的实现Service public class SaleService { Autowired private ProductMapper productMapper; Autowired private SaleOrderMapper orderMapper; Autowired private SaleOrderItemMapper orderItemMapper; /** * 收银结算扣库存 生成订单 写明细 * param items 购物车条目列表 * param cashierId 收银员ID */ Transactional(rollbackFor Exception.class) public String checkout(ListCartItem items, Long cashierId) { String orderNo generateOrderNo(); BigDecimal total BigDecimal.ZERO; for (CartItem item : items) { // 关键用 stock quantity 作为条件防止超卖 int affected productMapper.deductStock(item.getProductId(), item.getQuantity()); if (affected 0) { throw new BusinessException(商品[ item.getName() ]库存不足); } BigDecimal subtotal item.getSalePrice() .multiply(BigDecimal.valueOf(item.getQuantity())); total total.add(subtotal); SaleOrderItem orderItem new SaleOrderItem(); orderItem.setProductId(item.getProductId()); orderItem.setQuantity(item.getQuantity()); orderItem.setUnitPrice(item.getSalePrice()); orderItem.setSubtotal(subtotal); orderItemMapper.insert(orderItem); } SaleOrder order new SaleOrder(); order.setOrderNo(orderNo); order.setTotalAmount(total); order.setCashierId(cashierId); orderMapper.insert(order); return orderNo; } private String generateOrderNo() { // 时间戳 4位随机数够用且不重复 return System.currentTimeMillis() String.format(%04d, new Random().nextInt(10000)); } }这段代码的核心在deductStock的 SQL 写法。对应的 Mapper XMLupdate iddeductStock UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity} AND status 1 /updateWHERE stock #{quantity}这个条件是把库存检查下推到了数据库层。如果库存不够affected返回 0Service 层抛异常触发回滚。这样即使两个线程同时执行InnoDB 的行锁也会保证只有一个能成功。如果先SELECT查库存再UPDATE中间就有时间窗口并发下必然超卖。Transactional(rollbackFor Exception.class)里的rollbackFor不能省。Spring 默认只对RuntimeException回滚如果抛的是受检异常事务不会回滚库存扣了但订单没生成数据就脏了。3.2 库存预警与报表查询库存预警的逻辑很简单查stock stock_warn的商品列表。但实际用起来有个细节——预警阈值不应该是一个全局固定值。矿泉水一天卖几百瓶阈值设 10 合理电视机一个月卖两台阈值设 10 就永远在预警。所以stock_warn放在商品表里每个商品单独设。Mapper public interface ProductMapper { Select(SELECT * FROM product WHERE stock stock_warn AND status 1) ListProduct selectWarningProducts(); Select(SELECT DATE(create_time) AS date, SUM(total_amount) AS amount FROM sale_order WHERE create_time #{start} GROUP BY DATE(create_time) ORDER BY date) ListDailyReport selectDailyReport(Param(start) LocalDate start); }报表查询用GROUP BY DATE(create_time)按天聚合。注意create_time字段上要建索引否则数据量上万后报表页面会明显变慢。索引语句CREATE INDEX idx_create_time ON sale_order(create_time);。4. 权限控制与数据一致性答辩老师最爱追问的两个点4.1 基于角色的权限隔离超市系统至少有三类角色管理员全部权限、收银员只能收银和查自己的流水、库管只能管商品和库存。最轻量的实现是用 Spring 拦截器加注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value(); } Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) return true; HandlerMethod method (HandlerMethod) handler; RequireRole role method.getMethodAnnotation(RequireRole.class); if (role null) return true; // 从 session 取当前登录用户角色 User user (User) request.getSession().getAttribute(user); if (user null) { response.sendRedirect(/login); return false; } for (String r : role.value()) { if (r.equals(user.getRole())) return true; } response.setStatus(403); return false; } }用起来就是在 Controller 方法上加RequireRole(ADMIN)。这个方案比 Spring Security 轻得多毕设场景够用。但要注意拦截器要注册到 WebMvcConfigurer 里且排除登录接口和静态资源路径否则登录页自己都被拦了。4.2 收银流水的数据一致性收银涉及三张表的写入订单、明细、库存。这三步必须在同一个事务里。前面Transactional已经保证了。但还有一个容易忽略的点订单明细的order_id依赖订单插入后生成的自增 ID。如果先插明细再插订单order_id拿不到。正确顺序是先插订单拿到 ID再批量插明细。MyBatis 里拿自增 ID 用useGeneratedKeysinsert idinsert useGeneratedKeystrue keyPropertyid INSERT INTO sale_order(order_no, total_amount, pay_method, cashier_id) VALUES(#{orderNo}, #{totalAmount}, #{payMethod}, #{cashierId}) /insert插入后order.getId()就能拿到数据库生成的 ID。这个参数不写的话id永远是 null明细表的order_id就写不进去。5. 避坑与排查那些让我熬夜的报错5.1 库存扣成负数现象数据库里出现stock -3的记录。原因扣减 SQL 没加stock quantity条件或者加了但 Service 层没检查affected返回值。解决SQL 必须带条件Service 层必须判断affected 0时抛异常。两个条件缺一不可。5.2 事务不回滚现象库存扣了但订单表没数据。原因异常被 catch 了没重新抛出或者抛的是受检异常但没配rollbackFor。解决Transactional(rollbackFor Exception.class)且 catch 块里要么不吞异常要么手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。5.3 中文乱码现象商品名存进数据库变成问号。原因数据库连接 URL 没设字符集或者建表时用了 latin1。解决URL 加characterEncodingutf8建表用utf8mb4。已经建错的表用ALTER TABLE product CONVERT TO CHARACTER SET utf8mb4;改。5.4 收银页面重复提交现象收银员手快点了两次结算生成两笔订单。原因前端按钮没防重复点击后端没做幂等。解决前端点击后 disable 按钮后端用订单号做唯一约束重复插入会抛DuplicateKeyException捕获后返回「请勿重复提交」。5.5 MyBatis 映射不到字段现象查询返回的对象字段全是 null。原因数据库字段sale_price和 Java 属性salePrice没映射上。解决application.yml 里加map-underscore-to-camel-case: true或者手写 resultMap。前者更省事。6. 进阶技巧把毕设做成能写进简历的项目如果只做到上面这些系统能跑、能答辩但简历上写「超市管理系统」和别人的没什么区别。想让面试官多问两句可以加两个东西。第一个是收银小票的打印。用 Java 的Printable接口实现小票排版调用系统默认打印机。代码不复杂但演示时很直观public class ReceiptPrinter implements Printable { private final SaleOrder order; private final ListSaleOrderItem items; public ReceiptPrinter(SaleOrder order, ListSaleOrderItem items) { this.order order; this.items items; } Override public int print(Graphics g, PageFormat pf, int pageIndex) { if (pageIndex 0) return NO_SUCH_PAGE; Graphics2D g2 (Graphics2D) g; g2.setFont(new Font(Monospaced, Font.PLAIN, 10)); int y 30; g2.drawString(订单号 order.getOrderNo(), 20, y); y 15; for (SaleOrderItem item : items) { g2.drawString(item.getProductName() x item.getQuantity() item.getSubtotal(), 20, y); y 15; } g2.drawString(合计 order.getTotalAmount(), 20, y); return PAGE_EXISTS; } }第二个是加一个简单的销售趋势图。用 ECharts 在前端画后端提供一个按天聚合的接口就行。答辩时打开报表页面一条折线图比十页 CRUD 截图有说服力。验证系统是否真的可靠我一般做三件事一是用 JMeter 开 50 个线程同时调收银接口看库存有没有超卖二是手动把某个商品的stock改成 1然后下单数量填 2看是否返回「库存不足」三是把数据库停掉看前端是否返回友好提示而不是 500 堆栈。这三步过了系统的基本盘就稳了。最后说个血泪经验毕设代码一定要用 Git 管理每完成一个模块就 commit 一次。我见过太多同学改崩了想回退结果发现没有版本记录只能重写。另外论文里的系统截图要在代码冻结后再截否则改完代码忘了更新截图答辩时对不上。希望帮到你。本文还有配套的精品资源点击获取
返回列表