
简介这是一份面向计算机专业在校学生、教师及初级开发者的超市管理系统项目源码适用于毕业设计、课程设计、大作业或项目初期演示等场景。项目围绕商品管理、库存管理、订单处理等典型业务展开代码经过完整运行测试功能模块划分清晰适合用于学习Java Web开发流程也可以作为二次开发的基础框架。压缩包共包含2001个文件除核心Java源码、JSP页面与Servlet外还涵盖HTML、CSS、JavaScript前端资源以及图片素材、数据库脚本和项目配置文件能够支撑从后端逻辑到前端展示的完整实现。资源整体大小约52.19MB目前已有48人学习使用可通过README文档快速上手项目结构并根据课程要求或毕设需求灵活扩展模块是一份兼具参考价值与实战意义的完整项目资料。1. 超市管理系统课设一包代码背后的业务主线和此刻最该确认的事答辩前一周打开这个 zip 包里面是几十个 Java 文件外加一张数据库脚本这是很多人在课程设计阶段最常见的入场方式。超市管理系统这个标题听起来大实际要交付的是一套覆盖“商品、库存、采购、销售”的桌面管理工具核心目标是让录入的商品能入库并标价让收银台能结算并扣减库存让管理员能核对账面和库存是否对得上。它能解决的是课程设计和毕业设计里“一个完整业务闭环”的展示问题而不是一套商用收银系统。适合读这篇内容的人有两类一类是需要交课设或毕设、想快速理解这套代码并完成演示的人另一类是打算用 Java 做一套完整管理信息系统练手、不愿只写增删改查 Demo 的开发者。下文直接按“选型怎么定、表怎么建、代码怎么写、坑在哪、答辩怎么验”推进每一部分都给出能照着改的细节读完后你至少能回答三个问题这套系统为什么这么分层核心表之间是什么关系验收前最值得优先修的是什么。2. 技术选型与架构分层为什么“桌面端 MySQL”仍是课设主力2.1 三种主流技术组合的对比与选择逻辑课设里做超市管理技术路线大致分三种Java Swing MySQL 的桌面端Spring Boot Vue 的浏览器端以及 Python Flask 搭的轻量 Web 端。从“能不能在答辩现场稳定跑完”这个角度看Swing 桌面端有天然优势没有跨域、没有前端依赖、没有部署在服务器上的中间件解压、启动、连数据库三步就能到登录窗口。技术组合上手成本界面效果答辩风险点适合场景Java Swing MySQL低课程内普遍覆盖一般但稳定依赖 JDK 与驱动 jar 环境大多数课设与毕设Spring Boot Vue高需要 Node 与 Maven 配合好页面灵活环境链路长启动易失败想展示工程化能力的推荐做这个Python Flask HTML低代码量少中规中矩并发处理相对弱演示时易被追问快速实现且校内允许 Python如果目标是“稳妥交差并且能讲清楚”我一般会建议选 Swing 方案。它有一个容易被忽略的好处业务逻辑和界面事件在同一个进程里调试时打断点、看变量、追踪事务都简单。答辩老师问“你这段代码做了什么”你直接定位到事件监听方法就能讲清楚不需要先解释前端请求怎么到后端、后端又是怎么回数据。2.2 从商品流与资金流两条主线拆业务模块打开这套系统的代码包先别急着逐行读按两条数据流去梳理会清楚得多。第一条是商品流供应商的货到店之后生成进货单进货单明细里带着商品、数量、进价确认入库后把数量叠加到商品表的库存字段上。第二条是资金流收银台把用户购买的商品逐条加入购物车结账时生成销售单和销售明细同时扣减库存最后按角色查询销售统计。两条流交汇在商品表上所以商品表既是采购的落点也是销售的起点。代码包里的控制器类、业务类、数据库操作类本质上都在围绕这两条流做数据的读和写。理解这一点之后你再去看那几个核心类名会发现自己已经能猜出每个文件的大致职责了。对课设而言你不需要再额外设计什么复杂模块。系统里有几个加分项值得保留角色区分管理员和收银员、进货管理、销售管理、库存查询、销售报表。如果某个功能点还带了条码输入或简单的图表统计答辩时就可以主动往这上面引。2.3 开发环境准备与工程目录安排拿到代码包后先用下面一组命令确认本机环境是完整的。以 Windows 10 / 11 加 MySQL 8.0 为例原则是“先确认 JDK 和 MySQL 可用再导入工程”。# 查看 Java 版本需要 8 或 1164 位 java -version # 查看 MySQL 服务是否在监听 3306 端口 netstat -ano | findstr :3306 # 查看 MySQL 8 自带客户端是否能连接本地服务 mysql -uroot -p这段命令里java -version 解决的是 JDK 缺失或者版本不匹配问题netstat 检查 3306 端口是否被监听这一步能提前排除“服务没启动”和“端口被占用”两类故障mysql -uroot -p 则确认数据库账号能正常登录。三个检查通过之后才考虑在 IDE 里导入源码。工程目录方面我会按“界面层、业务层、数据访问层、工具类”四个包展开。你可以对照下面这个结构来理解代码包里的文件摆放src/main/java ├── ui # 登录窗口、主窗口、各管理面板 ├── service # 业务逻辑进货、销售、统计 ├── dao # 数据库操作增删改查 ├── model # 实体类Product、SaleOrder、User 等 ├── util # 数据库连接、字符串处理、MD5 └── Main.java # 程序入口第一次上手时建议从 model 包里最像数据库表的实体类看起再看 dao 包里对应的 SQL 语句最后回到 ui 包里找按钮事件。这样读代码的顺序和系统运行时的数据流向是一致的比从入口函数顺藤摸瓜要省力得多。3. 数据库建模几张核心表的关系与三个不能剪掉的设计细节3.1 核心表结构与字段设计超市管理系统最少需要六张表分类表、供应商表、商品表、进货单、销售单外加两张明细表。这里我把进货单和销售单都拆成“主表 明细表”的结构这样购买记录和进货记录能保留原始单价和数量不会因为商品价格调整而失真。表名业务含义关键字段与谁关联category商品分类id, name, sort_order被 product 引用supplier供应商id, name, contact, phone被 purchase_order 引用product商品id, name, barcode, price, stock, status关联 categorypurchase_order进货单头id, supplier_id, total_amount, created_at关联 supplierpurchase_item进货明细id, order_id, product_id, quantity, unit_price关联 purchase_order 和 productsale_order销售单头id, order_no, cashier, total_amount, created_at无外键关联单表存储sale_item销售明细id, order_id, product_id, quantity, price关联 sale_order 和 product最核心的设计我在上面加粗过一个词原始单价。进货明细里的 unit_price 记录的是“这次进货时商品进价”销售明细里的 price 记录的是“这次成交时商品的售价”。如果直接把这两个字段做成组装当前商品表里的现价历史数据就会被新价格覆盖到时统计报表里的成本与营收算账就变成一团乱麻。3.2 销售单与销售明细为什么要拆成两张表把一次购买行为拆成 sale_order 和 sale_item 两张表是这套系统里最关键的设计。如果只建一张销售表把一个订单里的所有商品塞进一行你会立刻遇到两个问题商品数量不定字段怎么留历史购买的商品如果下架了商品名怎么保存拆成主表和明细表之后一个 sale_order 对应多行 sale_item每一行保存商品 ID、成交单价、购买数量订单的全部信息都能追溯回来。进货逻辑同理。进货单头记录供应商、总金额、采购时间进货明细记录每种商品的进货数量和进价。这样“整单”和“商品明细”是分开的后续做按供应商对账、按商品统计进货成本时SQL 写起来才顺。很多同学为了图省事只建一张 purchase 表结果后面想在界面按时间范围筛进货记录不得不反复拼接字符串反而把自己绕进去了。3.3 建表 SQL 落地直接可用的脚本与参数说明下面这段 SQL 是我平时搭课设时会用的基础脚本去掉了一些额外索引和注释保留了最核心的字段。你可以在 MySQL 8.0 里直接执行执行顺序必须先建分类表和供应商表再建商品表最后建进货和销售相关表否则外键会报错。CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; CREATE TABLE category ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 分类名称, sort_order INT DEFAULT 0 COMMENT 排序号小的在前, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE supplier ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 供应商名称, contact VARCHAR(50) DEFAULT COMMENT 联系人, phone VARCHAR(20) DEFAULT COMMENT 联系电话, address VARCHAR(200) DEFAULT COMMENT 地址, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 商品名称, barcode VARCHAR(30) DEFAULT COMMENT 条码可为空, category_id INT DEFAULT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 销售价, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在售 0下架, PRIMARY KEY (id), KEY idx_category (category_id), CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个参数要特别说明。price 字段用 DECIMAL(10,2) 而不是 DOUBLE是为了避开浮点数在金额计算上的误差stock 用 INT 且默认 0保证库存能随手加减status 用 TINYINT 存 1 和 0比字符串更省空间查询也快。外键 fk_product_category 在这个演示项目里建议保留它能阻止你插入一个不存在的分类 ID但生产系统里为了性能常会去掉外键课设里保留反而能体现数据库设计的完整性。进货和销售的主表、明细表核心代码是下面这段。注意 sale_order 里的 order_no 设置了唯一索引这是为了让每个销售单都有独立的流水号避免重复。CREATE TABLE purchase_order ( id INT NOT NULL AUTO_INCREMENT, supplier_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(200) DEFAULT , PRIMARY KEY (id), KEY idx_supplier (supplier_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE purchase_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 0, unit_price DECIMAL(10,2) NOT NULL DEFAULT 0.00, PRIMARY KEY (id), KEY idx_order (order_id), KEY idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sale_order ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(30) NOT NULL, cashier VARCHAR(50) NOT NULL DEFAULT , total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sale_item ( id INT NOT NULL AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 0, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 成交单价, PRIMARY KEY (id), KEY idx_order (order_id), KEY idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这套结构里purchase_order 和 purchase_item 之间通过 order_id 关联sale_order 和 sale_item 之间也用同样方式。两个明细表里的 product_id 都没有设置外键这是有意的如果商品被删除销售明细的历史记录还能保留如果你加了外键删除商品时会卡住课设里反而不方便演示“商品下架”这个操作。真正约束一致性靠的更多是业务代码在写入前先检查商品是否存在。3.4 存储引擎与字符集InnoDB、utf8mb4 与事务的关系上面每张表我都指定了 ENGINEInnoDB这不是随便写的。InnoDB 支持事务而 MyISAM 不支持。超市系统的核心操作——结账时扣减库存必须在一个事务里完成如果销售明细写入了但库存没扣成功整个订单要回滚否则账实不符。MyISAM 遇到这种情况只能手动补偿课设阶段用 InnoDB 能省掉很多麻烦答辩时你也能顺理成章地讲“这里用了事务保证一致性”。字符集选 utf8mb4 而不是 utf8会员在录入商品名时万一碰到特殊字符比如 emoji和生僻字utf8 会报错或乱码。utf8mb4 是 utf8 的超集在 MySQL 8.0 里已经是默认字符集建数据库时显式写出来更稳妥。配套的 JDBC 连接参数也要一致连接串里加 characterEncodingutf8这一节的第 4.1 部分会给出具体写法。4. 从登录到收银台核心代码骨架与关键参数4.1 JDBC 连接的构造URL 参数决定了你调试的方向不管界面写得多花哨系统最终都要落到数据库读写上。课设项目里最常见的连接方式是用 JDBC 的 DriverManager配合一个工具类统一管理连接。下面这段是简化后的数据库连接工具类你可以直接复制到 util 包里。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 连接参数集中到常量里方便不同机器上修改 private static final String URL jdbc:mysql://localhost:3306/supermarket ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动未找到请检查 jar 包是否在 classpath 中, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码里URL 上的三个参数是排障重点。characterEncodingutf8 解决中文乱码useSSLfalse 避免 MySQL 8 的 SSL 握手警告serverTimezoneAsia/Shanghai 解决 MySQL 8 连接时常见的“Server time zone”报错。USER 和 PASSWORD 务必改成你自己数据库的实际账号密码如果你用的是 MySQL 5.7驱动类要换成 com.mysql.jdbc.Driver连接串里可以去掉 serverTimezone。提示如果运行界面时频繁报 Communications link failure先 telnet 127.0.0.1 3306 看端口通不通再用客户端工具连一次同样账号密码。九成以上的连接问题都出在“服务没启动”和“密码不对”上不是代码问题。4.2 登录模块参数化查询与角色跳转登录是系统的第一道门面也是答辩时最容易停的环节。我的做法是在代码里明确区分“按用户名查密码”和“比对密码”两步。把 SQL 写成分号拼接是最常见的翻车点下面这段用 PreparedStatement 参数化查询既防注入也方便后续加日志。public User login(String username, String password) { String sql SELECT id, username, role FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, MD5Util.md5(password)); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRole(rs.getString(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }逻辑上要注意两点。第一密码入库前用 MD5 摘要再比对不要在数据库里存明文我知道 MD5 已经不适合生产环境但课设里它格式短、好实现你只需要在文档里写一句“生产环境应改用 BCrypt”就能体现安全思维。第二登录完成后根据 user 表里的 role 字段跳转不同主界面管理员能看到商品管理和进货管理菜单收银员只能看到收银台和库存查询。这一步在 UI 层判断即可代码结构清晰。4.3 商品上架与库存初始化一次插入里隐藏的字段校验添加商品这个功能看起来只是往 product 表里 insert 一行但至少需要处理三个边界价格不能是负数、库存初始值不能为空、商品名称不能重复。下面这段代码把校验放在 service 层而不放在界面层这样以后如果做了批量导入同样的校验逻辑能复用。public boolean addProduct(Product p) { String checkSql SELECT COUNT(*) FROM product WHERE name ?; String insertSql INSERT INTO product(name, barcode, category_id, price, stock, status) VALUES(?,?,?,?,?,1); try (Connection conn DBUtil.getConnection()) { // 用商品名查重避免同一商品重复录入 try (PreparedStatement ps conn.prepareStatement(checkSql)) { ps.setString(1, p.getName()); try (ResultSet rs ps.executeQuery()) { rs.next(); if (rs.getInt(1) 0) { return false; // 已存在同名商品 } } } try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setString(1, p.getName()); ps.setString(2, p.getBarcode()); if (p.getCategoryId() null) { ps.setNull(3, java.sql.Types.INTEGER); } else { ps.setInt(3, p.getCategoryId()); } ps.setBigDecimal(4, p.getPrice()); ps.setInt(5, p.getStock()); return ps.executeUpdate() 1; } } catch (SQLException e) { e.printStackTrace(); return false; } }这段代码里有两个细节值得讲。categoryId 允许为空对应分类表外键允许 NULL这意味着商品可以不选分类避免“必须新建分类才能加商品”的尴尬价格和库存用 setBigDecimal 与 setInt与数据库字段类型严格对应能减少类型转换异常。代码里所有流都用 try-with-resources关闭顺序交给 JVM 处理不用写一堆 finally 来关连接。4.4 收银结账一个事务把销售明细、订单头和库存扣减串起来收银是整套系统最有含金量的模块。一份完整结账代码要完成四件事校验购物车不为空、计算订单总额、写入 sale_order、逐条写入 sale_item 并同时扣减库存。这里最关键的是“写入明细”和“扣库存”这两个动作必须被同一个事务包裹。public boolean checkout(ListCartItem cart, String cashier) { if (cart null || cart.isEmpty()) { return false; } String orderNo SO System.currentTimeMillis(); BigDecimal total BigDecimal.ZERO; try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 先写销售单头 try (PreparedStatement ps conn.prepareStatement( INSERT INTO sale_order(order_no, cashier, total_amount) VALUES(?,?,?))) { ps.setString(1, orderNo); ps.setString(2, cashier); ps.setBigDecimal(3, BigDecimal.ZERO); ps.executeUpdate(); } // 逐条写明细并扣库存 for (CartItem item : cart) { BigDecimal subtotal item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); total total.add(subtotal); try (PreparedStatement ps conn.prepareStatement( INSERT INTO sale_item(order_id, product_id, quantity, price) VALUES(?,?,?,?))) { ps.setLong(1, getLastInsertId(conn)); ps.setInt(2, item.getProductId()); ps.setInt(3, item.getQuantity()); ps.setBigDecimal(4, item.getPrice()); ps.executeUpdate(); } // 用原子更新扣减库存避免并发超卖 try (PreparedStatement ps conn.prepareStatement( UPDATE product SET stock stock - ? WHERE id ? AND stock ?)) { ps.setInt(1, item.getQuantity()); ps.setInt(2, item.getProductId()); ps.setInt(3, item.getQuantity()); if (ps.executeUpdate() 0) { conn.rollback(); return false; // 库存不足整单回滚 } } } // 回写销售单总金额 try (PreparedStatement ps conn.prepareStatement( UPDATE sale_order SET total_amount ? WHERE order_no ?)) { ps.setBigDecimal(1, total); ps.setString(2, orderNo); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } }我刻意在这段代码里暴露了一个常见简化写法每次插入明细后都用 getLastInsertId 去拿订单主键。实际你可以先插入订单头再用 ResultSet 的 getGeneratedKeys 拿到自增 ID然后循环写明细。这里用 order_no 反查是一个妥协不建议深究重点是库存扣减语句里的“stock ?”条件这个条件保证了即使在多人同时结账的情况下库存不足的那一侧会更新失败并触发回滚而不是先查到库存再在代码里判断那是典型的非原子操作。5. 避坑指南验收前最高频的五个翻车场景5.1 中文显示乱码窗口里全是问号或空框现象界面和数据表里所有中文变成“???”或显示成方框英文和数字正常。原因数据库连接串里缺少 characterEncodingutf8或者数据库本身的字符集是 latin1代码里写入的中文被按错误编码转换读出来就不是同一个字。解决先执行 show variables like character_set%; 确认数据库字符集再在 JDBC URL 里加上 useUnicodetruecharacterEncodingutf8最后把代码文件统一另存为 UTF-8 编码。Windows 上最容易漏的是最后一步IDE 的默认 GBK 会把你写的中文在编译时转错。如果你用的是 IntelliJ 或 Eclipse确认项目级编码是 UTF-8。5.2 金额计算对不上账double 的精度问题现象订单总额明明是 9.9 元显示出来却是 9.900000000000004统计报表里所有数字都带一长串小数。原因float 和 double 是二进制浮点数用它们计算十进制小数会引入转换误差。数据库里字段虽然已经是 DECIMAL但 Java 实体类如果用 double 接收值从数据库读出再相加就已经失真了。解决实体类里的价格字段一律声明为 BigDecimal不要图便宜用 double代码里所有金额相加用 BigDecimal.add不要用 “” 运算符。顺手在入库时检查一次 price 是否有两位小数若超过两位就做 setScale(2, RoundingMode.HALF_UP)保证写入数据库前数据就已经是干净的。5.3 库存变成负数两个窗口同时卖同一件商品现象A 窗口卖出 5 件B 窗口同时卖出 5 件但库里只有 8 件最后库存变成 -2。原因先查库存、再算新库存、最后 UPDATE 回写三步之间没有事务或锁保护。两个窗口同时读到“库存 8”各自都按自己的扣减量写回后写的人把前一个的扣减覆盖掉了。解决把扣减语句改成一条原子 UPDATE例如上面 4.4 里的 UPDATE product SET stock stock - ? WHERE id ? AND stock ?。这一步执行时数据库加行锁后到的事务会等待或更新 0 行不会再出现负数。这里建议在描述方案时主动提一句“行锁”答辩老师会很受用。5.4 换一台机器部署就报“驱动未找到”或端口连接失败现象在开发机一切正常把项目打包成 jar 复制到另一台机器双击运行后要么 ClassNotFoundException要么连接超时。原因两个独立问题。驱动未找到是 MySQL 驱动 jar 没有打包进可运行 jar 的 lib连接失败是目标机器上 MySQL 没有开启、3306 端口被防火墙挡或者数据库密码不一致。解决打包时把驱动 jar 复制到项目一个名为 lib 的目录并加入 classpath连接参数中的 localhost 改成目标机器能访问的实际 IP密码核对清楚。运行时可加 java -verbose:class 21 | grep mysql看驱动是否真的被加载这是排查依赖问题最快的路子。5.5 日期时间差 8 小时销售记录时间总是偏早或偏晚现象销售界面显示时间是下午 3 点数据库里的 created_at 是早上 7 点差 8 个小时整。原因MySQL 8 默认时区不是你所在时区JDBC 连接里的 serverTimezone 没有设置数据库和 JVM 之间时间换算出现偏差。本机开发时往往看不出来因为数据库服务器常常也在本机但部署到云数据库或 Docker 容器后立刻暴露。解决JDBC URL 里统一加 serverTimezoneAsia/Shanghai建表时 created_at 使用 DEFAULT CURRENT_TIMESTAMP让数据库来生成时间不要用 Java 代码里 new Date() 直接拼进 SQL。入库时间让数据库说了算显示格式交给前端格式化这样时区不一致的问题只在连接层处理一次。6. 答辩前必做的一条主链路验证和两条加分验证答辩的前一天与其把所有代码重新读一遍不如做三件事把不稳定的角落提前暴露出来。第一件跑通一条完整主链路新建一个分类新建一个供应商给供应商做一笔进货把商品在收银台卖出一件再去库存查询里核对数量。这条链路覆盖了几乎所有核心表中途任何一步报错你现在就能修等答辩现场出问题就没有后悔药了。第二件演示库存不足时的边界行为。把某商品库存改成 1然后在收银台尝试买 2 件系统应该弹出友好提示而不是抛异常退出。这个行为如果你的代码里还没实现花半小时补上这是答辩时最容易启发的提问点。第三件演示异常输入比如在商品价格里输负数、在数量里输 0、用空账号直接点登录系统应能拦下来并给出提示再顺手展示一下数据库里没有任何脏数据。我建议你在答辩前亲手建一个空的测试库而不是一直用开发库里已经攒了几百条记录的旧数据。理由是新环境能验证初始化脚本的完整性也能避开“你记得某功能能用但实际依赖着某个手动改过的数据”这种坑。我自己遇到过一个尴尬场景演示用的商品因为之前手动改过库存提前被卖成负数答辩当场被老师抓到。从那以后每次演示前我都会用一个全新数据库从头初始化决不沿用旧数据。如果时间还有富余可以再补一个最简单的销售柱状图按月统计销售额。不需要引入额外图表库用 Swing 的 JPanel 重写 paintComponent 也可以画出柱形这一步不但展示代码能力也能让你在“统计报表”这个功能点上回答得更从容。把库存核对、边界拦截、数据初始化这三关走完你的心里基本就有底了。希望这篇内容能帮你在验收前少踩几个坑把注意力放回真正重要的业务链路上。本文还有配套的精品资源点击获取