☰
Java超市管理系统实战:JDBC、MySQL与Eclipse完整开发指南
2026/10/1 4:07:17 网站建设 项目流程

简介:这是一套基于Eclipse与MySQL开发的Java超市管理系统,面向需要完成课程设计或毕业设计的计算机专业学生,也适合初入JavaWeb开发的学习者借鉴项目结构与权限设计。压缩包共378个文件,包含49个Java源文件、56个JSP页面及对应class文件,另有数据库SQL脚本、前端CSS/JS与图片资源,整体大小10.37MB,目录划分清晰,可直接导入Eclipse运行调试。系统覆盖商品进销存、采购管理、数据统计和双角色登录等核心功能,可辅助理解DAO模式、Servlet交互和MySQL表关联设计。目前已有2384人学习下载,适合作为快速上手的完整项目参考。

1. 为什么Java超市管理系统会成为课程设计和毕设的常青树

一个每天处理几十笔交易的便利店后端,用Java SE + Eclipse + MySQL写一套管理端,看起来不难,却年年被选为Java课程设计案例源码的热门方向。原因是这个题目把JDBC、SQL、面向对象编程Java、事务和异常处理全串在一起,复杂度正好卡在“能独立做完”和“有东西可写”之间。这套系统要解决的不只是增删改查,而是登录权限、库存扣减、收银对账这些连锁问题。下面按我搭这套系统的顺序,从Eclipse建工程到MySQL建库,再到登录、商品管理和收银事务完整过一遍,适合准备动手写超市管理系统、又不想只抄一段跑不通的旧代码的人。

2. 从Eclipse到MySQL的工程骨架:版本组合、包结构与连接测试

2.1 版本选择:Eclipse和MySQL的合理搭配

先给结论:这套系统不用赶时髦,Eclipse就装Eclipse IDE for Java Developers,MySQL用5.7或8.0都行,关键是驱动jar要跟MySQL版本对得上。很多网上流传的「Eclipse 找不到或无法加载主类 org.apache.catalina.startup.bootstrap」报错,看着像代码问题,实际大多是Eclipse的运行时配置和编译输出互相干扰,跟超市管理系统的逻辑本身没关系,后面专门在避坑章节展开。

MySQL 5.7时代的JDBC驱动类名是com.mysql.jdbc.Driver,到了MySQL 8.0改成了com.mysql.cj.jdbc.Driver,连接URL也要加上时区参数serverTimezone=Asia/Shanghai。如果你用的还是旧代码片段,拿到8.0环境里几乎必定报时区错误或者SSL连接错误。常见做法是我会先查一下mysql-connector-java的jar包版本,再看MySQL的SELECT VERSION();结果,两者保持同一大版本,能少踩一半坑。

Eclipse版本也是同理。老教程里让你装“Eclipse IDE for Enterprise Java”或者“MyEclipse”,那是当年写Web项目留下的习惯。做桌面管理的Java超市管理系统,纯Java工程加Swing界面就够用,装“Eclipse IDE for Java Developers”体积小、启动快,也不会把Tomcat那套东西带进来。你只需要把mysql-connector-java-8.0.33.jar放进项目lib目录,右键项目选择Build Path → Configure Build Path → Libraries → Add JARs,把它加进Classpath。

提示:如果你的Eclipse里同时打开过多个Java项目,记得给每个项目单独指定JRE版本。项目右键Properties → Java Build Path → Libraries,确认JRE System Library是JavaSE-17或你机器上装的那个JDK,不要让它自动去匹配旧版本。

2.2 新建工程的包结构划分

一个能跑通收银链路的超市管理系统,代码不能全堆在默认包下。我一般会按“视图、控制、数据访问、模型”四层拆包,这个结构即使用来写期末答辩文档,也说得清楚:

com.supermarket ├── view // Swing界面类:LoginFrame、MainFrame、GoodsManageFrame ├── controller // 界面和DAO之间的中转:LoginController、SaleController ├── dao // 数据访问层:UserDao、GoodsDao、SaleDao ├── model // 实体类:User、Goods、SaleOrder、SaleItem ├── util // 工具类:DBUtil、MD5Util └── Main.java // 程序入口

划分规则很简单:view里只放界面代码,不写SQL;dao里只做数据库读写,不弹窗口;model里是纯JavaBean,字段对应表字段。这样做的理由很实际——收银台出问题时,你要能一眼定位是界面取值错了还是SQL写错了,而不是在一个500行的类里上下翻找。

新建工程时还有个小习惯:Main.java放在com.supermarket根包下,里面只做一件事:

import com.supermarket.view.LoginFrame; public class Main { public static void main(String[] args) { // 程序入口只负责启动登录窗口,业务逻辑交给controller new LoginFrame().setVisible(true); } }

这段代码的逻辑说明:启动入口不直接连接数据库,也不读取配置文件,因为登录窗口自带一个“连接测试”按钮,由用户点击时才触发数据库连接。这样设计的好处是即使MySQL没启动,程序也能先起来,方便排查环境问题。参数方面,setVisible(true)是Swing里显示窗口的固定调用,你不写它,窗口对象建了也不显示。

2.3 第一个连接测试:DBUtil类的标准写法

在正式建表之前,先把数据库连接打通。一个成熟的DBUtil类长这样:

package com.supermarket.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?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { // MySQL 8.0必须用cj包下的Driver,5.7用com.mysql.jdbc.Driver Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段代码有三处值得说明。第一,Class.forName是在Java 6以后沿用下来的驱动注册方式,虽然新版JDBC可以通过SPI自动注册,但显式写一遍能让你在驱动jar缺失时报错更直观。第二,URL里的characterEncoding=utf8是中文不乱码的前提之一,配合后面建库时的DEFAULT CHARSET=utf8mb4,才能保证商品名“农夫山泉”不变成问号。第三,useSSL=false加上allowPublicKeyRetrieval=true是MySQL 8.0用户常见的组合,因为新版驱动默认要求SSL握手,本地开发不需要。

测试连接时,我建议写一个临时main方法,而不是直接打开登录界面:

import java.sql.Connection; import com.supermarket.util.DBUtil; public class TestConnection { public static void main(String[] args) { // 手动测试连接:能看到“连接成功”再继续做界面 try (Connection conn = DBUtil.getConnection()) { System.out.println("连接成功: " + conn.getCatalog()); } catch (Exception e) { e.printStackTrace(); } } }

如果你看到ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'这类信息,说明MySQL服务没启动,先去Windows服务管理器里启动MySQL服务,不是代码问题。如果报的是Unknown database 'supermarket',说明库还没建,下一章马上处理。

3. 数据库设计先行:建库建表与初始化脚本

3.1 四张核心表的结构与字段说明

超市管理系统的数据核心就四张表:用户表、商品表、入库表、销售表。别贪多,一张供应商表或者会员表可以放到二期再扩展,先把主链路跑通。

用户表t_user字段如下:id自增主键,username唯一索引,password存MD5加密后的密文,role区分管理员和收银员。商品表t_goods重点在barcode和price,前者用于扫码场景,后者要用DECIMAL(10,2)而不是FLOAT,因为浮点金额在累计对账时会产生精度误差。库存字段stock用INT,每次入库增加、每次销售扣减,后面事务章节会处理并发扣减。

入库表t_stock_in记录每次补货的批次,包含goods_id、quantity、in_price、in_time字段。销售表拆成主从两张:t_sale保存整笔订单的收银员、总金额和下单时间,t_sale_item保存明细行,一个订单关联多条商品记录。这种拆法是为了回答两个问题:今天总营业额是多少,每个商品卖了多少件。

字段类型上最容易忽视的是DECIMAL(10,2)和DATETIME的默认值。金额不推荐用DOUBLE,哪怕是四舍五入也会在你跑一个月数据后对不上账。时间字段设置DEFAULT CURRENT_TIMESTAMP,这样插入记录时不用手动写new Date()。

3.2 初始化脚本:字符集、外键与初始数据

接下来把建库脚本落到MySQL客户端执行。先连上MySQL,然后创建数据库和表:

CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(10) NOT NULL DEFAULT 'cashier' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_goods ( id INT PRIMARY KEY AUTO_INCREMENT, barcode VARCHAR(30) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_stock_in ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, quantity INT NOT NULL, in_price DECIMAL(10,2) NOT NULL, in_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_stock_goods FOREIGN KEY (goods_id) REFERENCES t_goods(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_sale ( id INT PRIMARY KEY AUTO_INCREMENT, cashier VARCHAR(50) NOT NULL, total_amount DECIMAL(10,2) NOT NULL, sale_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_sale_item ( id INT PRIMARY KEY AUTO_INCREMENT, sale_id INT NOT NULL, goods_id INT NOT NULL, goods_name VARCHAR(100) NOT NULL, quantity INT NOT NULL, amount DECIMAL(10,2) NOT NULL, CONSTRAINT fk_item_sale FOREIGN KEY (sale_id) REFERENCES t_sale(id), CONSTRAINT fk_item_goods FOREIGN KEY (goods_id) REFERENCES t_goods(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个脚本里最值得说的是字符集和外键。utf8mb4不是utf8的别名,前者能存Emoji和生僻字,后者在MySQL里是残缺的三字节编码,存“𠮷”这类字会直接报错。外键约束FOREIGN KEY保证了不会出现指向不存在商品的销售明细,这是数据完整性的底线。

初始化数据时,至少插入一个管理员账号和几个测试商品。注意password字段要存MD5,不能存明文:

-- 密码是“123456”的MD5值:e10adc3949ba59abbe56e057f20f883e INSERT INTO t_user (username, password, role) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin'); INSERT INTO t_goods (barcode, name, category, price, stock) VALUES ('6901234567890', '农夫山泉 550ml', '饮料', 2.00, 100), ('6901234567891', '可乐 330ml', '饮料', 3.50, 80), ('6901234567892', '方便面 经典款', '食品', 4.50, 60);

这组初始数据的用途是让登录和商品列表两个功能立刻有内容可看。barcode必须唯一,因为扫码枪按条形码精确匹配商品,重复条形码会导致同一商品被遮挡。

3.3 MySQL的编码参数与连接URL的对应关系

很多新手在MySQL命令行里插入中文正常,但Java程序读出来是问号,问题出在“三层编码不一致”:数据库层、JDBC连接层、Java编译层。数据库层你在建库时已经指定utf8mb4,JDBC连接层就是DBUtil里URL那一段characterEncoding=utf8,Java源码文件本身记得在Eclipse里设置成UTF-8编码。

Eclipse里改源码编码的位置在菜单Window → Preferences → General → Workspace,Text file encoding选UTF-8。如果你用的是老项目模板,Eclipse默认可能是GBK,这时你源码里的中文字符串常量会被编译成GBK字节,跟数据库的utf8对不上,查出来的商品名照样乱码。这个坑的隐蔽之处在于:控制台输出可能正常,但写入数据库后就变了。

MySQL 8.0还有一个SSL连接错误的坑,报错信息里会出现Cannot create PoolableConnectionFactory或者SSL connection error。解决方式是URL后半段加上useSSL=false&requireSSL=false&serverTimezone=Asia/Shanghai。这一组参数不区分大小写,但必须放在URL的查询串里,中间用&连接,不能加空格。

注意:如果你遇到的是mysql ssl连接错误,并且已经加了useSSL=false还报错,检查驱动版本。MySQL 8.0.30之后的驱动对SSL处理改了默认行为,旧连接参数可能失效,最直接的办法是升级驱动到8.0.30以上。

4. 核心功能实现:从登录到收银台的完整链路

4.1 登录模块:PreparedStatement防SQL注入

登录界面从用户输入框拿用户名和密码,然后调用UserDao的findByUsernameAndPassword方法。DAO层写法如下:

package com.supermarket.dao; import com.supermarket.model.User; import com.supermarket.util.DBUtil; import com.supermarket.util.MD5Util; import java.sql.*; public class UserDao { public User login(String username, String rawPassword) { // 参数1: 密文密码,不能在SQL里拼用户输入 String md5Pwd = MD5Util.md5(rawPassword); String sql = "SELECT id, username, role FROM t_user WHERE username=? AND password=?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Pwd); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRole(rs.getString("role")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } }

这里最核心的是PreparedStatement配合?占位符,而不是用字符串拼接SQL。为什么强调这个?因为用户名里如果输入' OR '1'='1,拼接SQL就能绕过登录。虽然超市管理系统是课程设计,但这个习惯会影响你后面写Java面试题八股文时的理解深度。

MD5Util.md5是工具方法,把明文密码转成32位十六进制字符串。MD5本身不适合存重要系统密码,因为彩虹表可以反查,但课程设计里用它演示“不存明文”这个思想够了。参数说明:rs.getInt("id")按列名取数比rs.getInt(1)可读性好,后续加字段不用数位置。

登录成功后的会话状态,我用一个简单的方式控制:在LoginController里维护一个静态的currentUser对象,收银台和商品管理界面都读它。这不算安全设计,但足够让一个管理端跑起来。

4.2 商品管理:增删改查的DAO封装

商品管理的需求无非是三件套:新增商品、修改价格库存、按关键字搜商品。我把这些集中写在GoodsDao里:

public class GoodsDao { public boolean addGoods(Goods goods) { // 新增商品:先检查条形码是否存在,再insert String checkSql = "SELECT COUNT(*) FROM t_goods WHERE barcode=?"; String insertSql = "INSERT INTO t_goods(barcode, name, category, price, stock) VALUES(?,?,?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement check = conn.prepareStatement(checkSql)) { check.setString(1, goods.getBarcode()); try (ResultSet rs = check.executeQuery()) { rs.next(); if (rs.getInt(1) > 0) { return false; // 条形码已存在 } } try (PreparedStatement ps = conn.prepareStatement(insertSql)) { ps.setString(1, goods.getBarcode()); ps.setString(2, goods.getName()); ps.setString(3, goods.getCategory()); ps.setBigDecimal(4, goods.getPrice()); ps.setInt(5, goods.getStock()); return ps.executeUpdate() == 1; } } catch (SQLException e) { e.printStackTrace(); return false; } } }

这段代码在同一个方法里先后开了两个PreparedStatement,放在同一个Connection里执行,目的是先查重再插入,避免重复条形码。这里要注意的是两次try-with-resources嵌套,外层连接、内层语句,关闭顺序由语法保证,不需要手动调用close。

更新价格和库存时,SQL写成UPDATE t_goods SET price=?, stock=? WHERE id=?。如果要做批量进货,就是遍历待入库商品的集合,逐条执行UPDATE ... SET stock = stock + ?,这个累加动作要放在SQL里,不能先查旧值再Java里加完传回去,否则高并发下会丢更新。

4.3 收银台:一次完整事务的提交与回滚

收银台是整个系统里最需要严谨的环节,因为一次结账要同时做两件事:往t_sale插入订单头,往t_sale_item插入明细行,还要扣t_goods的库存。这三步必须要么全成功,要么全失败,中间任何一步出问题都会造成“订单生成了但库存没扣”或“库存扣了但订单没生成”的脏数据。

事务的标准写法是关闭自动提交,手动commit,出异常回滚:

public boolean createSale(List<SaleItem> items, String cashier) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务,JDBC默认每句SQL自动提交 // 1. 插入订单头 String saleSql = "INSERT INTO t_sale(cashier, total_amount) VALUES(?,?)"; try (PreparedStatement ps = conn.prepareStatement(saleSql, Statement.RETURN_GENERATED_KEYS)) { double total = 0; for (SaleItem item : items) { total += item.getAmount().doubleValue(); } ps.setString(1, cashier); ps.setBigDecimal(2, BigDecimal.valueOf(total)); ps.executeUpdate(); try (ResultSet keys = ps.getGeneratedKeys()) { if (keys.next()) { int saleId = keys.getInt(1); // 2. 插入明细行并扣减库存 for (SaleItem item : items) { insertSaleItem(conn, saleId, item); updateStock(conn, item.getGoodsId(), item.getQuantity()); } } } } conn.commit(); // 所有操作成功,提交事务 return true; } catch (SQLException e) { try { if (conn != null) { conn.rollback(); } } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

这段代码里有三个关键点。第一,setAutoCommit(false)必须在所有SQL执行之前调用,不然每条语句各自提交,事务形同虚设。第二,Statement.RETURN_GENERATED_KEYS让MySQL返回自增主键id,这样明细行才能挂到正确的订单头上。第三,conn.rollback()要在catch块里调用,且要判断conn不为空,因为getConnection()本身也可能抛异常,这时conn还是null。

为什么事务要手动切回setAutoCommit(true)再关闭?因为连接池复用时,如果连接归还时还处于事务未提交状态,下一个人拿到这个连接会继承你的事务状态,这是很多诡异bug的源头。我这里虽然还是DriverManager.getConnection(),但养成主动恢复现场的习惯,后面换连接池就少踩一个坑。

提示:如果收银时发现库存扣减冲突,比如两个收银员同时卖同一商品,UPDATE t_goods SET stock = stock - ? WHERE id=? AND stock >= ?这种带条件更新比先查再更安全。条件是stock >= ?,扣减量为负数时即不更新,保证不会扣成负库存。

5. 避坑:新手最容易翻车的几个点

5.1 现象:运行报ClassNotFoundException: com.mysql.cj.jdbc.Driver

原因:驱动jar没有真正进到编译后的classpath。Eclipse里出现了“Add JARs”和“Add External JARs”两个按钮,前者是把项目lib目录下的jar加进来,后者是引用磁盘任意位置的jar。如果选的是External JAR,且后来移动过jar文件,Eclipse就找不到驱动类了。

解决方式是把mysql-connector-java-8.0.33.jar直接复制到项目根目录的lib文件夹下,再通过Add JARs加进来。这样哪怕整个工程打包成zip发给别人,驱动也在项目内,换机器不丢依赖。

5.2 现象:MySQL 8.0连接时报SSL连接错误或Public Key Retrieval is not allowed

原因:MySQL 8.0默认启用caching_sha2_password认证,JDBC驱动首次连接需要从服务端获取公钥,而这个动作默认被安全策略挡在外面;SSL握手则在本地开发环境经常因为证书不匹配失败。

解决方式是在连接URL里加四个参数:useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8。别问“这样是不是不安全”——本地开发环境本来就不靠SSL保数据安全,生产环境才需要认真配置证书。

5.3 现象:Eclipse运行项目报找不到或无法加载主类 org.apache.catalina.startup.bootstrap

原因:Eclipse工作区里残留了旧Web项目的编译产出,或者你把一个纯Java工程误配成了Tomcat服务器运行方式。我见过的情况是,新手装了“Eclipse IDE for Enterprise Java”,默认带Tomcat插件,他创建的项目被加上了Dynamic Web Module的Facet,Eclipse运行面板里自动出现了Tomcat服务器关联。

解决方式:右键项目 → Properties → Project Facets,取消勾选Dynamic Web Module,然后Project → Clean清理编译输出。如果运行配置里选的是“Run on Server”,改成“Run as Java Application”,主类选择Main。这个报错跟你写的超市管理系统逻辑无关,是运行方式错配。

5.4 现象:往数据库插入中文商品名,读出全是问号

原因:不是Java代码问题,就是三层编码不一致。最容易被忽略的是Eclipse源码文件的编码,新建工程时默认继承Workspace编码,而Workspace默认可能是GBK。你源码里的字符串常量在编译时按GBK转字节,连接数据库时又按UTF-8传,两边对不上。

解决方式:把Workspace和项目编码都设置成UTF-8,然后重新编译整个项目(Project → Clean)。如果MySQL表还是建库时的utf8mb4,连接URL里也带characterEncoding=utf8,基本能解决。还有一个隐藏点:MySQL的my.ini里character_set_server也要是utf8mb4,命令行执行SHOW VARIABLES LIKE 'character_set%';能一键排查。

6. 改进与验证:让系统能真正投入小超市使用

6.1 从DriverManager切换到连接池

DriverManager.getConnection()每次创建新连接,对一个小超市的低并发够用,但到学生毕设答辩演示时,频繁开关数据库会让系统显得很慢。常见做法是引入Druid或C3P0连接池,把连接对象池化复用。Druid的配置类一般叫DruidDataSource,设置initialSize、maxActive、minIdle三个核心参数就能跑起来。

换连接池后,DBUtil.getConnection()内部改成从DruidDataSource获取,其余DAO代码不需要动。这一层的好处是:频繁的登录查询和商品列表刷新不再反复走三次握手建连,体验提升立竿见影。

6.2 上线前的功能验证清单

把系统交接给实际使用者之前,我会按这个顺序过一遍:

  1. 用新装的MySQL客户端连接一次,确认supermarket库存在,且四张表都在;
  2. 用管理员账号登录,修改商品价格,再新增一个带中文名字的商品,重启程序确认数据落盘;
  3. 登录两个收银员账号,测试同一商品并发销售,确认库存不会扣成负数;
  4. 用浏览器模拟“拔网线”——直接杀掉MySQL进程,确认收银报错而不会留下半个订单;
  5. 检查t_sale和t_sale_item的关联数据,手工算一笔账,跟界面显示的总金额比对。

我自己的习惯是最后再对着异常堆栈问一句:如果明天答辩时老师故意拔数据库网线,程序是崩成白屏还是弹个友好提示?把e.printStackTrace()换成JOptionPane.showMessageDialog提示“数据库服务未启动”,是能让评分印象分上升的小细节。

做这套系统的过程中,我最大的教训是:别急着写界面,先把SQL脚本跑通、把数据填好,界面和DAO只是在给数据库“找交互入口”。Eclipse里报的错,八成是环境问题,先把控制台第一行日志看清楚再审代码。希望这些踩坑记录能帮你少走几步弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询