ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java Swing与MySQL仓库管理系统源码解析:从JDBC事务到JTable界面优化

Java Swing与MySQL仓库管理系统源码解析:从JDBC事务到JTable界面优化 简介基于Java Swing与MySQL构建的仓库管理系统完整设计源码面向高校软件工程类专业学生及有仓储信息化需求的开发者可用于课程设计、毕业设计或中小型仓库管理项目的二次开发。压缩包内共39个文件整包大小约8.45MB以15个Java源文件为主体实现商品出入库、库存查询、表单校验、权限控制、报表生成与日志记录等功能另含10个JAR依赖包、7个XML配置、properties、iml、gitignore与PNG图标等辅助文件源码按dao、entity、frame、utils等包组织分层明确便于对照Swing界面、业务逻辑与MySQL数据层的协作方式。系统利用JFrame、JTable等组件构建跨平台GUI并通过MySQL表结构与事务处理机制保证数据一致性和安全性适合快速上手实际仓储业务。资源目前已有118人学习是一份理解SwingMySQL整合开发思路的实用范例可直接作为仓库管理系统的功能框架与代码模板。1. Java Swing MySQL 仓库管理系统这套源码解决的是桌面端仓储管理的最后一公里接手过几个用 Excel 管仓库的中小企业最头疼的就是出入库记录和对账。这套基于 Java Swing MySQL 的仓库管理系统不是那种动辄几百个文件的大型框架而是把仓储管理的核心逻辑——商品档案、入库、出库、库存查询、操作日志——落成了一个可离线运行的桌面应用。它胜在好读、好改、能落地15 个 Java 源文件把 entity、dao、frame、config 分得清清楚楚数据库用 MySQL 存业务数据JDBC 直连没有 Spring Boot 那套复杂依赖。适合正在学 Java SE 的人、想找课程设计或毕业设计骨架的人以及需要一套桌面端 WMS 做二次开发的从业者。下文我会从文件结构、数据库设计、界面交互到避坑排查拆一遍告诉你这套源码到底该怎么用、改哪里、踩过什么坑。2. 盘点 37 个文件的源码构成从目录结构看懂系统的边界2.1 文件清单这 37 个文件各自承担什么职责拿到压缩包先别急着解压跑起来我建议先按文件类型过一遍清单搞清楚哪些是源码、哪些是依赖、哪些是配置。这套源码的文件分布大致如下类型数量典型文件作用Java 源文件15frame/、dao/、entity/、utils/、config/系统全部业务逻辑和界面代码JAR 包10MySQL 驱动、JGoodies、LGoodS 等Swing 布局增强、数据库连接等第三方依赖XML 配置7dataSources.xml、modules.xml、.idea/下配置IDE 项目配置与数据库连接配置Git 忽略文件2.gitignore排除编译输出和本地配置其他3.iml、.properties、.png项目描述、连接属性、界面图标读源码的顺序有讲究。建议先读entity包再看dao包然后看frame包最后看config和utils。为什么呢因为entity定义的是数据模型对应数据库表结构这是整个系统的地基dao负责和 MySQL 打交道你会在里面看到 JDBC 连接、PreparedStatement、ResultSet 这些老熟人frame是 Swing 界面类通过调用dao的方法把数据渲染到 JTable、JComboBox 上。提示config包里一般放着数据库连接参数比如 URL、用户名、密码。拿到手第一件事就是看这个文件数据库连不上后面全是白搭。2.2 包结构与分层逻辑一个典型的 Java SE 工程该有的样子这套源码没有用 Spring、MyBatis 这类重框架而是朴素的包分层符合 Java SE 课程设计和中小型桌面项目的常见套路。entity包放实体类字段对应数据库列名dao包放数据访问对象封装所有 SQL 操作frame包放 JFrame 窗体是用户看到的界面utils包放工具类比如字符串处理、日期格式化config包放配置读取逻辑通常配合.properties文件。一个值得注意的设计是dao包里的类不应该直接操作 Swing 组件frame包里的类也不应该写 SQL。这个边界一旦被打破代码很快就会变成一锅粥。我见过很多课设项目把 JDBC 代码直接写在按钮的事件里界面一复杂就彻底没法维护。这套源码的分层是标准的、合理的你做二次开发时也应该保持这个习惯。换句话说如果你打算把数据源从 MySQL 换成别的数据库只需要改dao包和config包frame完全不用动——这就是分层的价值。2.3 为什么选 Java Swing MySQL 而不是 Web 方案现在很多人一上来就问为什么不用 Spring Boot Vue。这个问题要结合场景看这套系统跑在本地桌面双击启动不依赖 Tomcat 容器MySQL 装在本机或局域网机器上就能用。对仓库现场来说Swing 的响应速度比 Web 页面快得多表格刷新没有网络延迟而且离线也能用。部署成本也低一台 Windows 电脑装个 JDK 和 MySQL 就够了。MySQL 作为后端存储胜在成熟稳定。开源免费、资料多、SQL 语法标准配合 JDBC 驱动几行代码就能建立连接。这个组合适合数据量在百万行以内、并发用户数在几十以内的仓储场景完全够用。当然也有它的局限Swing 界面不够现代按钮和布局的观感停留在十年前MySQL 本身的性能和扩展性也有限数据量大了需要分库分表或者引入缓存。但对于课设、毕设、企业内部工具级别的需求这套组合是最务实的选择。3. 从 CRUD 到事务处理MySQL 表结构与 DAO 层实现细节3.1 数据库表设计仓库管理的核心表长什么样要理解这套源码就得先还原它的数据库设计。一个典型的仓库管理系统至少包含五张核心表用户表、商品表、库存表、入库记录表、出库记录表。用户表存放登录账号、密码、角色权限商品表存放商品基本信息如编号、名称、规格、单位、单价库存表记录每种商品当前剩余数量通常是商品表的一对一扩展入库记录表和出库记录表是流水表每发生一次操作就插一行。从源码中entity包的类能反推出两张最重要的表结构。商品表大概长这样CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, product_code VARCHAR(50) UNIQUE, product_name VARCHAR(100) NOT NULL, spec VARCHAR(100), unit VARCHAR(20), price DECIMAL(10,2), stock INT DEFAULT 0, created_time DATETIME DEFAULT CURRENT_TIMESTAMP );库存表的逻辑和product.stock字段有些重复实际项目中有两种做法一种是不建独立库存表直接在商品表里维护stock字段每次出入库后UPDATE这个字段另一种是独立建inventory表存商品 ID 和数量支持多仓库扩展。这套源码的实体类里如果存在Inventory类那就是后者。从实用性角度看单仓库场景直接用product.stock字段最省事查询也快多仓库场景就必须拆表了。索引设计也是性能的关键。product_code要加唯一索引因为商品编号是业务查询的最常用条件stock字段不要建索引区分度太低MySQL 优化器也不会用它。出入库记录表的外键字段product_id必须建索引否则两表 JOIN 时全表扫描数据量到十万行以后查询会明显变慢。3.2 DAO 层封装 JDBC 操作连接管理、SQL 参数化与结果映射dao包里的代码是这套源码里最有复用价值的部分。常见的写法是在BaseDao或直接在DBUtil工具类里管理 Connection然后每个 DAO 类写具体的增删改查方法。连接管理用DriverManager.getConnection()是最朴素的方案不引入连接池适合桌面应用的低并发场景。一个典型的查询方法这么写public ListProduct findAll() { ListProduct list new ArrayList(); String sql SELECT * FROM product ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setProductCode(rs.getString(product_code)); p.setProductName(rs.getString(product_name)); p.setSpec(rs.getString(spec)); p.setUnit(rs.getString(unit)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); p.setCreatedTime(rs.getTimestamp(created_time)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码有四个值得注意的点。第一用try-with-resources自动关闭 Connection、PreparedStatement、ResultSet避免连接泄漏第二使用PreparedStatement而不是拼接 SQL能有效防止 SQL 注入第三DBUtil.getConnection()是静态方法内部从config包读数据库地址、账号、密码这样改连接信息不用重新编译第四查询结果手动映射到实体类字段名和表列名保持一致这行代码才写得顺手。如果这套源码里的 DAO 写法和我描述的有差异那大概率是某些方法直接用了Statement。遇到Statement的写法建议改成PreparedStatement尤其在搜索框支持模糊查询的场景下拼接 SQL 的风险非常高。3.3 核心业务逻辑入库出库操作怎么保证数据一致性仓库管理系统最核心的业务方法是入库和出库。简单做法是入库时先插入一条入库流水记录再更新商品表的库存字段stock stock 1出库则相反stock stock - 1同时校验库存是否足够。这个流程涉及两次数据库操作如果不开启事务会出现严重问题——比如插入入库记录成功但库存没更新。正确的做法是把两次操作包在同一个事务里要么都成功要么都失败。DAO 层的代码大致是这样public boolean inStock(int productId, int quantity, String operator) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 第一步插入入库记录 String sql1 INSERT INTO in_record(product_id, quantity, operator, create_time) VALUES(?,?,?,NOW()); PreparedStatement ps1 conn.prepareStatement(sql1); ps1.setInt(1, productId); ps1.setInt(2, quantity); ps1.setString(3, operator); ps1.executeUpdate(); // 第二步更新库存 String sql2 UPDATE product SET stock stock ? WHERE id ?; PreparedStatement ps2 conn.prepareStatement(sql2); ps2.setInt(1, quantity); ps2.setInt(2, productId); 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.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这是 JDBC 事务的标准写法教科书和面试题都是这个套路。注意setAutoCommit(false)必须在执行第一条 SQL 之前调用commit()在所有操作完成后调用rollback()只在catch块里调用finally里只关闭连接不提交——这几个位置不能搞混。在 MySQL 环境下还要确认建表时用的引擎是 InnoDB这个点经常踩坑。如果用 MyISAM事务是不生效的commit()和rollback()都白搭。检查方法很简单执行SHOW TABLE STATUS看每张表的Engine字段是不是 InnoDB。3.4 数据校验与权限控制桌面系统也不能省掉的防线很多课设项目在数据校验上是糊弄的比如前端只做必填判断后端收到什么存什么。这会导致库存表出现负数、价格字段出现负值这类脏数据。正规做法是在dao层写checkStock、checkPrice之类的方法或者在 DAO 里通过 SQL 条件约束完成。比如出库前先查库存是否足够代码上是一个校验方法public boolean hasEnoughStock(int productId, int quantity) { String sql SELECT stock FROM product WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, productId); ResultSet rs ps.executeQuery(); return rs.next() rs.getInt(stock) quantity; } catch (SQLException e) { e.printStackTrace(); return false; } }权限控制的常见实现是用户表带role字段admin和operator两种角色。frame包里的登录窗口根据角色决定哪些按钮可用、哪些菜单可见。比如普通操作员只能做入库出库管理员才能看报表和用户管理。这种粗粒度的权限控制在桌面应用里够用了没有必要引入 Shiro 之类的权限框架。4. Swing 界面与数据交互把 JTable 变成实时库存看板4.1 JTable 数据绑定把查询结果渲染到表格上界面上最常用的组件是 JTable它用来展示商品列表、出入库记录。JTable 本身不直接接收 List 数据需要包一层 TableModel。最常用的写法是DefaultTableModel把查询结果一行一行往里塞DefaultTableModel model new DefaultTableModel(new String[]{ ID, 商品编号, 商品名称, 规格, 单位, 单价, 库存 }, 0); for (Product p : productDao.findAll()) { model.addRow(new Object[]{ p.getId(), p.getProductCode(), p.getProductName(), p.getSpec(), p.getUnit(), p.getPrice(), p.getStock() }); } table.setModel(model);表格列宽和排序在 Swing 里是出了名的反直觉。JTable默认不排序需要调用TableRowSorterTableRowSorterDefaultTableModel sorter new TableRowSorter(model); table.setRowSorter(sorter);这段代码很重要加了之后点击表头就能按列排序。如果不加用户只能盯着原始顺序看体验很差。注意排序之后行号会变取值时要用table.convertRowIndexToModel()转换否则很容易取错行数据。4.2 事件监听与界面刷新按钮点击后数据是怎么流回来的Swing 是事件驱动模型。入库按钮点击后整个流程是获取表单输入值 → 调用 DAO 方法 → 刷新表格 → 清空表单。这里最容易翻车的是刷新时机。如果在 DAO 操作完成后没有重新查询数据库表格就停留在旧数据。常规做法是这样的btnInStock.addActionListener(e - { try { int productId Integer.parseInt(txtProductId.getText()); int quantity Integer.parseInt(txtQuantity.getText()); if (inStockDao.inStock(productId, quantity, CurrentUser.getUsername())) { refreshTable(); // 重新加载表格数据 JOptionPane.showMessageDialog(frame, 入库成功); } else { JOptionPane.showMessageDialog(frame, 入库失败); } } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(frame, 数量和商品ID必须是数字); } });refreshTable()内部就是重新执行一次 DAO 查询然后重新setModel。这个方法的调用位置很关键必须放在事务提交之后不能放在弹出提示之前否则用户看到入库成功但表格还是旧数据就会怀疑系统是不是漏了什么。表单校验的位置也值得注意。上面这段代码用try-catch拦截了数字解析异常这是最基本的防线。更完整的校验还包括数量不能为 0 或负数、商品 ID 必须存在、出库数量不能超过库存。这些校验逻辑写在按钮事件里还是 DAO 里取决于团队约定但至少要有不能全靠数据库报错兜底。4.3 搜索与筛选功能典型实现与参数绑定带搜索条件的库存查询是这个系统的刚需。搜索框一般用JTextField下拉框用JComboBox查询按钮触发 DAO 里的条件查询方法。SQL 的LIKE模糊查询要注意占位符写法public ListProduct searchByName(String keyword) { ListProduct list new ArrayList(); String sql SELECT * FROM product WHERE product_name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ResultSet rs ps.executeQuery(); while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setProductName(rs.getString(product_name)); p.setProductCode(rs.getString(product_code)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }注意LIKE ?和%keyword%这一组匹配关系在 SQL 语句里写%?%是无效的必须在 Java 端把%拼进参数。这是新手写模糊查询时最常见的报错点之一。搜索参数的值建议在方法入口做一次空值判断。如果keyword为null或空字符串直接查全表或返回空列表不要在 SQL 层面做太多动态拼接。条件组合多了以后SQL 拼接会变得非常难维护。5. 避坑与排查JDBC 连接失败、中文乱码与 Swing 刷新卡顿5.1 MySQL 连接失败驱动版本与 SSL 配置的双重坑现象双击运行程序登录窗口迟迟不弹出来控制台报Communications link failure或Access denied for user。原因分两种情况。第一种是 MySQL 5.7 与 MySQL 8.0 的驱动类名不同MySQL 8.0 之后驱动类从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver连接 URL 还需要加serverTimezoneAsia/Shanghai。如果代码里还在用旧驱动类连新数据库直接报类找不到。第二种是账号密码不匹配MySQL 8.0 默认使用caching_sha2_password认证老驱动不支持。解决先确认本地 MySQL 版本然后用mysql -u root -p登录验证账号密码。如果代码没问题就把 JAR 包换成对应版本的驱动。连接 URL 统一写成jdbc:mysql://localhost:3306/wms?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8useSSLfalse用来关闭 SSL 加密连接本地开发场景不需要 SSL 也能连MySQL 8.0 默认开启 SSL不显式关掉在部分 JDK 版本下会报 SSL 握手失败。5.2 中文乱码写入、查询、表格显示三个环节都要统一编码现象界面上的中文可以正常显示但从数据库里查出来的中文字段变成了一串???或者反过来从界面写进数据库的中文查询时是乱码。原因MySQL 连接 URL 里少了characterEncodingutf8参数或者数据库表本身是 latin1 字符集。两者的编码不一致数据在写入时就已经错了查回来自然是乱码。解决第一步改连接 URL加上characterEncodingutf8第二步确认数据库和表的字符集执行ALTER TABLE product CONVERT TO CHARACTER SET utf8mb4;第三步在DBUtil.getConnection()里打印一下 Connection 的 URL确认配置被加载了。utf8mb4 是 utf8 的超集能存 emoji 和生僻字建议直接用 utf8mb4。5.3 Swing 界面卡顿数据库操作阻塞了 UI 线程现象点击查询库存按钮后整个窗口卡住不动转圈过几秒才恢复响应。原因数据库查询是耗时操作直接写在 Swing 事件监听器里会阻塞事件分发线程EDT。EDT 被堵住界面渲染、按钮点击、拖动窗口全部失去响应。解决把数据库查询丢到后台线程里执行查询完成后再通过SwingUtilities.invokeLater()回到 EDT 更新界面。标准写法是new Thread(() - { ListProduct list productDao.findAll(); SwingUtilities.invokeLater(() - { DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); for (Product p : list) { model.addRow(new Object[]{p.getId(), p.getProductCode(), p.getProductName()}); } }); }).start();如果数据量不大几百行单线程直接查询也可以接受。但查询超过一秒的就建议按上面这个模式改成后台线程这是 Swing 程序性能优化的第一步。5.4 JTable 刷新后数据没变或行列错乱现象执行了model.setRowCount(0)和循环addRow但界面上显示的还是旧数据或者行数翻倍。原因换了新的DefaultTableModel但 JTable 还持有旧的 model 引用或者往旧 model 里addRow时没有先清空旧数据。后者最常见——model变量指向同一个实例上一次的 100 行数据还在又往里追加了 100 行表格越点越长。解决每次刷新前先调用model.setRowCount(0)清空再添加新行。如果要换整张表的列结构就重新创建 model 并setModel(...)。另外要区分清空行数和移除所有行的区别setRowCount(0)是标准做法table.removeAll()对 JTable 无效。6. 把课设改造成能用的系统换数据库、导数据、加报表的三个实战技巧拿到这套源码最值得做的第一件事不是改代码而是把部署环境完整跑通。按这个顺序验证装好 MySQL 8.0 → 用dataSources.xml里的账号密码建库 → 把entity包里的字段还原成建表 SQL → 运行frame包的主类 → 登录 → 新增商品 → 入库 → 查库存 → 出库。整个链路走通说明这套源码在你手上是活的后面改哪里心里都有底。第二步做数据迁移。仓库管理系统的价值在真实数据手工录入太慢建议写一个 Excel 导入工具。Apache POI 是常见选择读 Excel 文件后逐行调用productDao.insert(...)。导入时注意商品编号重复的问题建议导入前先查重用product_code做唯一约束。第三步是加报表导出功能。课程设计里如果没做报表需求这套源码大概率也没有导出逻辑。做一个简单的 CSV 导出按钮代码量不大但实用价值很高private void exportStockReport() { try (PrintWriter writer new PrintWriter( new OutputStreamWriter(new FileOutputStream(stock_ System.currentTimeMillis() .csv) , StandardCharsets.UTF_8))) { ListProduct list productDao.findAll(); writer.println(编码,名称,库存,单价); for (Product p : list) { writer.println(p.getProductCode() , p.getProductName() , p.getStock() , p.getPrice()); } } catch (IOException e) { e.printStackTrace(); } }注意 CSV 文件用 Excel 打开如果乱码就把导出文件的编码改成带 BOM 的 UTF-8或者只导出英文表头。这不是系统逻辑问题是 Windows 下 Excel 对 UTF-8 的兼容性顽疾。从那以后我每次拿到别人写的 Swing 项目都先看dao包里连接管理是怎么写的再顺手把所有Statement替换成PreparedStatement这个习惯帮我避开了很多上线后的数据安全事件。希望你也能从这套源码里练出手感把课设级别的代码改成真正能扛事的工具。希望帮到你。本文还有配套的精品资源点击获取
返回列表