基于MySQL+Java的仓库管理系统:JDBC连接、事务与避坑指南
简介这是一个基于MySQL与Java技术栈开发的仓库管理系统完整项目面向计算机、数学、电子信息等专业的课程设计、期末大作业与毕业设计场景适合已掌握Java基础、希望实战数据库增删改查与桌面端界面开发的读者。项目包含全部源码、数据库脚本及运行所依赖的库整体下载后可按说明部署运行也可作为二次开发起点。资源共26个文件压缩包大小960KB主要文件类型有Java源码、FXML界面文件、XML配置、SQL脚本、JAR依赖库和项目配置文件等覆盖建库建表、业务逻辑、界面交互与工程配置的完整链路目录结构清晰便于按模块拆解。目前已有342人浏览学习。借助这套仓库管理系统读者能快速理解仓库出入库、库存管理等业务的数据组织方式掌握JavaFXML构建管理系统的典型流程与调试方法同时可基于自带SQL脚本自行扩展功能是课程实战和毕设阶段值得参考的完整资料。1. 这套 MySQLJava 仓库管理系统源码能直接跑但别急着跑答辩前三天拿到这套压缩包解压后先导入 SQL页面还没开控制台先红了一屏——这是我第一次拆仓库管理系统课设源码的真实经历。基于MySQLJava的仓库管理系统项目源码压缩包内是完整的 IDEA 工程和一份独立的Warehouse management.sql代码量不大却把 JDBC 连接、增删改查、出入库流水这几条主线都覆盖了属于典型的课程设计案例源码。它适合三类人正在做 Java 课程设计还没凑齐功能的人想把数据库设计讲清楚的人以及单纯想拿一套能跑的 Java 课设复盘实现逻辑的新手。先说结论这套东西能导入能运行但库表关系和事务边界的处理才值得细看建议动手前把整篇笔记过一遍。2. 项目结构与数据模型先搞懂文件布局和五张核心表2.1 工程目录IDEA 项目 libs 依赖没有 Maven 也能编译解压之后的第一印象是“朴素”。顶层没有pom.xml也没有 Gradle 配置最显眼的是src、libs、Warehouse management.sql和.idea。这种结构在课设里非常常见说明作者当时用的是 IntelliJ IDEA并且把依赖 jar 放在了libs目录下手动管理。drug.iml这个文件暴露了一点背景这个工程大概率是从某个医药仓储场景改过来的但核心的表结构和代码逻辑已经通用化做普通仓库管理系统课设完全够用。路径作用使用建议srcJava 源码按包组织重点看 dao、model、ui 三块libs外部依赖 jar包括 MySQL 驱动用 IDEA 打开后需手动挂到 ModuleWarehouse management.sql建库、建表、初始数据导入时注意字符集和库名.idea/drug.imlIDEA 工程配置保留即可版本换了自己的也能用依赖直放在libs而不是走 Maven对一个课程设计来说不是坏事。答辩现场通常不能保证网络畅通点开 IDEA 的项目结构把libs里的驱动和控件库作为依赖加进 Module 就能编译这避免了 Maven 远程拉包失败的尴尬。做法上我一般会先确认libs下是否有mysql-connector-java的 jar没有的话去本机 Maven 仓库或离线目录里拿一个 5.1.49 或 8.0 系列的驱动补进去版本不对后面连接串也会出问题。2.2 核心表设计库存、货品、流水各司其职仓库管理系统最怕的就是一张大表从头写到尾。拆这份 SQL 脚本会发现表是按业务拆开的。一套合格的仓库课设至少应该包含这几类表用户表、货品表、库存表、流水表和仓库表。以库存表为例核心字段一般设计成下面这个样子CREATE TABLE t_stock ( id INT AUTO_INCREMENT PRIMARY KEY, goods_id INT NOT NULL COMMENT 货品ID, warehouse_id INT NOT NULL COMMENT 仓库ID, batch_no VARCHAR(50) DEFAULT COMMENT 批次号, expire_date DATE DEFAULT NULL COMMENT 有效期, quantity DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 当前数量, UNIQUE KEY uk_goods_warehouse_batch (goods_id, warehouse_id, batch_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存表;这里把quantity定义成DECIMAL(10,2)而不是FLOAT或DOUBLE原因很直接浮点数在累计加减时会产生精度误差做仓库盘点时 0.1 斤的差异都可能解释不清。UNIQUE联合索引解决的是同一个货品在同一仓库同一批次重复插入的问题配合INSERT ... ON DUPLICATE KEY UPDATE写入库逻辑会省很多事。字符集用utf8mb4比utf8多覆盖了 emoji 和特殊字符导入导出时也更不容易翻车。2.3 SQL 脚本里的两个约定单库单文件数据全在 DML 里打开的Warehouse management.sql会发现它把建库、建表、初始数据全放在了一个文件里。这种做法和线上项目的惯例不同但对一个课设是合理的答辩时只需导入一次就能把整个库结构跑起来。脚本里一般会先CREATE DATABASE IF NOT EXISTS然后USE再创建表最后用多条INSERT写入初始管理员账号和几个示例货品。这意味着你不需要手动建库直接在 Navicat 或命令行里source整个文件即可。注意点在于 DEFAULT 值。有些字段允许 NULL有些则设置成空字符串或 0。比如quantity的默认值是0.00而不是NULL。这个细节在判断“库存为空”时区别很大用NULL判断要写IS NULL用0判断直接 0代码写起来顺手很多。顺带一提MySQL 里设置默认值为 0 是最常见的做法但要注意DECIMAL类型的默认值必须和字段精度匹配写成0还是0.00取决于定义否则某些版本会直接报语法错误。3. JDBC 连接与增删改查从工具类到表格数据源3.1 连接工具类驱动加载、URL 参数与连接池的取舍跑通项目的突破口一定是 JDBC 连接。课设阶段不建议追求连接池DriverManager.getConnection()足够直接也方便口头讲原理。一个标准的工具类会把驱动加载放在静态代码块里保证类首次加载时驱动只注册一次public class DbUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/warehouse_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }注意驱动类名有两个版本。MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 用com.mysql.cj.jdbc.Driver。如果libs里放的是 8.0 系列的 jar写老驱动名会直接报ClassNotFoundException。URL 里的useSSLfalse是解决 SSL 连接告警或握手报错的常用手段serverTimezoneAsia/Shanghai解决的是 MySQL 8 当时区为空时抛异常的问题。这两串参数看起来是玄学实则是课设跑通前最容易卡人的地方。连接池在这个项目里属于“可讲但不必做”。如果答辩被问到了可以回答DriverManager每次创建新连接成本高生产环境会使用连接池但这里为了展示 JDBC 原生流程故意没引入额外依赖。这样答反而比硬吹 HikariCP 更稳。3.2 列表查询ResultSet 到表格模型的数据管道仓库管理系统的核心界面是表格。无论是货品列表还是库存明细Java Swing 的JTable配合DefaultTableModel是课设源码里最常见的做法。查询逻辑一般长这样public ListGoods listGoods(String keyword) { ListGoods list new ArrayList(); String sql SELECT id, goods_name, spec, unit, price FROM t_goods WHERE goods_name LIKE ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Goods g new Goods(); g.setId(rs.getInt(id)); g.setGoodsName(rs.getString(goods_name)); g.setSpec(rs.getString(spec)); g.setUnit(rs.getString(unit)); g.setPrice(rs.getBigDecimal(price)); list.add(g); } } } catch (SQLException e) { e.printStackTrace(); } return list; }逻辑上要注意三件事第一用PreparedStatement而不是Statement既避免拼接 SQL 时的注入风险也方便传参第二LIKE查询时通配符要放在参数里写在 SQL 字符串外更清晰第三把查询到的数据转成实体Goods避免在 UI 层直接操作ResultSet否则连接一关数据就没了。setBigDecimal对应数据库里的DECIMAL不会丢精度。填充JTable时惯用写法是遍历集合并把每行转成Object[]塞进DefaultTableModel。源码里大概率能在ui包下找到类似initTable()的方法。这里有一个看不见但实际存在的陷阱SELECT *会把所有字段都查出来连id也显示在界面上。处理方式是查完数据后手动构造String[] columns表头和数据一一对应不去动数据库表的列顺序。3.3 写操作入库单与库存数量的事务边界课设里容易被忽视的往往是事务。单纯执行一条INSERT没问题可仓库入库本质上牵涉两张表往入库单里插记录同时更新库存数量。两个操作中间任何一步失败都会留下“单据有记录但库存没变”的脏数据。正确的写法是把两个操作放进同一个事务Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); // 1. 写入库单 String insertSql INSERT INTO t_store_record (type, goods_id, quantity, create_time, operator) VALUES (1, ?, ?, NOW(), ?); PreparedStatement ps1 conn.prepareStatement(insertSql); ps1.setInt(1, goodsId); ps1.setBigDecimal(2, quantity); ps1.setString(3, operator); ps1.executeUpdate(); // 2. 库存表数量增加 String updateSql UPDATE t_stock SET quantity quantity ? WHERE goods_id ? AND warehouse_id ?; PreparedStatement ps2 conn.prepareStatement(updateSql); ps2.setBigDecimal(1, quantity); ps2.setInt(2, goodsId); ps2.setInt(3, warehouseId); ps2.executeUpdate(); conn.commit(); } catch (SQLException e) { if (conn ! null) { conn.rollback(); } e.printStackTrace(); }这里的手法是先关掉自动提交然后在同一个连接里连续执行两条写 SQL最终统一commit。任何一条抛异常rollback()会把已执行的落库操作全部撤回保证数据库状态回到动作之前。注意事务边界不是“方法里出现多个 SQL 就算事务”而是所有 SQL 必须共享同一个Connection。很多人上课没留意这点自己在两个工具方法里各自拿连接写了两条 SQL 照样出现数据不一致根因就在这里。4. 入库、出库与库存流水把业务流程做成闭环4.1 入库流程批次、有效期、价格三个字段怎么配合仓库系统的入库不能只理解为“库存数量加一”。实际场景里一批货进来会带有批次号、有效期、进价这些属性。批次号是唯一的同一种货不同批次进价可能不同有效期直接影响先进先出的出库策略。所以入库动作在代码层面要考虑五件事货品是否存在、仓库是否允许收该货品、批次是否重复、有效期格式是否正确、本次数量是否为正数。写代码时可以用先查后插的方式先按goods_id和warehouse_id查库存表如果批次相同就执行的数量累加如果批次不同则新插一行。这里用INSERT ... ON DUPLICATE KEY UPDATE quantity quantity VALUES(quantity)可以把两步合成一句但这个语法不少课设源码里根本没用过。给答辩准备的解释是靠唯一索引去重避免先查再插的间隙窗。如果面试官追问底层就答ON DUPLICATE KEY UPDATE在Innodb下对同一条记录会先执行索引查找判重是原子的。4.2 出库策略批次优先级与扣减顺序出库最容易做错的点是凭空扣一个总数。实际上库存表按batch_no拆了行出库时必须确定先扣哪一批。行业内普遍遵循“先进先出”即批次到期日越早越先出有些系统也支持按剩余保质期短、先出。在 Java 代码里通常是查库存表按expire_date ASC排序然后逐行扣减直到满足出库数量为止。建议给这份课设补一个出库算法普通写法是从数据库查出该商品的所有库存批次列表按到期日排序再用循环依次扣减。实现时要注意循环判断库存是否充足剩余需求量remain初始等于出库数量每一批的扣减量是min(stock.quantity, remain)扣完remain减去实际扣减量当remain小于等于零时跳出。这个算法不难但是写到纸上能加分不少因为它把“库存充足但批次不够扣”这种边界也覆盖了。4.3 盘点与报表为什么流水表不能删流水表是这份课设里最容易被当成“多余”的表。从 UI 上看它不过是一份带类型的进出记录可它承担了两个重量级职责一是审计能追溯每一笔单据是谁在什么时间操作的二是财务对账基础月度盘点时库存账面数与流水累加数互相验证。因此我在看这类课设时第一条检查就是入库和出库是否都往t_store_record写记录。如果只改了库存数量、没写流水直接判不合格。流水表设计上通常会带type字段1 是入库2 是出库3 是盘盈盘亏调整。盘盈亏在课设里是亮点功能用独立的调整类型区分手工改动而不直接覆盖库存数。这个设计思想本质是“任何对库存的变更都必须有据可查”。把这些话说给答辩老师听比单纯演示“我点了按钮数据变了”要扎实得多。面向对象编程里常说单一职责落实到表设计就是数据类型靠主表过程记录靠流水表各回各家。5. 避坑指南导入数据库到跑通界面的五个高频翻车点5.1 控制台报ClassNotFoundException: com.mysql.jdbc.Driver现象运行主类IDEA 控制台直接抛出java.lang.ClassNotFoundException指向驱动类。原因libs目录下的驱动 jar 没有正确添加到项目的 Module 依赖中或者 jar 包本身是 MySQL 8 的驱动但代码里写的是老驱动名com.mysql.jdbc.Driver。解决先点菜单 File → Project Structure → Libraries把libs目录作为依赖导入。再把代码里的驱动类名改成和 jar 版本一致的名称。MySQL 8 对应com.mysql.cj.jdbc.DriverMySQL 5 对应com.mysql.jdbc.Driver。如果实在不确定 jar 版本解压 jar 看META-INF/services/java.sql.Driver文件里的类名最准。5.2 本地 MySQL 装好了却连不上Communications link failure现象JDBC 报Communications link failure但 MySQL 服务在系统里已经启动。原因MySQL 默认端口 3306 被占用或者服务实际没有启动也可能是远程连接权限只开了localhost而你用了127.0.0.1。解决先在命令行执行mysql -u root -p能连说明服务正常。再检查 IDE 里DbUtil的 URL 的端口是否和本地 MySQL 一致。查端口用netstat -ano | findstr 3306Windows或lsof -i:3306macOS。如果本机装了多个 MySQL 实例SQLyog 或 Navicat 里能连不代表 Java 连的就是同一个。5.3 中文数据全是问号连接串少了一个参数现象页面加载后中文列全部显示为???英文和数字正常。原因数据库字符集是utf8mb4但连接串没带characterEncodingJDBC 用了默认字符集去读导致中文字节解析失败。解决在 URL 里加characterEncodingutf8这个是驱动层的编码声名顺序别放在最后面最稳妥的写法是把它放在数据库名后面紧跟?。另外导入 SQL 文件时命令行工具要用source方式导入确保脚本本身的编码格式是 UTF-8用记事本另存时不要选 ANSI。5.4 MySQL 8 的密码插件Public Key Retrieval is not allowed现象连接 MySQL 8 时抛Public Key Retrieval is not allowed有的环境还会伴随caching_sha2_password cannot be loaded。原因MySQL 8 默认认证插件是caching_sha2_password较旧的驱动版本不支持这个插件或者 JDBC 连接时没有允许客户端获取服务端公钥。解决连接串里加allowPublicKeyRetrievaltrue并且把useSSLfalse一起带上。也可以在 MySQL 里把该用户的认证方式改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;然后刷新权限。两种方式按项目环境取舍如果只是本机课设改驱动参数最省事。5.5 库存出现负数查出来是 0提交后却是 -60现象库存剩余 40出库 50 也能成功库存表变成 -10 甚至更多没有任何提示。原因查询库存后在 Java 代码里做减法然后 UPDATE 回数据库。两个用户同时操作或代码逻辑没有先校验库存充足就会出现负数。更隐蔽的是UPDATE t_stock SET quantity quantity - ?执行后没有检查受影响行数即使影响行数为 0 也照常提交单据。解决在 UPDATE 语句里加库存要求UPDATE t_stock SET quantity quantity - ? WHERE goods_id ? AND warehouse_id ? AND quantity ?。当实际库存不足时受影响行数为 0代码据此抛库存不足异常有机结合地拦截负数。我给这类仓库课设补漏时第一反应就是把所有库存扣减 SQL 都加上这个quantity ?条件比任何前端弹窗都可靠。6. 用最小闭环验证项目从登录到入库再出库的一套自检流程项目跑通后我建议你先别急着改功能照着下面这条链路完整走一遍。不走完你可能永远不知道这个 base 项目的真实性边界。第一步导入数据库。打开 Navicat 或 MySQL Workbench新建查询执行source D:/path/Warehouse management.sql。确认库里出现表结构和初始数据。检查t_user里是否有管理员账号通常初始密码是admin或123456源码里可能写死去dao包翻。第二步IDEA 启动主类。如果主类不好找在src里搜索带main方法的类这类项目一般叫MainFrame或LoginFrame。先跑起来再说报错按上一章的方法排。第三步用管理员账号登录。登录成功后有主界面菜单一般包含基础数据、入库管理、出库管理、库存查询、流水查询。有缺口的模块先记下来这些缺口往往是答辩时老师最喜欢问“如果让你扩展你会加什么”的落点。第四步新增一个货品手动输入编码、名称、规格、单位、参考价。再对这个货品做一次入库操作填仓库、批次、数量。入库后立刻查库存和流水数量要等于入库量流水应有一条入库记录。第五步做一次出库。出库量设为当前库存的一半出库后库存表数量减半流水多一条出库记录。如果库存不足确认系统是否弹提示、是否阻止提交。第六步验证盘点。如果项目有盘点功能做一次盘点调整某个货品的库存到真实盘数核对流水里是否产生了调整记录。没有盘点功能也没关系这条链路已经覆盖了主要数据流。走完这套流程你的状态就从“拿别人代码试试”变成了“亲手验证过的使用者”。给这套课设做扩展时再有底气去改 SQL 和业务方法。前几年我拿到一份连接串写错时区的项目当时没做验证就改着玩最后把库存表翻得对不上账只能重新导入 SQL。从那以后我每次动手改代码前都强制走一遍上面的最小闭环验证把数据库备份和清数据放在第一位数据库比代码更怕乱搞。希望你第一次跑这个仓库管理系统时别赶在答辩前夜留一天给自己踩坑、填坑后面就顺了希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →