☰
Java课设超市管理系统实战:JDBC分层架构与MySQL从环境到事务
2026/10/4 11:12:29 网站建设 项目流程

简介:一份面向高校Java课程设计的超市管理系统完整项目,整体采用命令行文本交互,全面覆盖商品、库存、销售等核心业务的数据维护,适合需要利用Java与MySQL完成课程设计,并希望深入理解JDBC与分层架构的开发者。资源包共49个文件,包含22个Java源码及其编译后的class文件、1个SQL建表脚本、1个MySQL连接驱动的jar包、以及工程配置文件与IDE设置文件,整体压缩包约783KB。目前已有1379人学习下载,源码按control、dao、po、db、view、main六个包组织,清晰区分控制、数据访问、实体、数据库与视图等职责;SQL脚本可直接导入MySQL生成数据表,配合驱动可快速跑通数据库连接流程。通过阅读和运行这套代码,读者能掌握JDBC连接管理、SQL异常处理、分层编程等关键技能,也能为独立开发同类管理信息系统提供一套可复用的课设参考。

1. 一个 Java 课设超市管理系统,到底值不值得动手跑一遍

如果你是 Java 初学者,手上正好需要一个“能交差、能讲清楚、还能在答辩时说出点门道”的课程设计,这类java 超市管理系统 + MySQL的控制台项目是最合适的练手样本。它没有 Spring、没有 MyBatis,就是纯 Servlet 时代的经典写法:JDBC 连库、PO 装数据、DAO 做查询、Control 管逻辑、View 做交互。很多人在网上下到这类压缩包后第一反应是“代码能跑吗”,我的经验是:八成跑不起来的人不是代码有问题,而是 Java 版本、MySQL 版本、驱动 jar 和编码这四座山没搬走。

这个项目正是这样一份素材——里面有完整的 src 分层源码、数据库脚本 supermarket.sql、还有配好的驱动包 lib/mysql-connector-java-5.1.18-bin.jar。适合三类人:第一次做课设想照葫芦画瓢的新手、想复习 JDBC 和分层设计的求职者、以及需要快速改造成自己的系统拿去答辩的同学。这篇文章我会从压缩包结构开始,一步步带你把它跑起来,再把最容易踩的坑挨个点名。

2. 先看懂压缩包:supermarket.zip 的五包结构与运行入口

2.1 文件清单速览:每个目录是干嘛的

拿到supermarket.zip,先别急着双击运行。我一般会先解压看一眼目录结构,心里有个地图,后面改代码时才知道去哪找文件。

路径作用
supermarket.sqlMySQL 数据库脚本,里面是建表语句和初始数据,是整个系统的数据地基
src/源码根目录,五个包全在这
src/control/业务逻辑层,处理登录校验、商品增删改查、销售等业务规则
src/dao/数据访问层,所有 JDBC 操作都在这一层
src/po/实体类,对应数据库表结构,比如商品表、用户表
src/db/数据库连接管理,通常是一个工具类负责拿 Connection
src/view/交互层,控制台菜单、输入输出都在这里
lib/依赖 jar,只有 mysql-connector-java-5.1.18-bin.jar,说明这是一个纯 JDBC 项目
bin/编译后的 class 文件,能直接跑的话入口就在这
.classpath/.projectEclipse 工程配置文件,说明这份代码是用 Eclipse 写的

有一个细节值得注意:项目里没有 pom.xml,也没有 build.gradle,所以它不是 Maven/Gradle 工程。想导入 IDE 的话,Eclipse 直接 Open Projects from File System 最省事,IDEA 里选 Eclipse 导入也可以,就是路径配置要手动指一下。

2.2 五包职责:这份分层对应什么样的架构思想

有人看到control、dao、po、db、view这五个包名会问:这不是和 MVC 差不多吗?确实,它就是 MVC 的一种轻量变体,只不过把 Controller 叫成了 Control,把 Model 拆成了 PO 和 DAO 两层。

PO(Plain Object)层最朴素,一个类对应一张表,类里的私有字段对应表的列。比如商品表有id、name、price、stock四个字段,Goods.java里就一定有四样东西:四个字段、四个 getter、四个 setter。这个层不掺任何 JDBC 代码,它只负责“搬数据”。

DAO 层是 JDBC 的核心战场,这里写SELECT、INSERT、UPDATE、DELETE语句,用PreparedStatement绑定参数,把结果集ResultSet转成 PO 对象返回。这层最容易出现代码混乱的地方是很多人喜欢在这里写业务判断,比如“库存不足不能卖”——这其实是 Control 层的活,放在 DAO 里就会导致 Control 层除了调 DAO 无事可做,架构直接塌了。

Control 层是业务中枢,登录时校验用户名密码、卖货时先查库存再扣库存、进货时更新商品数量,这些“先干什么后干什么”的规则都在这一层。DB 层最简单,通常就是一个DBUtil或者ConnectionManager类,负责加载驱动、返回连接。View 层是控制台交互,打印菜单、读取输入、调用 Control 层的方法,它不知道 SQL 长什么样,也不直接碰数据库。

2.3 从包结构和 SQL 反推业务流程

如果你拿到的supermarket.sql里有几张核心表,业务流就很好推了。通常是这样的:管理员表(登录用)、商品表(存商品名、进价、售价、库存)、销售记录表(存每一次卖出商品的明细)。对应的控制台菜单大体是登录 → 主菜单 → 商品管理(增删改查)→ 进货 → 销售 → 退出。

业务操作涉及的表典型 SQL
登录管理员表SELECT * FROM user WHERE username=? AND password=?
查看商品列表商品表SELECT * FROM goods
新增商品商品表INSERT INTO goods(name,price,stock) VALUES(?,?,?)
修改价格商品表UPDATE goods SET price=? WHERE id=?
删除商品商品表DELETE FROM goods WHERE id=?
销售扣库存商品表 + 销售表UPDATE goods SET stock=stock-? WHERE id=?

这些 SQL 在 DAO 层里几乎都会出现。我建议你拿到压缩包后先打开supermarket.sql看一遍表结构,再对照 src 里的 DAO 类看一遍,业务逻辑的骨架基本就清楚了。这一步做完,你对整个系统的理解深度会超过大多数直接抄代码的同学。

3. 从零跑起来:JDK、MySQL 与驱动 jar 的装配顺序

3.1 环境准备:JDK 版本怎么选

这个项目的驱动是 5.1.18,对应的是 JDBC 4.0 规范,Class.forName 加载驱动那行代码可以保留也可以去掉——JDBC 4.0 之后驱动会自动注册,但课设代码里基本都保留着Class.forName("com.mysql.jdbc.Driver"),跑起来没影响。

JDK 版本我建议用 Java 8。原因很现实:这个驱动 jar 和 MySQL 5.x 的组合在 Java 8 下最流畅,不会碰到模块化带来的java.sql模块访问问题。如果你机器上装的是 Java 17,编译大概率能过,但运行时可能会遇到“cannot access class com.mysql.jdbc.Driver in module”之类的幺蛾子,没必要给自己加戏。装完 JDK 后在命令行验证一下:

java -version javac -version

两条命令都显示版本号就说明环境变量配好了。如果java -version有输出但javac提示找不到命令,那就是JAVA_HOME配了但PATH里没加%JAVA_HOME%\bin,这是新手常犯的顺序问题。把JAVA_HOME指到 JDK 安装目录,再在 Path 里追加一条%JAVA_HOME%\bin,重新打开命令行就生效了。

3.2 数据库准备:导入 supermarket.sql 的完整操作

MySQL 版本方面,5.5、5.7 都能直接跑这份脚本。如果你装的是 MySQL 8.0,脚本能导入,但驱动的兼容问题会在第 5 章细说。导入前先建库,因为很多课设脚本里不包含CREATE DATABASE语句,直接用source导入会报“No database selected”。

mysql -uroot -p

登录 MySQL 后依次执行下面三条命令:

CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4; USE supermarket; SOURCE D:/path/to/supermarket.sql;

SOURCE后面要用绝对路径,这是最常被忽略的一点。如果你嫌命令行麻烦,用 Navicat 或 DataGrip 的“运行 SQL 文件”功能也行,但导入完成后务必确认两件事:数据库名是不是叫supermarket、表是不是都建出来了。用SHOW TABLES;看一眼,如果结果为空,说明脚本没导入成功,回去查路径和编码。这里有个容易踩的坑:脚本文件如果是以 UTF-8 编码保存的,但 MySQL 客户端默认用 GBK 读取,导入后表里中文注释会变成乱码,不影响功能但影响观感。导入前可以先用记事本打开 sql 文件看一眼编码,确认是 UTF-8 再操作。

3.3 驱动 jar 的正确装配方式

lib/mysql-connector-java-5.1.18-bin.jar是整个项目唯一的外部依赖,它的位置决定了程序能不能连上数据库。Eclipse 里的操作是:右键项目 → Build Path → Configure Build Path → Libraries → Add External JARs,选中 lib 目录下的驱动包。IDEA 里操作不同:File → Project Structure → Modules → Dependencies → 加号 → JARs or directories,选同一个 jar。如果这一小步漏了,报错会非常经典:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。

另外注意,不要把已经导入的 jar 再复制到src目录下,那样有时会造成编译混乱。正确的做法只有一个:jar 待在 lib 目录,被 Build Path 引用,编译时它参与,运行时它也在 classpath 里。我见过有人把 jar 复制到 src 下面结果报了一堆莫名其妙的重复类错误,属于自己给自己挖坑。

3.4 连接参数:从配置文件到 DBUtil 的补全逻辑

这个项目的db包里应该有数据库连接工具类,常见的写法是读一个名为db.properties的配置文件。如果压缩包里没有这个文件,通常是因为代码里写死了连接参数。我建议你不管原来怎么写,统一改成读配置文件的形式,答辩时也好说“我用配置文件管理数据库连接参数”。

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

对应工具类的关键代码大概是这样的:

public class DBUtil { private static String url; private static String username; private static String password; static { try { // 从 classpath 根目录加载 db.properties Properties props = new Properties(); InputStream is = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); props.load(is); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }

这段代码的核心逻辑是:类加载时通过静态块读取db.properties,注册驱动,然后把连接参数保存在三个静态变量里。getConnection()每次调用都通过DriverManager返回一个全新的连接。注意db.properties要放在src根目录下,编译后才会出现在 classpath 根路径,否则getResourceAsStream会返回 null 然后抛 NullPointerException。配置文件里password改成你自己的数据库密码,URL 里的supermarket必须和 3.2 节建的库名完全一致。

4. 看懂 JDBC 登录链路:从 db 层到 view 层的调用怎么串起来

4.1 登录模块的 DAO 实现:参数化查询比字符串拼接强在哪

打开src/dao/下的用户表 DAO,大概率能看到一个findUserByUsername(String username)或者login(String username, String password)方法。这是整个系统里最值得细读的一段代码,因为它是 JDBC 六步的完整示范:注册驱动、获取连接、创建语句、执行查询、处理结果集、释放资源。驱动注册已经挪到 DBUtil 静态块里了,所以 DAO 里实际只剩后五步。下面是常见的实现方式:

public class UserDao { public User login(String username, String password) { String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; // 连接和结果集需要在 finally 里释放,防止连接泄漏 try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; } }

这段代码里最值得讲的是PreparedStatement的参数绑定。ps.setString(1, username)把第一个问号替换成变量,替换过程由 JDBC 驱动处理,它会自动对特殊字符做转义,所以即使用户输的是' OR '1'='1这种字符串,也只被当成普通文本而不是 SQL 代码片段。如果你在原项目里看到的是字符串拼接 SQL,比如"SELECT * FROM user WHERE username='" + username + "'",那这个系统存在 SQL 注入漏洞,答辩时老师一追问就会露馅。我的建议是:不管原代码怎么写的,登录方法一律改成参数化查询,这是课设里性价比最高的安全改造。

另外一个细节是返回值和 null 的语义。方法返回User对象,查到了就返回带数据的对象,查不到返回 null,由上层 Control 判断 null 之后输出“用户名或密码错误”。这意味着 SQL 的WHERE条件要把用户名和密码同时带上,属于“一次查验证全部凭证”的写法。另一种思路是先查用户名再比对密码,多一次查询但能区分“用户不存在”和“密码错误”两种提示,那种写法更利于用户体验,但课设里两种都常见。

4.2 Control 层怎么接住 DAO 的返回值

Control 层的职责是拿到 DAO 的结果后做业务判断。登录的 Control 方法通常长这样:

public class LoginControl { private UserDao userDao = new UserDao(); /** * 返回值 1 表示登录成功,0 表示用户名或密码错误 */ public int login(String username, String password) { User user = userDao.login(username, password); if (user == null) { return 0; } return 1; } }

Control 层不直接拼 SQL,也不自己打开数据库连接。它只是把 View 传来的两个字符串原样转交给 DAO,然后对返回的User对象做判断。这里有几个命名习惯值得学习:方法名直接叫业务动作login,返回int而不是布尔值——这样将来如果有“账号被锁定”的第三种状态,只需要把返回值改成 2,View 层加一个分支就行。如果你拿到手的项目里这个方法返回的是boolean,也不影响功能,只是扩展时稍微费点事。

4.3 View 层:控制台怎么把流程串起来

View 层的 main 方法大概是整个项目里最长的类,因为它要负责打印菜单、接收键盘输入、调用 Control、再根据返回值打印不同提示。核心结构是while(true)循环加 switch 分支:

public class MainView { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("=== 超市管理系统 ==="); System.out.print("用户名: "); String username = sc.nextLine(); System.out.print("密码: "); String password = sc.nextLine(); LoginControl loginControl = new LoginControl(); int result = loginControl.login(username, password); if (result == 0) { System.out.println("用户名或密码错误"); return; } while (true) { System.out.println("1.商品管理 2.进货 3.销售 4.退出"); int choice = sc.nextInt(); // 根据 choice 调用不同的 Control 方法 } } }

注意看这段代码里的三个层次:sc.nextLine()负责读输入,loginControl.login()触发业务逻辑,返回值决定页面走向。这正好对应了 view→control→dao→db 的调用方向,也是整个项目最重要的理解主线——数据从键盘进,一路穿过 view 和 control,到达 dao 变成 SQL 参数,最后以 ResultSet 的形式原路返回。很多人课设做完不知道自己在写什么,就是把这条链路当成了“背代码”,实际上只要按这个方向读代码,每一步的输入输出都清清楚楚。

5. 常见问题与排查:五个课设里最容易翻车的点

5.1 环境类报错:驱动没加载、连接被拒绝

现象一:控制台抛java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。这是出现频率最高的报错,原因只有一个——驱动 jar 没有被加入项目的 Build Path,或者加了但加错了地方。注意ClassNotFoundException是运行时才报的,代码能编译不代表运行没问题。解决步骤:先确认 lib 目录下有那个 jar,再重新做一遍 3.3 节的导入操作。如果你是 IDEA 用户,导入之后注意看 Dependencies 列表里有没有红叉,有红叉说明 jar 路径失效了,重新选一次即可。

现象二:java.sql.SQLException: Access denied for user 'root'@'localhost'。数据库密码不对,或者用户名不对。原因通常是 db.properties 里的密码和你 MySQL 实际密码不一致。很多人把数据库密码写到代码里之后忘了自己改过密码,排查时第一件事就是去数据库客户端实测一下root/密码能不能登录。解决:修改db.properties里的jdbc.password,改完必须重启程序,因为连接参数只在类加载的静态块里读一次。

5.2 数据类报错:库名对不上、中文变乱码

现象三:java.sql.SQLException: Unknown database 'supermarket'。这个报错意思很明确——URL 里写的库名在 MySQL 里不存在。原因一般是只导入了 sql 脚本里的表,但没建数据库,或者建库时名字不一样。解决:按 3.2 节的顺序先执行CREATE DATABASE supermarket再导入。如果你用的是 Navicat 的“运行 SQL 文件”,它会默认在某个库下执行表创建语句,如果你没选库就会跑到系统库里,这时程序连过来自然找不到表。排查方法是进 MySQL 执行SHOW DATABASES;看有没有 supermarket 这个库,再USE supermarket; SHOW TABLES;看表在不在这。

现象四:登录成功后控制台输出全是???或者乱码。数据库里的中文、控制台输出的中文都乱成一锅粥。原因有两个方向:数据库连接 URL 里没有指定字符集,或者 MySQL 表本身的字符集不是 utf8。解决:URL 加上?useUnicode=true&characterEncoding=utf8,这是 5.1 驱动时代最标准的写法。如果你用 MySQL 5.7,也顺手把default-character-set=utf8加进 MySQL 的 my.ini 的[mysql]段再重启服务。Windows 的 cmd 窗口默认用的是 GBK 码页,Java 程序输出 UTF-8 字符到 cmd 时也会显示乱码,这是显示环境的问题不是程序问题,把 cmd 切到 UTF-8(执行chcp 65001)或者直接改用 IDEA 的控制台运行就能看清楚。

5.3 版本类报错:驱动 5.1.18 撞上 MySQL 8.0

现象五:MySQL 8.0 环境下运行时出现Communications link failure或The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。这是驱动 5.1.18 和 MySQL 8.0 的时区特性冲突导致的。5.1 这个驱动年代,MySQL 还没有强制校验 serverTimezone,到了 8.0 版本,连接握手时服务器时区上报给客户端,旧驱动解析不了这个中文时区描述就直接拒绝连接。解决有两个方向:第一个是把 URL 加上?serverTimezone=Asia/Shanghai&useSSL=false,5.1.18 驱动认这个参数,加了就能连;第二个是一劳永逸地换驱动 jar,去 Maven 中央仓库下载mysql-connector-java-8.0.xx.jar,放到 lib 目录替换旧的,然后代码里把驱动类名从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver。我更推荐后者,因为新版驱动对 8.0 的认证插件支持更完整,也不会再碰到时区这个玄学问题。

6. 课设加分项:把销售模块改成事务版的实操技巧

6.1 一个连接完成两个写操作

课设里的销售功能通常是两步:往销售记录表插入一条数据,再扣减商品库存。原项目如果分别在 DAO 里打开两次连接、各执行各的,就会存在一个数据一致性隐患——插入成功了但扣库存失败,或者反过来,数据库里留下一笔残缺记录。这在答辩时是个非常好的加分点,改造思路也很简单:让两个 DAO 方法共用同一个 Connection,最后统一提交或回滚。

具体做法是给 DAO 方法增加一个带 Connection 参数的版本,由 Control 层负责开启事务:

public class SaleControl { public boolean sale(int goodsId, int count, double totalPrice) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 SaleDao saleDao = new SaleDao(); GoodsDao goodsDao = new GoodsDao(); saleDao.insertSale(conn, goodsId, count, totalPrice); goodsDao.reduceStock(conn, goodsId, count); conn.commit(); // 两步都成功才提交 return true; } catch (Exception e) { e.printStackTrace(); try { if (conn != null) { conn.rollback(); // 任何一步失败就回滚 } } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(conn, null, null); } } }

注意这里 DAO 方法签名换成了insertSale(Connection conn, ...),而不是原来的insertSale(...),这是为了把连接控制权上交给调用方。为什么必须这样做?因为事务的作用范围就是“同一个连接从 setAutoCommit(false) 到 commit”之间。如果用两个连接,各自自动提交,事务就无从谈起。这个改造说明了一个容易被忽略的道理:JDBC 事务和数据库连接的绑定关系。

6.2 验证方法:用一组测试数据跑通全流程

改造完成后,验证它是否真的可靠,做法很简单:在事务里故意抛一个异常,比如在insertSale之后手动throw new RuntimeException("模拟异常"),然后运行销售功能。如果事务生效,你会发现库存没有被扣减,数据完整回滚。验证完把测试代码删掉,恢复正常的销售流程。这是我最常用的自测手段,比盯着代码干想快得多。

然后跑一遍完整流程:用管理员的账号登录,进入商品管理新增一件商品,价格设 5 元库存设 10 件,再去销售模块卖 3 件,最后回商品列表查看库存应该变成 7,销售记录表里多了一行。如果每一步查到的数据都符合预期,说明这套控制台版的超市管理系统已经能正常运作了。这套流程也适合答辩演示时的准备脚本,照着走不会卡壳。

有一件事我记得很清楚:我当年第一次改这类课设时,把扣库存和插订单写进了两个连接里,测试时发现库存扣了订单却没插进去,账目对不上,当时还以为是 MySQL 出问题了。从那以后我只要遇到一个业务涉及多个写操作,都会强制把连接收拢到同一事务里走一遍,这个习惯帮我在好几个项目里避免过数据不一致的尴尬。希望这篇拆解能帮你把这个课设跑通,也能让你在答辩时多一个真正能讲透的技术点。

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

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

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

立即咨询