简介:采用Java Swing与MySQL构建的物资信息管理系统,是一套面向Java课程设计、期末大作业及初学者实战练习的高分源码项目。系统包含登录、用户管理、物资出入库、库存查询统计等典型业务模块,代码结构清晰,可帮助读者掌握Swing界面开发、JDBC数据库操作以及物资流转逻辑。压缩包共34个文件,涵盖27个Java源码、3个SQL脚本及设计文档、README等,整体大小4.74MB,便于快速下载与本地部署。目前已有133人学习下载,项目经本地编译可运行,评审分达到98分,难度适中,并附有数据库表、存储过程、触发器及系统设计说明,适合直接作为课程设计参考或二次开发基础。
1. 课程设计选它不亏:Java+Swing+MySQL 物资信息管理系统到底在解决什么问题
期末要交 Java 课程设计,很多人第一反应是搜“Java+Swing+MySQL 的物资信息管理系统源码”,这个技术栈不新,但它恰好把 Java 基础里最常考的三块全部覆盖了:Swing 做桌面窗口、JDBC 操作 MySQL、SQL 做数据管理。这套物资信息管理系统解决的问题非常具体:把物资的入库、出库、库存查询、库存预警从 Excel 表格搬到一个小型桌面系统里,数据不再靠人工维护,查询和统计也能在界面上直接完成。它适合三类人——准备课程设计答辩的学生、刚学完 Java 基础想拿一个完整项目练手的新手,以及需要一个轻量桌面工具来做内部物资登记的小团队。接下来我会从技术选型到代码复现,把这个项目怎么做、参数怎么调、坑在哪一次说清。
2. 搭建前的关键判断:技术选型、源码目录与环境清单
2.1 为什么这个组合到现在还能打:Swing 的边界在哪里
很多人一看到 Swing 就觉得是老古董,但放在课程设计和中小型内部工具这个场景里,它反而是最稳的选择。一个物资管理系统通常是单机使用或者最多局域网内几个人同时操作,界面逻辑不复杂,数据量也就几千条到几万条,Swing 的事件驱动模型和 JTable 组件完全够用。相比之下,如果上 Spring Boot + Vue 这种前后端分离方案,你需要同时搞定 Maven 依赖、Tomcat 部署、跨域请求、前端构建工具,一套下来课程设计两周的周期根本不够折腾,答辩时还容易在环境部署上翻车。
MySQL 在这个组合里承担持久化层,它比 SQLite 更适合这个场景的原因有两个:一是 MySQL 的表结构、事务、索引概念是面试和答辩的高频考点,用它能展示的东西更多;二是绝大多数教学环境都已经装好了 MySQL,你不需要引导老师去理解“为什么用的是嵌入式数据库”。Swing 负责界面展示和事件交互,JDBC 负责把界面操作翻译成 SQL 语句,三者分工非常清楚,正好对应 Java 课程里 GUI 编程、JDBC 编程、数据库设计三个模块。
2.2 源码包目录:src、lib、sql、doc 四块怎么对应
拿到一份完整的物资信息管理系统源码,第一件事不是急着跑起来,而是先看目录结构。常见做法是四块:src 放 Java 源码,lib 放 MySQL 驱动 jar,sql 放建库建表和初始化数据的脚本,doc 放文档说明。这四块互相之间的对应关系是这样的。
| 目录 | 常见内容 | 解决什么问题 |
|---|---|---|
| src | 按包划分的 .java 文件,主要分为 model、dao、view、util 四层 | 项目代码本体,view 是 Swing 界面,dao 是 JDBC 操作,model 是实体类,util 放工具类 |
| lib | mysql-connector-java.jar 等第三方依赖 | 编译和运行时的 classpath 依赖,没有它 ClassNotFoundException 跑不掉 |
| sql | init.sql、data.sql 等脚本 | 数据库表结构和初始数据的唯一权威来源 |
| doc | 需求说明、数据库设计、操作手册等 | 答辩时要看的文档,也是查字段含义的参考书 |
这个结构里最容易被忽略的是 sql 目录。我一般拿到项目会先打开 sql 脚本,把库和表建好,再去读 doc 里的数据库设计说明。很多“高分项目”的文档写得很全,但代码和文档经常对不上,后面我会在第 4 章专门讲这个坑。还有一点,src 里如果看到了 .class 文件或者 .idea 目录,说明这个压缩包是直接从 IDE 工作目录拷贝出来的,不影响运行,但提交前最好清理掉。
2.3 环境清单:JDK 版本、MySQL 版本与字符集选择
环境不对,代码再对也跑不起来。这个项目我建议你用 JDK 8,不是因为它新,而是 Swing 应用在 JDK 8 上表现最稳定,高版本 JDK 虽然没有删掉 Swing,但模块化之后有些 IDE 里的 Swing 控件渲染反而会出现奇怪的缩放问题。如果你还没配过 Java 环境,照着 java 环境配置的教程把 JAVA_HOME 和 PATH 配好,命令行里能敲出java -version就算过关。
MySQL 版本用 5.7 或 8.0 都可以,差别主要在驱动和连接参数上。5.7 用 mysql-connector-java-5.1.x 驱动,连接串里不需要指定 serverTimezone;8.0 用 mysql-connector-java-8.0.x 驱动,连接串里必须加serverTimezone=Asia/Shanghai,否则会报时区错误。数据库字符集建议直接指定utf8mb4,尤其当物资名称里可能出现生僻字或 emoji 符号时,utf8 会直接报错或存成乱码。建库语句最好写成这样:
CREATE DATABASE IF NOT EXISTS material_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;utf8mb4_general_ci是排序规则,_ci结尾表示大小写不敏感,查询时不需要担心Material和material匹配不上的问题。如果你要自己从零搭环境,MySQL 的安装配置教程网上很多,但在 Windows 上装完 MySQL 8.0 后注意服务名和端口,默认 3306 就好,别改成别的端口,后面连接串参数也要跟着改。
3. 从建表到入库:核心代码复现与参数说明
3.1 建库建表:物资表、入库表、出库表的三表设计
物资信息管理系统最核心的就是三张表:物资表、入库记录表、出库记录表。物资表存当前库存状态,入库表和出库表存流水。为什么要拆成流水表而不是直接在物资表上修改数量?因为答辩时老师一定会问“你怎么知道这个库存数据是怎么来的”,流水表就是回答这个问题的最好凭证,每一笔库存变动都能追溯到具体的时间和操作。
CREATE TABLE material ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, category VARCHAR(30), spec VARCHAR(50), unit VARCHAR(10), stock INT DEFAULT 0, warning_limit INT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );物资表的字段设计里,code是物资编号,设成 UNIQUE 是为了防止同一编号重复录入;stock是当前库存量,每次出入库都通过事务更新它;warning_limit是预警阈值,低于这个值时界面用颜色高亮提醒。不要把spec和unit合并到一个字段里,虽然这么做省事,但统计“每种单位的物资有多少种”时 SQL 会写得很痛苦。
CREATE TABLE material_in ( id INT PRIMARY KEY AUTO_INCREMENT, material_code VARCHAR(20) NOT NULL, quantity INT NOT NULL, operator VARCHAR(20), in_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (material_code) REFERENCES material(code) ); CREATE TABLE material_out ( id INT PRIMARY KEY AUTO_INCREMENT, material_code VARCHAR(20) NOT NULL, quantity INT NOT NULL, operator VARCHAR(20), out_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (material_code) REFERENCES material(code) );material_in和material_out的结构是对称的,字段就四个:物资编号、数量、操作人、时间。外键约束在这里不是摆设,它可以保证流水表里不会出现物资表里不存在的编号。但要注意,外键会影响插入性能,数据量到十万级以上时性能下降会很明显,课程设计这个量级完全不用担心。实际开发里有人会把外键去掉,改用应用层校验,但在课程设计里保留外键反而更容易拿分。
3.2 JDBC 连接工具类:复用的连接管理与驱动加载
Swing 界面里的每个按钮事件都可能触发数据库操作,如果每个方法里都写一遍DriverManager.getConnection,代码会重复到让人崩溃。实际项目里会写一个 DBUtil 工具类,把连接参数集中管理,所有 DAO 层都通过它拿连接。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/material_db?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/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) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }关键参数逐个说。useSSL=false是因为本地开发不需要加密连接,开了反而会有证书警告;useUnicode=true&characterEncoding=utf8是解决中文乱码的核心,这两个参数缺一不可,单独写characterEncoding在某些 MySQL 版本上会被忽略;serverTimezone=Asia/Shanghai是 MySQL 8.0 驱动强制要求的时间时区参数,不加会直接报The server time zone value错误。
静态代码块里的Class.forName是加载驱动类,MySQL 驱动 5.x 和 8.x 的类名不一样,5.x 是com.mysql.jdbc.Driver,8.x 改成了com.mysql.cj.jdbc.Driver,如果你看到ClassNotFoundException,先检查是不是驱动类和 jar 包版本不匹配。
3.3 登录窗口的事件绑定与密码校验
登录模块是每个管理系统的门面,但很多学生写的登录校验有问题——把账号密码硬编码在代码里,或者用Statement拼接 SQL,这在答辩时被问到 SQL 注入就尴尬了。正确的做法是账号密码存数据库,校验过程走 JDBC,并且用PreparedStatement防止注入。
JButton loginBtn = new JButton("登录"); loginBtn.addActionListener(e -> { String username = userField.getText().trim(); String password = new String(passField.getPassword()); if (authenticate(username, password)) { JOptionPane.showMessageDialog(frame, "登录成功"); new MainFrame().setVisible(true); frame.dispose(); } else { JOptionPane.showMessageDialog(frame, "用户名或密码错误"); } });authenticate方法里是标准的 JDBC 查询,用PreparedStatement而不是Statement的原因是PreparedStatement会对参数做预编译,恶意输入的' OR 1=1 --只会被当成普通字符串处理,不会改变 SQL 语义。
private boolean authenticate(String username, String password) { String sql = "SELECT COUNT(*) FROM sys_user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { return rs.next() && rs.getInt(1) > 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }密码存储这里我只用明文做演示。实际项目必须加盐哈希,比如 SHA-256 加随机盐,这是另一个话题,但面试或答辩时主动提一句“生产环境应该用盐值哈希存储密码,这里为了演示用了明文”,反而能体现你的安全意识。
3.4 物资入库与库存更新的事务处理
物资入库不只是往material_in表插一条数据那么简单,它必须同时更新material表的stock字段。这两个操作要么都成功,要么都失败,不能出现“流水记了但库存没变”的情况,所以必须放到事务里。
public boolean doInbound(String materialCode, int quantity, String operator) { String insertSQL = "INSERT INTO material_in (material_code, quantity, operator) VALUES (?, ?, ?)"; String updateSQL = "UPDATE material SET stock = stock + ? WHERE code = ?"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement(insertSQL); PreparedStatement ps2 = conn.prepareStatement(updateSQL)) { ps.setString(1, materialCode); ps.setInt(2, quantity); ps.setString(3, operator); ps.executeUpdate(); ps2.setInt(1, quantity); ps2.setString(2, materialCode); ps2.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn != null) { try { 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,最后commit()统一提交。事务中间任何一个环节抛出SQLException,rollback()会把已经执行的 SQL 撤销回事务开始前的状态。finally块里把autoCommit恢复成true再关闭连接,这是复用连接时必须养成的习惯,否则连接被归还到连接池时还处于事务状态,会影响到后续操作。
4. 运行期翻车排查:5 个高频坑的现象、原因与解决
4.1 中文乱码,界面显示问号或菱形方块
现象:程序能跑起来,但物资名称、操作人这种中文数据存进数据库再查出来就变成了???,或者在 Swing 界面上直接显示乱码。
原因:这个坑通常是三层叠加出来的。第一层是 MySQL 客户端连接时没有指定字符集,连接串里缺少characterEncoding=utf8;第二层是表本身的字符集不是 utf8,建表时用的默认 latin1;第三层是 Java 源码文件本身不是 UTF-8 编码,javac编译时按系统默认码表解析了中文字符。
解决:按顺序修。第一步,把连接串补全为jdbc:mysql://localhost:3306/material_db?useUnicode=true&characterEncoding=utf8。第二步,检查建库语句,确认是DEFAULT CHARACTER SET utf8mb4,如果库已经建好了,可以执行ALTER TABLE material CONVERT TO CHARACTER SET utf8mb4;,注意是CONVERT不是DEFAULT。第三步,用命令行编译时加参数javac -encoding UTF-8,用 IDEA 时检查右下角文件编码是不是 UTF-8。这三步都做完还有乱码,那就是 MySQL 服务端默认字符集的问题,在 my.cnf 的[mysqld]段加character-set-server=utf8mb4重启服务。
4.2 点登录按钮直接报 ClassNotFoundException 或 SQLException
现象:代码在 IDE 里编译通过,但一运行点按钮就抛java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,或者No suitable driver found for jdbc:mysql://localhost:3306/...。
原因:Class.forName找不到驱动类,说明 mysql-connector-java.jar 没有在运行时 classpath 里。很多新手只在 IDE 里把 jar 加到了编译路径,但 IDEA 里这两个是不同的操作:Project Structure 里的 Libraries 管编译,Run Configuration 或 Module 的依赖管运行。如果两者配置不一致,编译不报错,一运行就翻车。
解决:IDEA 里右键点击 lib 目录下的 jar 文件,选择 Add as Library;Eclipse 里右键项目选 Build Path — Add External Archives。如果你坚持用命令行跑,编译和运行都要指定 classpath:
javac -encoding UTF-8 -cp ".;lib/mysql-connector-java-8.0.33.jar" src/com/example/*.java java -cp ".;lib/mysql-connector-java-8.0.33.jar;src" com.example.LoginFrame注意-cp参数中间的;在 Windows 下是路径分隔符,Linux 和 macOS 下要改成:。这行命令里的src目录也要加进去,否则 JVM 找不到编译后的 .class 文件。
4.3 点击查询后 Swing 窗口直接卡死,转圈圈
现象:点“查询物资”按钮,界面立刻无响应,鼠标变成转圈状态,过几秒甚至几十秒才弹出来结果。
原因:Swing 是单线程模型,所有 UI 事件都在 EDT(Event Dispatch Thread)上执行。你把数据库查询这种耗时操作直接写在按钮监听器里,等于占住了 UI 线程,界面刷新和鼠标点击事件全部排队等待。本地 MySQL 查询通常不至于卡太久,但如果表数据量大或者 MySQL 服务在另一台机器上,网络延迟会把这个卡顿放大到肉眼可见。
解决:把耗时操作丢到SwingWorker后台线程里执行,查询完成后在done()方法里更新界面。一个最小实现长这样:
new SwingWorker<List<Material>, Void>() { @Override protected List<Material> doInBackground() { return materialDao.queryAll(); } @Override protected void done() { try { table.setModel(buildTableModel(get())); } catch (Exception e) { JOptionPane.showMessageDialog(frame, "查询失败: " + e.getMessage()); } } }.execute();doInBackground里执行的是耗时操作,返回值会在后续传给done();done()方法本身在 EDT 上执行,所以这里可以直接操作table组件。注意在查询前可以给按钮setEnabled(false)防止用户重复点击,完成后恢复。
4.4 跑一段时间后报 Too many connections
现象:程序刚启动一切正常,连续操作十几分钟或几十次出入库后,点击按钮报Too many connections,重启程序又恢复正常。
原因:这是典型的数据库连接泄漏。代码里获取了Connection但没关闭,或者只关了ResultSet忘了关Connection。MySQL 默认的最大连接数是 151,每个连接不被释放就会一直占着,直到打满这个上限。课程设计项目里连接池都没配置,连接就是一个一个创建、丢失、再创建,非常容易触顶。
解决:Java 7 之后的 try-with-resources 语法可以自动关资源,核心代码里应该这么写:
try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { ... } }注意变量声明顺序有讲究:conn在最前,然后ps依赖conn,最后rs依赖ps。关闭顺序是自动逆序执行,先关rs,再关ps,最后关conn,这个顺序不能乱。凡是看到代码里出现DriverManager.getConnection的地方,都要确认连接是否被正确关闭。
4.5 文档说明和 sql 脚本对不上,照着文档跑不通
现象:上手先看了 doc 里的数据库设计文档,按文档里的字段名写了 SQL,结果运行报Unknown column,打开 sql 脚本一看,字段名根本不一样。
原因:源码包在迭代过程中改过表结构,比如把material_num改名成material_code、删掉了remark字段,但文档说明没有同步更新。这在“源码+文档说明(高分项目)”里是重灾区,文档写得越详细越容易出这个事,因为文档通常是在项目初期架构阶段写的,而代码是写到最后才改的。
解决:拿到项目先执行 sql 脚本,把数据库真正建起来,再以实际表结构为准去核对代码。遇到字段不一致,优先改代码而不是改数据库——数据库已经初始化了数据,改表结构可能触发外键约束报错。如果文档里写了实体类字段和表字段的映射关系,对照着逐条核一遍,把文档当成参考,把 sql 脚本当成唯一事实来源。这个问题没法在代码层面修复,只能靠流程习惯规避。
5. 让系统支持多人同时录入:从单机到局域网做对 4 处改动
一个能答辩的物资管理系统通常是单机版,但如果你想把项目价值再往上提一档,最值得做的就是把它改造成局域网可访问版本,让仓库管理员和采购员能在各自电脑上同时操作。这个改造不需要重写代码,四处置改就能完成。
第一处,连接串从localhost改成 MySQL 服务器的局域网 IP,同时把DBUtil里的URL抽到配置文件里,不要硬编码。第二处,MySQL 需要授权允许远程主机访问:GRANT ALL PRIVILEGES ON material_db.* TO 'root'@'%' IDENTIFIED BY '密码'; FLUSH PRIVILEGES;,注意'%'表示所有主机,生产环境应该缩小到指定网段。
第三处,多人同时操作时事务冲突概率上升,MySQL 的默认隔离级别是REPEATABLE READ,在并发写入时容易出现锁等待。如果录入界面操作时莫名卡住,检查innodb_lock_wait_timeout参数,默认 50 秒,可以适当调小到 10 秒,让冲突快速报错而不是长时间挂着。第四处,Swing 界面的表格数据不会自动刷新,我在做局域网改造时加了一个Timer定时器,每 30 秒重新拉一次库存列表,这样别人刚入库的物资,在你这边的界面上也能自动出现,体验提升非常明显。
这几处改造里最让我长记性的是远程授权那一步。当时代码改了 IP,网络放通了,防火墙也加了规则,另一台电脑就是连不上,后来发现是 MySQL 用户只授权了localhost,压根没给远程主机权限。这种问题不看 MySQL 的user表很难定位,所以后来我每次排查连接问题,第一反应就是先查user表的host字段。物资信息管理系统本身不难,难的是把这些边界情况想全,希望这些内容对你有帮助。
本文还有配套的精品资源,点击获取