资讯详情 Java Swing + SQL Server 超市管理系统课程设计复现与二次开发指南
📅 2026/10/28 12:59:34
简介这份Java超市管理系统实训报告面向计算机相关专业学生与Java初学者服务于课程设计、数据库原理实践与项目答辩等场景帮助读者完整走通从需求分析到系统测试的数据库应用开发流程。压缩包内共1个doc文档约411KB以报告正文形式组织涵盖课程设计目的与意义、系统需求与可行性分析、开发技术及运行环境、系统结构框架与功能设计、数据库E-R模型与数据表设计等章节并延伸至用户管理、商品管理、库存管理、销售记录等核心模块的实现思路与测试优化方法。该资源已有5778人学习下载读者可借助其清晰的目录结构与规范的报告体例快速理解超市管理系统的设计脉络参考E-R图与表结构设计方法并借鉴实训报告的写作框架与项目总结思路适合作为课程设计参考与实战练手素材。1. 从一份课程设计报告说起这套超市管理系统到底能跑出什么翻到一份十年前的课程设计报告课题是超市管理系统技术栈写着 Java SQL Server 2005报告里贴了建库语句、七张表结构、登录验证和商品录入的代码片段。很多人看到这种材料第一反应是过时了但真正拿它当二次开发起点的人会发现它把进销存最核心的数据关系讲清楚了职工、商品、供货商、进货、销售、库存六张业务表加一张登录表外键串起来就是一条完整的业务链。这份报告适合两类人一是刚学完 Java 和数据库、想找一个能完整跑通的练手项目的新手二是需要快速搭一个进销存原型、再往上加功能的开发者。它不追求界面花哨胜在结构完整、逻辑直白照着报告里的表设计和模块划分能在一两天内把骨架立起来。2. 技术选型与运行环境为什么是 Java 桌面端加 SQL Server2.1 报告里的技术栈拆解报告正文写得很明确前台开发工具 Eclipse后台数据库 SQL Server 2005运行环境 Windows XP界面基于 Windows 图形用户界面。这里有个细节值得注意——报告第 3 章写前台Microsoft Visual Studio 2005但第 6 章的代码却是 C# 风格的SqlDataReader、MessageBox.Show、ListViewItem和标题里的Java 程序设计对不上。这是这类课程设计报告常见的拼接痕迹文档模板和代码来源不是同一套。对拿这份材料做二次开发的人来说处理方式有两种。第一种是忠于标题用 Java 重写Swing 做界面JDBC 连数据库PreparedStatement替代字符串拼接。第二种是忠于代码按 C# WinForms ADO.NET 的路子走。我一般建议选第一种因为标题和摘要都指向 Java而且 Java 的生态在后续扩展上更顺手。下面所有代码示例都按 Java 路线给表结构和业务逻辑完全沿用报告里的设计。技术选型的合理性在于这是一个单机、单用户、数据量在几千条量级的进销存系统不需要 Web 容器不需要分布式桌面端加本地数据库是最省事的组合。用 Swing 而不是 JavaFX是因为 Swing 在 JDK 里自带、零额外依赖课程设计场景下启动成本最低。数据库用 SQL Server 是报告的历史选择实际复现时换成 MySQL 或 PostgreSQL 也完全可行SQL 语法差异很小主要改连接串和驱动。2.2 环境准备与依赖清单按 Java 路线复现需要准备的东西不多但版本要对齐否则会卡在一些莫名其妙的地方。组件建议版本说明JDK8 或 118 兼容性最好Swing 组件行为稳定数据库MySQL 5.7 / 8.0替代 SQL Server语法改动小JDBC 驱动mysql-connector-java 5.1.x 或 8.x与数据库版本匹配IDEEclipse 或 IntelliJ IDEA报告用 EclipseIDEA 也行构建工具无手动加 jar或 Maven小项目手动加 jar 更快数据库连接部分报告里用的是DataClass.MyMeans这种自定义类封装连接Java 侧对应写一个DBUtil单例即可。常见做法是把连接串、用户名、密码放在一个db.properties里避免硬编码。// DBUtil.java —— 数据库连接工具类 import java.sql.*; import java.io.*; import java.util.Properties; public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties p new Properties(); p.load(in); url p.getProperty(jdbc.url); user p.getProperty(jdbc.user); password p.getProperty(jdbc.password); Class.forName(p.getProperty(jdbc.driver)); } catch (Exception e) { throw new RuntimeException(数据库配置加载失败, e); } } // 每次调用返回新连接由调用方负责关闭 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }db.properties内容大致是jdbc.urljdbc:mysql://localhost:3306/db_smms?useUnicodetruecharacterEncodingutf8、jdbc.userroot、jdbc.password你的密码、jdbc.drivercom.mysql.jdbc.Driver8.x 驱动写com.mysql.cj.jdbc.Driver。这里的关键点是驱动类名和 URL 参数characterEncodingutf8不加的话中文商品名会变问号这是血泪经验。2.3 从报告到可运行工程的落地步骤报告本身是文档不是工程压缩包所以第一步是把文档里的表结构翻译成建表脚本。报告第 5 章给了七张表的字段定义直接照着写 DDL 即可。注意报告里库存表的建表语句有个明显错误PRIMARY KEY (StockID)但字段列表里只有GoodsID和AmountStockID根本没定义。复现时要补上这个字段或者干脆用GoodsID做主键。-- 建库 CREATE DATABASE db_smms DEFAULT CHARACTER SET utf8mb4; -- 职工信息表 CREATE TABLE staff_bill ( staff_id CHAR(6) PRIMARY KEY, name VARCHAR(8) NOT NULL, sex CHAR(2) NOT NULL, kind VARCHAR(10), id_card VARCHAR(18) NOT NULL, others VARCHAR(50) ); -- 用户登录表 CREATE TABLE users_info ( login_id CHAR(6) PRIMARY KEY, pass_word CHAR(6) NOT NULL ); -- 商品信息表 CREATE TABLE goods_info ( merch_id CHAR(6) PRIMARY KEY, proffer_id CHAR(6) NOT NULL, full_name VARCHAR(80) NOT NULL, kind VARCHAR(8), unit_cost DECIMAL(10,2) NOT NULL, others VARCHAR(50), CONSTRAINT fk_goods_sup FOREIGN KEY (proffer_id) REFERENCES sup_info(supplier_id) ); -- 供货商信息表 CREATE TABLE sup_info ( supplier_id CHAR(6) PRIMARY KEY, name VARCHAR(50) NOT NULL, contact_person VARCHAR(8) NOT NULL, phone VARCHAR(12) NOT NULL, address VARCHAR(50) NOT NULL, others VARCHAR(50) ); -- 进货信息表 CREATE TABLE buy ( buy_id CHAR(6) PRIMARY KEY, goods_id CHAR(6) NOT NULL, supplier_id CHAR(6) NOT NULL, price DECIMAL(10,2) NOT NULL, amount INT NOT NULL, sum DECIMAL(10,2) NOT NULL, date DATETIME NOT NULL, remark VARCHAR(50), CONSTRAINT fk_buy_goods FOREIGN KEY (goods_id) REFERENCES goods_info(merch_id), CONSTRAINT fk_buy_sup FOREIGN KEY (supplier_id) REFERENCES sup_info(supplier_id) ); -- 销售信息表 CREATE TABLE sell ( sell_id CHAR(6) PRIMARY KEY, goods_id CHAR(6) NOT NULL, amount INT NOT NULL, date DATETIME NOT NULL, CONSTRAINT fk_sell_goods FOREIGN KEY (goods_id) REFERENCES goods_info(merch_id) ); -- 库存信息表 CREATE TABLE stock ( goods_id CHAR(6) PRIMARY KEY, amount INT NOT NULL DEFAULT 0, CONSTRAINT fk_stock_goods FOREIGN KEY (goods_id) REFERENCES goods_info(merch_id) );建表顺序有讲究goods_info依赖sup_infobuy依赖goods_info和sup_info所以sup_info必须最先建。报告里没强调这一点直接按目录顺序执行会报外键错误。建完表插一条管理员账号INSERT INTO users_info VALUES (admin1, 123456)就能进登录界面了。3. 数据库设计与核心表关系七张表撑起进销存闭环3.1 E-R 关系与主外键链路报告第 5 章给了 E-R 模型图虽然图没贴出来但从表结构能反推出关系供货商一对多商品商品一对多进货记录商品一对多销售记录商品一对一库存。职工表和登录表是独立的不参与业务外键只做权限和操作人记录。这条链路的价值在于任何一次进货或销售都能追溯到具体商品和供货商库存表则是商品表的延伸——商品建档时同步插入一条库存记录数量初始为 0。报告第 6 章商品添加代码里正好体现了这个设计先insert into tb_Goods再insert into tb_Stock两条语句在同一个 try 块里失败一起回滚虽然原代码没显式开事务这是个隐患后面避坑章节会讲。3.2 库存视图与联表查询报告 5.3 节给了一个库存视图v_StockSAS把库存表和商品表内连接输出商品名、库存量、备注。这个视图是库存管理界面的数据源避免每次查询都手写 JOIN。CREATE VIEW v_stock_sas AS SELECT s.goods_id, g.full_name AS goods_name, s.amount, g.others AS remark FROM stock s INNER JOIN goods_info g ON s.goods_id g.merch_id;视图的好处是界面层只认v_stock_sas这一个名字后面如果库存表加字段改视图定义就行不用动 Java 代码。参数说明s.amount是当前库存量g.full_name是商品全名g.others借用作备注。查询时直接SELECT * FROM v_stock_sas WHERE amount 10就能筛出低库存商品做补货提醒。3.3 编号生成策略报告代码里反复出现MyMC.GetAutocoding(tb_Goods, GoodsID)这是一个自动编号生成方法。课程设计里常见的做法是查当前表最大 ID加一后补零到固定长度。Java 侧实现如下// 生成形如 G00001 的编号 public static String nextId(Connection conn, String table, String column, String prefix) throws SQLException { String sql SELECT MAX( column ) FROM table; try (Statement st conn.createStatement(); ResultSet rs st.executeQuery(sql)) { if (rs.next()) { String max rs.getString(1); if (max ! null) { int n Integer.parseInt(max.substring(prefix.length())) 1; return prefix String.format(%05d, n); } } } return prefix 00001; }参数说明table是表名column是编号列prefix是前缀商品用 G、进货用 B、销售用 S。这个方案在单机单用户场景够用但并发下会撞号——两个人同时点添加可能拿到同一个 max 值。课程设计不涉及并发实际小超市单收银台也问题不大但如果要扩展成多终端就得换成数据库序列或加锁。这是二次开发时第一个要评估的点。4. 功能模块实现登录、商品、职工三条主线的代码拆解4.1 登录验证模块报告 6.1 节给了Veri方法逻辑是拼 SQL 查用户表SqlDataReader.Read()返回 true 就放行同时把登录名和 ID 存到静态变量里供后续使用。Java 侧改写时最大的改动是把字符串拼接换成PreparedStatement防注入。public class LoginService { // 返回登录用户ID失败返回 null public static String verify(String userId, String password) { if (userId null || userId.trim().isEmpty() || password null || password.trim().isEmpty()) { return null; } String sql SELECT login_id FROM users_info WHERE login_id ? AND pass_word ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, userId.trim()); ps.setString(2, password.trim()); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getString(login_id); // 登录成功 } } } catch (SQLException e) { e.printStackTrace(); } return null; } }逻辑说明先做空值校验避免拼出WHERE login_id这种无效查询PreparedStatement的占位符由驱动负责转义用户输入 OR 11也注入不进去。原报告代码用temDR.GetString(0)取第一列Java 侧用列名login_id更稳列顺序变了也不受影响。登录成功后把返回的 ID 存到会话对象里Swing 里可以用一个Session单例持有当前用户替代报告里的静态变量MyMeans.Login_ID。4.2 商品信息管理模块报告 6.2 节的btnAdd_Click是整份材料里信息量最大的片段先做四个字段的非空校验再查重名然后生成两个编号执行两条 insert最后往 ListView 里追加一行。这个流程把校验—查重—写库—刷界面四步走得很完整值得逐段对照。Java 侧的商品添加服务public class GoodsService { // 添加商品成功返回 true public static boolean addGoods(String name, String typeId, String price, String keep, String remark) throws SQLException { try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 try { // 1. 查重 try (PreparedStatement ps conn.prepareStatement( SELECT 1 FROM goods_info WHERE full_name ?)) { ps.setString(1, name); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { conn.rollback(); return false; // 商品名已存在 } } } // 2. 生成商品编号和库存编号 String goodsId nextId(conn, goods_info, merch_id, G); // 3. 插入商品 try (PreparedStatement ps conn.prepareStatement( INSERT INTO goods_info(merch_id, proffer_id, full_name, kind, unit_cost, others) VALUES (?, ?, ?, ?, ?, ?))) { ps.setString(1, goodsId); ps.setString(2, typeId); ps.setString(3, name); ps.setString(4, keep); ps.setBigDecimal(5, new BigDecimal(price)); ps.setString(6, remark); ps.executeUpdate(); } // 4. 同步插入库存记录初始为 0 try (PreparedStatement ps conn.prepareStatement( INSERT INTO stock(goods_id, amount) VALUES (?, 0))) { ps.setString(1, goodsId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } } } }参数说明typeId对应报告里的comboxType选中项实际是供货商编号或商品类别编号报告代码里取的是My_Set.Tables[0].Rows[comboxType.SelectedIndex][0]即下拉框数据源的第一列。price用BigDecimal而不是double金额计算不会出现0.10.20.30000000000000004这种精度问题。keep是保质期报告里当字符串存实际可以改成日期字段。和原代码的差别在于事务报告代码两条 insert 之间没有begin transaction第一条成功第二条失败时商品表会留下一条没有库存记录的孤儿数据。加上setAutoCommit(false)和rollback之后要么都成功要么都不写这是二次开发时必须补的一课。4.3 职工信息管理模块报告 6.3 节只贴了btnSave_Click的开头逻辑和商品添加类似校验姓名非空生成职工编号插入staff_bill。职工表的字段里id_card是NOT NULL但报告代码没校验身份证号直接插会抛数据库异常。复现时要在界面层补一个 18 位长度校验或者把该字段改成可空。职工模块的删除操作有个隐藏依赖如果该职工在别处被引用比如销售记录里记了操作人直接删会破坏数据完整性。报告的表设计里销售表没有职工外键所以删除是安全的但二次开发如果加了销售员字段删除前就得先检查引用。常见做法是软删除加一个status字段标记离职而不是物理删除。4.4 进货与销售模块的库存联动报告 6.5 和 6.6 节分别讲添加销售信息和进货信息但没贴完整代码。从业务逻辑推断这两个模块的核心是写流水 改库存两步。进货时库存增加销售时库存减少且销售前要检查库存是否足够。// 销售扣减库存库存不足返回 false public static boolean sell(String goodsId, int amount) throws SQLException { try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try { // 1. 查当前库存并加行锁 int stock; try (PreparedStatement ps conn.prepareStatement( SELECT amount FROM stock WHERE goods_id ? FOR UPDATE)) { ps.setString(1, goodsId); try (ResultSet rs ps.executeQuery()) { if (!rs.next()) { conn.rollback(); return false; } stock rs.getInt(amount); } } if (stock amount) { conn.rollback(); return false; } // 2. 写销售流水 String sellId nextId(conn, sell, sell_id, S); try (PreparedStatement ps conn.prepareStatement( INSERT INTO sell(sell_id, goods_id, amount, date) VALUES (?, ?, ?, NOW()))) { ps.setString(1, sellId); ps.setString(2, goodsId); ps.setInt(3, amount); ps.executeUpdate(); } // 3. 扣库存 try (PreparedStatement ps conn.prepareStatement( UPDATE stock SET amount amount - ? WHERE goods_id ?)) { ps.setInt(1, amount); ps.setString(2, goodsId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } } }FOR UPDATE是这里的关键查库存时锁住该行防止两个收银台同时读到 10 件、各卖 8 件、最后库存变成 -6。单机场景用不上但代码里加上不亏属于现在不写以后补起来更痛的那类细节。进货逻辑对称把-换成流水表换成buy即可。5. 避坑与常见问题复现这份报告时最容易翻车的五处5.1 表结构前后矛盾直接执行建表脚本报错现象照抄报告 5.3 节的CREATE TABLE Stock语句数据库提示StockID列不存在。原因报告里字段列表只写了GoodsID和Amount主键却引用了StockID是文档编辑时的遗漏。解决以 5.2.7 节的字段定义表为准库存表主键用GoodsID或者补一个自增的StockID并把GoodsID设为唯一索引。我一般选前者商品和库存一对一GoodsID做主键最自然。5.2 中文乱码商品名存进去变成问号现象添加商品后列表里显示???。原因数据库字符集、连接串字符集、JDBC 驱动三者不一致。报告用的是 SQL Server复现换 MySQL 后建库时没指定utf8mb4或者连接串漏了characterEncodingutf8。解决建库语句带DEFAULT CHARACTER SET utf8mb4连接串加useUnicodetruecharacterEncodingutf8MySQL 8 驱动还要确认serverTimezone参数否则可能连不上。三处对齐后乱码消失。5.3 登录成功但后续操作提示未登录现象登录界面验证通过进主界面后点任何功能都报请先登录。原因报告代码把登录状态存在DataClass.MyMeans.Login_ID静态变量里但登录成功后执行了My_con.Close()和Dispose()如果后续模块重新 new 了一个MyMeans实例静态变量可能被重置。解决Java 侧用独立的Session单例存当前用户和数据库连接对象解耦连接用完就关会话状态单独维护。这是典型的状态和资源混在一起的坑。5.4 删除商品时外键约束报错现象在商品管理界面点删除弹出Cannot delete or update a parent row。原因该商品在buy或sell表里有流水记录外键挡住了删除。报告的表设计里外键没加ON DELETE CASCADE这是对的——流水不能跟着商品一起消失。解决删除前先查流水表有记录就提示该商品存在进出货记录不能删除或者改成软删除加is_deleted字段查询时过滤。直接级联删除是后悔药都买不到的操作。5.5 编号生成撞号主键冲突现象连续快速添加两条商品第二条报Duplicate entry G00002。原因nextId先查MAX再插入两步之间没有锁并发时两个请求拿到同一个最大值。解决单机场景可以在nextId方法上加synchronized或者把整个添加流程包在一个事务里并对编号表加行锁。更彻底的做法是用数据库自增列或序列表但报告用的是定长字符编号改成自增要动界面显示逻辑权衡后加锁更省事。6. 二次开发改造思路从课程设计到能用的原型拿到这套代码最值钱的改造不是换界面皮肤而是把三处课程设计妥协补成生产可用的样子。第一处是事务边界。报告里商品添加的两条 insert、销售的三步操作都没有显式事务。改造时统一在 Service 层开setAutoCommit(false)所有写操作走同一个Connection成功commit、异常rollback。验证方法很简单在第二条 insert 前手动抛一个异常看商品表是否留下脏数据改造前会留改造后不会。第二处是输入校验下沉。报告把校验全写在按钮点击事件里if (txtName.Text )一路 else if 下去字段一多就成面条。改造时抽一个Validator工具类用注解或配置描述每个字段的规则非空、长度、数字范围界面层只调Validator.validate(form)返回错误列表。这样加字段不用改事件代码也方便后面接 Web 端复用同一套规则。第三处是报表与统计。报告只做到增删改查没有经营分析。库存视图v_stock_sas已经是个好的起点往上加两个查询就能出日报按日期聚合sell表算日销售额按商品聚合算销量排行。下面这个查询可以直接挂到主界面的今日概览面板上。-- 今日销售汇总总单数、总件数、总金额 SELECT COUNT(*) AS order_count, SUM(amount) AS total_items, SUM(amount * g.unit_cost) AS total_amount FROM sell s JOIN goods_info g ON s.goods_id g.merch_id WHERE DATE(s.date) CURDATE(); -- 近 7 天销量前 5 的商品 SELECT g.full_name, SUM(s.amount) AS sold FROM sell s JOIN goods_info g ON s.goods_id g.merch_id WHERE s.date DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY g.full_name ORDER BY sold DESC LIMIT 5;注意SUM(amount * g.unit_cost)用的是商品当前单价不是销售时的成交价。如果要做准确的毛利分析sell表得加一个unit_price字段销售时把当时单价快照进去。这是我在类似项目里踩过的坑一开始图省事用当前价算历史销售额商品调价后历史报表全变了对不上账。从那以后我每次做流水表都把成交单价、成交数量、金额三个字段一起落库宁可多存一列也不让报表依赖可变数据。改造完这三处这套系统就从能演示变成能给小超市用。再往上走可以加多用户权限报告 6.8 节提了权限设置但没展开、加条码扫描枪输入Swing 里监听键盘事件即可、把数据库换成 SQLite 做单文件部署。每一步都不大但每一步都得先把事务和校验的地基打牢否则功能越多数据越乱。希望这份拆解能帮你少走几段弯路。本文还有配套的精品资源点击获取