Java课设实验室设备管理系统:从数据库设计到Swing/JDBC全流程解析
发布时间:2026/9/28 1:07:27来源:尧图网络
简介实验室设备管理系统是一份用于Java课程设计的完整项目包基于GUI/Swing与MySQL实现面向需要完成设备管理类课设的在校学生。资源围绕设备台账管理设计涵盖登录注册、管理员与普通用户双角色主界面、设备信息新增修改删除、列表浏览以及按名称、设备状态、报废状态等条件查询并配有JDBC数据库连接工具类可直接作为课设代码框架或功能参考。压缩包共收录72个文件包含13个Java源文件、36个class编译文件、数据库SQL脚本、界面截图与工程配置文件等整体大小仅350KB结构紧凑便于本地导入运行与二次修改。已有159人学习下载。资源内附完整数据库脚本和可直接运行的源码文件覆盖从建表到业务操作的关键环节界面呈现原始风格未做过度美化更贴近真实课设验收水平。对于需要快速上手、完成Java课设的读者具有明确的参考与复用价值。1. 实验室设备管理系统为什么课设清单里总有它的位置期末季的 Java 课设题目里实验室设备管理系统几乎年年出现。它不是新东西一台 PC 装 MySQL另一侧用 Java Swing 画窗口把设备登记、借出、归还、维修、统计做成桌面程序。技术栈老但恰好覆盖了 Java 课设的考点面向对象、JDBC、GUI 事件、SQL 增删改查、事务。对初学者来说难度中等对要交作业的学生来说答辩也好展开。这篇按课设包最常见的形态来讲从表设计到 Swing 界面再到借还业务和打包最后给出答辩能用的完整思路。2. 先设计数据库设备台账、借还流水和状态机2.1 设备管理系统的核心数据流所有 GUI 功能最后都落到 SQL 上。先把业务画成一条链路管理员录入设备 → 设备在库 → 被借出 → 归还 → 出现故障进维修 → 维修完成回到在库 → 报废下架。接口上常见的功能点设备增删改查、借出、归还、逾期提醒、按分类/状态统计、操作员登录。这条链路里最关键的是区分“设备当前状态”和“设备历史流水”。很多课设只做一张 equipment 表把借用信息直接塞进设备表做法是加一个 borrow_user、borrow_time 字段。这样短期能运行但一旦要查“这台设备上个月借给谁了”数据就被覆盖了。所以正规做法是设备表只保存当前状态借出归还的每次行为都落到独立的借用记录表。后面写统计报表时你会感谢这张流水表。2.2 五张核心表建表 SQL 与字段解释常见课设包里的数据库脚本一般包含这几张表。命名上避开 MySQL 的保留字user 在 MySQL 里是系统库表名自定义业务用户表建议用 operator。-- 设备表保存当前状态不追历史流水 CREATE TABLE equipment ( id INT AUTO_INCREMENT PRIMARY KEY, eq_no VARCHAR(32) NOT NULL COMMENT 设备编号, name VARCHAR(100) NOT NULL COMMENT 设备名称, category VARCHAR(32) DEFAULT 其他 COMMENT 分类, location VARCHAR(64) DEFAULT COMMENT 存放位置, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在库 1借出 2维修 3报废, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 资产价格, buy_date DATE DEFAULT NULL COMMENT 购置日期, remark VARCHAR(255) DEFAULT COMMENT 备注, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT设备台账; -- 操作员表课设不需要复杂权限一个角色字段足够 CREATE TABLE operator ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 建议存MD5或加盐后的值, real_name VARCHAR(32) DEFAULT , role VARCHAR(16) DEFAULT admin COMMENT admin/operator ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统操作员; -- 借用记录表每次借出/归还都写一条 CREATE TABLE borrow_record ( id INT AUTO_INCREMENT PRIMARY KEY, equipment_id INT NOT NULL, operator_id INT NOT NULL COMMENT 经办人, borrower_name VARCHAR(32) NOT NULL COMMENT 借用人, borrower_dept VARCHAR(64) DEFAULT COMMENT 借用部门, borrow_time DATETIME NOT NULL, due_time DATETIME DEFAULT NULL COMMENT 应还时间, return_time DATETIME DEFAULT NULL COMMENT 实际归还时间, status TINYINT NOT NULL DEFAULT 0 COMMENT 0借出中 1已归还 2逾期未还, remark VARCHAR(255) DEFAULT , KEY idx_equipment (equipment_id), KEY idx_status_time (status, due_time), CONSTRAINT fk_borrow_equip FOREIGN KEY (equipment_id) REFERENCES equipment(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借用流水; -- 维修记录表 CREATE TABLE repair_record ( id INT AUTO_INCREMENT PRIMARY KEY, equipment_id INT NOT NULL, operator_id INT DEFAULT NULL COMMENT 报修经办人, cost DECIMAL(10,2) DEFAULT 0.00, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, description VARCHAR(255) DEFAULT , KEY idx_equipment (equipment_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT维修记录; -- 分类字典表让“新增分类”不需要改代码 CREATE TABLE category_dict ( id INT AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(32) NOT NULL UNIQUE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT设备分类字典;建表时有几个细节别偷懒。状态字段用 TINYINT 加注释不要用字符串“在库/借出”否则排序和统计都难受。价格用 DECIMAL(10,2)float 在合计时会出现 0.10.2 不等于 0.3 的尴尬答辩时被问到会很狼狈。所有表都用 utf8mb4不要用 utf8否则生僻字和 emoji 注释会报错。2.3 外键、索引和逻辑删除的取舍借用记录的 equipment_id 加了外键约束作用是保证流水不会指向不存在的设备。但外键是一把双刃剑删除设备时如果有历史借用数据库会直接拒绝。课设里比较稳的做法是“逻辑删除”而不是物理删除给 equipment 表加 is_deleted TINYINT DEFAULT 0删设备时 UPDATE 成 1。好处是历史流水永远查得到坏处是所有“查设备列表”的地方都要多带一个 AND is_deleted0。我见过不少课设忘了这个条件导致删除后设备还在列表里“灵异复活”所以加字段时顺手把它写进 DAO。索引方面借出列表最常见的查询是“按状态筛设备”和“按逾期时间筛借用记录”。idx_status 和 idx_status_time 已经覆盖这两个高频查询。如果设备编号经常被当作查询条件可以把 eq_no 也建成唯一索引因为设备编号在真实实验室里是一物一码的重复录入应该直接提醒而不是硬插进去。初始化数据里记得塞一个管理员账号密码不要用明文用 MD5 或 SHA-256 存。登录校验时把用户输入同样做哈希后再比较。这样答辩时可以理直气壮地说“我没有明文存密码”。3. 用 Swing 搭界面登录、主窗体与 JTable 联动3.1 包结构UI、Service、DAO 三层不要混写课设源码最容易出现的问题是一个类里既写窗口又写 SQL登录按钮事件里直接 Connection.createStatement()。能跑但改需求时会原地爆炸。推荐按三层分包com.example.lab ├── entity/ Equipment.java, BorrowRecord.java, Operator.java ├── dao/ EquipmentDao.java, BorrowDao.java, OperatorDao.java ├── service/ EquipmentService.java, BorrowService.java ├── ui/ LoginFrame.java, MainFrame.java, EquipmentPanel.java ├── util/ DbUtil.java, SessionUtil.java └── Main.java 程序入口entity 是纯字段的 POJO对应数据库表DAO 只做增删改查返回实体或 ListService 处理业务判断和事务UI 只调 Service。这样分工后每个类的代码量都控制在 200 行上下答辩讲起来也清晰。很多老师会问“为什么这么分层”答案就一句话换数据库驱动或者换界面框架时不需要动业务代码。3.2 JDBC 连接一个 DbUtil 搞定连接参数连接参数是课设里翻车率最高的地方。MySQL 8 的驱动对时区、SSL、公钥获取都敏感URL 里漏一个参数就报错。下面是经过多次踩坑后稳定的写法public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/lab_manager ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8 allowPublicKeyRetrievaltrue rewriteBatchedStatementstrue; 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); } }参数说明useSSLfalse 避免本地连接时做证书交换serverTimezoneAsia/Shanghai 解决 MySQL 8 默认时区读取失败allowPublicKeyRetrievaltrue 是 caching_sha2_password 认证插件下首次连接必须的不加会报 Public Key Retrieval is not allowedcharacterEncodingutf8 保证中文在 JDBC 层不变成问号。连接驱动类名要注意MySQL 5.x 是 com.mysql.jdbc.Driver8.x 是 com.mysql.cj.jdbc.Driver把旧驱动类名贴到 MySQL 8 的工程会直接 ClassNotFoundException。3.3 登录窗口校验与跳转登录窗口的逻辑很固定用户输入用户名密码Service 校验通过后保存当前登录人打开主窗口关闭登录窗口。代码里要注意一点不要在事件线程里用 while 阻塞等待用按钮事件驱动就好。public class LoginFrame extends JFrame { private JTextField usernameField; private JPasswordField passwordField; public LoginFrame() { setTitle(实验室设备管理系统 - 登录); setSize(360, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 省略布局代码按钮监听如下 loginBtn.addActionListener(e - { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); Operator loginUser new OperatorService().login(username, password); if (loginUser null) { JOptionPane.showMessageDialog(this, 用户名或密码错误); } else { SessionUtil.setCurrentUser(loginUser); new MainFrame().setVisible(true); dispose(); } }); } }这里把当前登录用户放进 SessionUtil 的静态变量里后续借出、维修操作都要记录“经办人”这就是 SessionUtil 存在的意义。有人会用 Swing 的 Properties 传参也行但静态会话类在课设里最直观。JPasswordField.getPassword() 返回 char[]不要用 getText()这是 Java 官方建议答辩可能被问到。3.4 JTable 刷新别 new 一整个表格要更新 TableModel设备列表是主界面的核心。最容易翻车的是每次查询都重新 new JTable然后 add 到面板界面会出现残影或旧数据不消失。正确做法是持有同一个 JTable查询后替换它的 TableModel。public class EquipmentPanel extends JPanel { private JTable table; private DefaultTableModel model; public void initTable() { // 表头固定数据行先留空 model new DefaultTableModel( new String[]{ID, 编号, 名称, 分类, 位置, 状态, 价格}, 0); table new JTable(model); add(new JScrollPane(table)); } public void refreshData() { ListEquipment list new EquipmentService().listAll(); model.setRowCount(0); // 先清空旧行 for (Equipment eq : list) { model.addRow(new Object[]{ eq.getId(), eq.getEqNo(), eq.getName(), eq.getCategory(), eq.getLocation(), statusText(eq.getStatus()), eq.getPrice() }); } // 默认表格容易被误触编辑直接关掉整表编辑 table.setEnabled(false); } }关键点是 model.setRowCount(0) 加 model.addRow 的组合。如果不重设 model 也不清空每次刷新都会在底部追加旧数据。表格列名建议和数据库字段名错开界面显示“状态”值映射成“在库/借出/维修/报废”不让用户看到 0/1/2。数据量超过几百条时把 DAO 查询放进 SwingWorker 里做避免界面卡在“刷新中”。4. 把业务串起来借出、归还和统计报表4.1 借出是一次跨表操作设备状态和流水必须同时变借出按钮背后的业务是“插入一条借出流水 把设备状态改为 1”。这两件事要么都成功要么都失败。如果只插入流水、设备状态没改设备会被重复借出只改状态、没写流水归还时没有记录可对账。这就是事务边界。对应到代码上绝不能写成两个 DAO 方法分开调用。正确方式是 Service 层拿同一个 Connection先 setAutoCommit(false)两条 SQL 执行完再 commit。DAO 层的方法要支持传入 Connection也就是按“手动事务”写法设计而不是 DAO 内部自己开连接。public boolean borrow(int equipmentId, String borrower, String dept, LocalDateTime dueTime) { Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); EquipmentDao eqDao new EquipmentDao(); Equipment eq eqDao.findById(conn, equipmentId); if (eq null || eq.getStatus() ! 0) { // 状态不是“在库”直接回滚 conn.rollback(); return false; } eqDao.updateStatus(conn, equipmentId, 1); new BorrowDao().insert(conn, equipmentId, borrower, dept, LocalDateTime.now(), dueTime, 0); 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) { } } } }注意两个细节。第一查设备状态的操作也要在这个事务连接里做否则并发场景下两台电脑同时借同一台设备两边读到的都是“在库”都能插入流水设备就超卖了。第二DAO 方法签名要传 conn例如 findById(Connection, int)这样才和 Service 层共用连接。有些课设里 DAO 每个方法自己调 DbUtil.getConnection()这种写法在普通查询没问题但放进事务里就会变成两条独立连接回滚完全失效。4.2 归还流程先还设备再关流水顺序有讲究归还设备有两个动作更新设备状态回 0、更新流水表的 return_time 和 status 为 1。推荐先更新流水表再更新设备表。原因在于逾期判断如果有“逾期未还”的定时任务在跑它只认流水表 status2。先把设备状态改回 0但流水还没关这时候有人来借这台设备逻辑上会判定“在库可借”但旧流水仍显示未归还账目就乱了。反过来先关流水设备短暂显示“借出中”最多是界面刷新慢半拍不会造成错借。事务包裹下两者最终一致但顺序体现了对业务状态的思考答辩时讲出来是加分项。逾期处理的常见做法不是定时任务而是在查询“在借列表”时实时计算SELECT * FROM borrow_record WHERE status 0 AND due_time IS NOT NULL AND due_time NOW();这条 SQL 每次都把过期未还的流水找出来界面上红色标红提醒免去了课设里写 Timer 的复杂度。4.3 统计报表聚合留在 SQL 里别在 Java 里循环设备管理的课设通常要一个统计页常见统计项是“按分类统计设备数量”“按月份统计借出次数”。新手容易把数据全查出来再在 Java 里 for 循环数数数据一多就慢代码也丑。SQL 聚合一行就出来了-- 分类设备数量 SELECT category, COUNT(*) AS cnt FROM equipment WHERE is_deleted 0 GROUP BY category; -- 每月借出次数 SELECT DATE_FORMAT(borrow_time, %Y-%m) AS ym, COUNT(*) AS cnt FROM borrow_record GROUP BY DATE_FORMAT(borrow_time, %Y-%m) ORDER BY ym DESC; -- 设备利用率已归还记录的平均占用天数 SELECT AVG(TIMESTAMPDIFF(DAY, borrow_time, return_time)) AS avg_days FROM borrow_record WHERE status 1 AND return_time IS NOT NULL;JTable 只需要把这些结果集的行列填进去。DATE_FORMAT 的第一个参数是时间列第二个是格式串 %Y-%m注意大小写%y 是两位年%Y 才是四位年写反了报表会很难看。TIMESTAMPDIFF 的单位可以是 DAY、HOUR、MINUTE统计设备占用天数用 DAY 就够。4.4 连接池要不要上课设的度在哪里课设里用 DriverManager 每次 getConnection 完全够用量级就一个班几十个人点几下。但如果有精力把 DbUtil 换成 HikariCP 会让项目质量上一个档次。HikariCP 的配置很省HikariConfig config new HikariConfig(); config.setJdbcUrl(URL); config.setUsername(root); config.setPassword(123456); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(3000); HikariDataSource dataSource new HikariDataSource(config); // 之后 dataSource.getConnection() 替代 DriverManager好处是连接复用频繁开关窗口不会不断创建物理连接。代价是 jar 包多一个 slf4j 依赖。如果答辩被问“为什么不用连接池”老实说“课设规模用不上”反而是好答案如果项目写“支持高并发”那一定被追问连接池到时没有就圆不回来。我的建议是需求里没写高并发就保持简单。5. 课设避坑清单环境、编码与 GUI 刷新翻车现场5.1 MySQL 8 连接失败时区与公钥获取现象程序启动后查数据报 Communications link failure或者 Public Key Retrieval is not allowed还有的报 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因MySQL 8 默认认证插件是 caching_sha2_password客户端第一次连接需要服务端下发公钥本地 MySQL 时区如果没配置驱动拿不到合法时区 ID。控制台里看到的乱码说明 JDBC URL 没指定 serverTimezone驱动把系统默认时区 Asia/Shanghai 按平台默认编码读出来就成了乱码。解决JDBC URL 里固定带上 serverTimezoneAsia/Shanghai 和 allowPublicKeyRetrievaltrue 和 useSSLfalse三件套缺一不可。另一个方案是把 MySQL 配置文件里的 default-time-zone 写成 08:00但课设机房的 MySQL 不一定让改配置文件用 URL 参数最可控。5.2 中文乱码三层都要统一编码现象登录界面上显示“张三”点查询后 JTable 里变成“寮犱笁”或者问号数据库里看到的数据是乱码。原因三个位置不一致Java 源码文件编译编码、JDBC URL 的 characterEncoding、MySQL 表字符集。机房电脑可能是 GBK 编码IDE 默认又是 UTF-8只要有一层没对上就乱。解决统一套路是 IDE 里把项目编码、源码文件编码都设成 UTF-8JDBC URL 加 characterEncodingutf8建表时写 DEFAULT CHARSETutf8mb4。如果数据库已经是乱码不要试图在代码里转码把表 DROP 后按上面的方式重建初始化脚本重新导入。转码函数 CONVERT 在乱码数据上经常越转越乱属于后悔药都救不回来的情况只能清掉重来。5.3 JTable 刷新后界面没变化模型没通知视图现象点“新增设备”后数据库里有数据列表界面却还是旧的重启程序才看到新记录。原因JTable 不会主动知道数据变了。如果只改了 model 里的数组而没有触发事件表格不会重绘。另一个常见原因是 model.setValueAt 或直接 addRow 后没调用 fireTableDataChanged。DefaultTableModel 的 addRow 会自带事件通知但很多人自己写了 AbstractTableModel 的子类而忘了发事件。解决简单项目直接用 DefaultTableModel不要自定义 model复杂项目在自定义 model 的 addRow/removeRow/updateRow 末尾调用 fireTableRowsInserted 或 fireTableCellUpdated。如果查询本身没问题可以临时在 refreshData 末尾加一句重绘但不要用 frame.revalidate() 凑合那是布局重算不是表格数据重绘。真正的解法是让 model 通知视图。5.4 打包成可执行 jar 后连不上数据库现象在 IDE 里点运行一切正常双击打好的 jar 包报 ClassNotFoundException: com.mysql.cj.jdbc.Driver或 NoClassDefFoundError。原因jar 包里没有把 mysql-connector 驱动打进去。IDE 运行时用的是 IDE 配置的依赖打 jar 时如果不选“包含外部依赖”驱动不会进最终包双击运行时 classpath 只有这个 jar 自己。解决用 Maven 的话在 pom.xml 里配 maven-shade-plugin 或 maven-assembly-plugin把依赖打进 fat jar。不用 Maven 的话在 IDE 的 Artifacts 配置里把驱动 jar 加进输出目录。另外数据库连接信息如果写在 .properties 文件里注意打包后路径变成 classpath用 getResourceAsStream(/jdbc.properties) 读取不要写 File 绝对路径否则换一台电脑就崩。打包后把 jar 放到没有安装 IDE 的机器上试一次这是最后一道验收很多课设就是在这步翻车的。5.5 删除设备被外键拦住Cannot delete or update a parent row现象列表里删一台设备报错 java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails。原因borrow_record 表对 equipment_id 建了外键这台设备有借出历史时数据库拒绝物理删除防止流水指向空记录。这个报错是外键保护在起作用是好的不是 bug。解决三种方案按需选。第一种界面提示“该设备存在借用记录不能删除”直接不放行最简单第二种把外键改成 ON DELETE SET NULL但 borrow_record.equipment_id 要允许 NULL统计历史流水时会多出“设备已删除”的空记录第三种设备表加 is_deleted 做逻辑删除这也是课设里最推荐的删除变成一条 UPDATE外键完全不受影响历史报表数据完整。方案定了之后设备列表查询记得统一带 is_deleted0否则列表里会出现删过的设备重新出现。6. 答辩前的最后打磨报表导出、启动脚本和验收习惯课设做到能跑只是及格答辩时能让老师点头还需要两三个亮点。第一个亮点是导出 Excel。用 Apache POI 的 XSSFWorkbook 把统计报表导出成本地 xlsx代码量不大但演示效果很好try (Workbook workbook new XSSFWorkbook()) { Sheet sheet workbook.createSheet(设备台账); Row header sheet.createRow(0); header.createCell(0).setCellValue(编号); header.createCell(1).setCellValue(名称); ListEquipment list new EquipmentService().listAll(); for (int i 0; i list.size(); i) { Row row sheet.createRow(i 1); Equipment e list.get(i); row.createCell(0).setCellValue(e.getEqNo()); row.createCell(1).setCellValue(e.getName()); } try (FileOutputStream out new FileOutputStream(equipment.xlsx)) { workbook.write(out); } }try-with-resources 保证了流关闭Workbook 的 xlsx 格式是 XSSFWorkbook老式 HSSFWorkbook 只能写 .xls 且单表最多 65536 行课设规模无所谓但答辩时说出这个区别会显得懂行。依赖需要 poi-ooxml选一个稳定版本引入即可。第二个亮点是启动脚本。写完 jar 后给 Windows 写一个 start.bat内容两行echo off 和 java -jar lab-manager.jar给 Linux/macOS 写 start.sh。机房验收时双击就能起不打命令印象分会好不少。数据库初始化脚本放在 sql/ 目录README 里写清楚导入顺序是“建库 → 建表 → 初始化数据”。我自己的习惯是每次改完代码把数据库删了重新执行一遍整个 sql 脚本确认脚本能一键重建不然答辩老师问“从零部署怎么搞”时当场紧张。最后一个习惯是验收前做三轮测试第一轮全是正常操作新增、借出、归还、统计第二轮是异常操作借出不在库的设备、删除有流水的设备、超时未还的标红第三轮是数据一致性借出完成的同时去查设备状态。三轮跑完再把工程里的连接密码改回弱口令并加注释说明避免把真实机器的账号写死在代码里交上去。这套流程走一遍课设代码本身反而是小事了。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网