简介这份资源是面向Java初学者与课程设计开发者的学校教材管理系统完整源码包基于Java语言、Swing图形界面与MySQL数据库构建可用于教育信息化场景下的教材信息、班级与教师数据管理帮助读者理解桌面应用从界面到持久层的整体实现思路。压缩包共53个文件约3.39MB包含18个java源文件与18个class编译文件、6个gif与2个jpg界面素材、2个jar依赖库、2个db数据文件以及1个sql建表脚本覆盖源码、资源、数据库与工程配置等类别。目前已有1726人学习下载。系统采用MVC架构与DAO模式分离业务逻辑和数据访问通过事件驱动编程响应用户操作并涉及权限管理、异常处理与测试调试等工程实践适合作为课程设计、毕业设计或Swing与JDBC综合练习的参考案例便于读者快速梳理项目结构并对照学习增删改查与界面交互的实现方式。1. JavaSwingMysql学校教材管理系统为什么教务老师宁愿用Excel也不碰你的系统教材科每学期最头疼的事不是订书是盘账。入库、领用、退库、报废四个动作交叉在一起一个班领走 45 本、另一个班退回来 3 本破损Excel 里三张表一对就串行。很多学校不是没做过教材管理系统而是做出来的东西教务老师用两天就丢回 Excel——界面卡、查询慢、数据对不上。JavaSwingMysql 这套组合之所以在学校教材管理系统里被反复提起恰恰因为它能在一台普通办公电脑上跑起来不依赖浏览器、不依赖服务器运维双击就能用。这篇笔记面向的是要独立交付一套能真正投入使用的教材管理系统的开发者从建库建表、Swing 界面分层到教材入库、领用登记、库存预警、报表导出把每一步的参数和坑讲清楚。适合有 Java 基础、做过课程设计但没做过完整桌面业务系统的同学也适合被派去给学校做内部工具的一线开发。2. 教材管理系统的数据模型四张核心表怎么定字段教材管理系统的复杂度不在界面在数据模型。教材、班级、领用记录、库存流水这四块如果一开始字段定错后面所有查询都要打补丁。我一般会先把业务动作拆成「谁、领了什么书、领多少、什么时候、什么状态」再反推表结构。2.1 教材表与库存表要不要合并常见做法是教材基础信息和库存数量分两张表。教材表存 ISBN、书名、作者、出版社、单价、版次这些不常变的属性库存表存教材 ID、当前库存、预警阈值、库位。合并成一张表在单机场景下也能跑但一旦要做入库流水和领用流水库存字段会被反复更新和教材属性混在一起后期加字段容易锁表。-- 教材基础信息表 CREATE TABLE tb_textbook ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE COMMENT ISBN号唯一, book_name VARCHAR(100) NOT NULL COMMENT 教材名称, author VARCHAR(50) DEFAULT COMMENT 作者, publisher VARCHAR(80) DEFAULT COMMENT 出版社, edition VARCHAR(20) DEFAULT COMMENT 版次, price DECIMAL(8,2) DEFAULT 0.00 COMMENT 单价, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 库存表与教材一对一 CREATE TABLE tb_stock ( id INT PRIMARY KEY AUTO_INCREMENT, textbook_id INT NOT NULL UNIQUE COMMENT 关联教材ID, quantity INT NOT NULL DEFAULT 0 COMMENT 当前库存, warn_line INT NOT NULL DEFAULT 10 COMMENT 预警阈值, location VARCHAR(30) DEFAULT COMMENT 库位, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_stock_book FOREIGN KEY (textbook_id) REFERENCES tb_textbook(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明tb_textbook的isbn加唯一索引防止同一本书重复录入tb_stock用textbook_id做外键并加唯一约束保证一本教材只有一条库存记录。参数上quantity用 INT 而不是无符号是因为退库和报废会产生负数调整虽然最终库存不该为负但流水表里需要记录方向。warn_line默认 10实际按班级规模调一个班 40 人预警线设 15 比较稳。2.2 领用记录表的状态字段怎么设计领用记录是教材管理系统里最容易被改烂的表。很多课程设计只存「班级、教材、数量、时间」结果退库、破损、补领全挤在一张表里查询时靠备注字段猜。我一般会加record_type和status两个字段record_type区分领用、退库、报废、入库调整status标记正常、已撤销、待审核。CREATE TABLE tb_record ( id INT PRIMARY KEY AUTO_INCREMENT, textbook_id INT NOT NULL, class_name VARCHAR(50) NOT NULL COMMENT 班级名称, quantity INT NOT NULL COMMENT 数量退库为负, record_type TINYINT NOT NULL DEFAULT 1 COMMENT 1领用 2退库 3报废 4调整, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0已撤销, operator VARCHAR(30) DEFAULT COMMENT 经办人, record_time DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(200) DEFAULT , INDEX idx_book (textbook_id), INDEX idx_class (class_name), INDEX idx_time (record_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明quantity在退库时存负数这样统计某本教材净领用量时直接SUM(quantity)即可不用按类型分支。record_type用 TINYINT 而不是 ENUM是为了后续加类型时不用改表结构。三个索引分别服务按教材查、按班级查、按时间范围查教材管理系统的报表基本围绕这三个维度。提示班级名称不要用外键关联班级表很多学校班级是每学期临时导入的用字符串存反而更灵活代价是录入时要加下拉框约束。3. Swing 界面分层从登录到主窗口的最小可运行骨架Swing 写业务系统最大的问题是代码全堆在一个 JFrame 里几千行之后没法维护。教材管理系统的界面不算多但登录、主框架、教材管理、领用登记、报表至少五个面板不分层后面改一个按钮都要翻半天。3.1 用 CardLayout 做主面板切换常见做法是主窗口用CardLayout左侧或顶部放导航按钮点击切换右侧面板。每个业务面板单独一个类继承JPanel只负责界面和事件转发数据库操作交给 DAO。public class MainFrame extends JFrame { private final CardLayout cardLayout new CardLayout(); private final JPanel contentPanel new JPanel(cardLayout); public MainFrame() { setTitle(学校教材管理系统); setSize(1000, 680); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); // 注册各业务面板key 用于切换 contentPanel.add(new BookPanel(), book); contentPanel.add(new RecordPanel(), record); contentPanel.add(new StockPanel(), stock); contentPanel.add(new ReportPanel(), report); JPanel nav new JPanel(new GridLayout(4, 1, 0, 8)); nav.add(createNavBtn(教材管理, book)); nav.add(createNavBtn(领用登记, record)); nav.add(createNavBtn(库存查询, stock)); nav.add(createNavBtn(报表统计, report)); add(nav, BorderLayout.WEST); add(contentPanel, BorderLayout.CENTER); } private JButton createNavBtn(String text, String card) { JButton btn new JButton(text); btn.addActionListener(e - cardLayout.show(contentPanel, card)); return btn; } }逻辑说明CardLayout的show方法按添加时的 key 切换面板对象只创建一次切换时不会重建状态得以保留。参数上GridLayout(4,1,0,8)表示 4 行 1 列、垂直间距 8 像素导航按钮数量变化时改第一个参数即可。setLocationRelativeTo(null)让窗口居中办公电脑分辨率不一时体验更稳。3.2 表格数据加载与分页的取舍教材数量上千条时JTable一次性加载会卡。我一般用TableModel只加载当前页配合上一页、下一页按钮。分页查询在 MySQL 里用LIMIT offset, sizeoffset 大时性能下降但学校教材量级通常几千条够用。public ListTextbook queryPage(int page, int size, String keyword) { String sql SELECT * FROM tb_textbook WHERE book_name LIKE ? OR isbn LIKE ? ORDER BY id DESC LIMIT ?, ?; ListTextbook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ps.setInt(3, (page - 1) * size); ps.setInt(4, size); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } catch (SQLException e) { e.printStackTrace(); } return list; }逻辑说明LIKE两侧加%支持模糊搜索但注意这会让索引失效教材量超过几万条时要改成前缀匹配或全文索引。page从 1 开始offset计算为(page-1)*size。size建议 20 到 50太大表格滚动体验差太小翻页频繁。DBUtil.getConnection()用连接池或单例连接Swing 是单线程事件模型但数据库操作要放到SwingWorker里否则查询时界面假死。注意Swing 的所有界面更新必须在事件分发线程EDT里做SwingWorker的done()方法已经回到 EDT可以直接更新表格模型。4. 教材入库、领用与库存预警的完整链路数据模型和界面骨架搭好后核心业务就是入库加库存、领用减库存、退库加库存、报废减库存以及库存低于阈值时提醒。这条链路最容易出的问题是库存和流水不一致所以每次变动都要在同一个事务里完成。4.1 入库与领用的事务写法以领用为例先插入领用记录再更新库存两步必须同事务。库存不足时直接抛异常回滚不能先插记录再判断。public boolean lendBook(int textbookId, String className, int qty, String operator) { String insertSql INSERT INTO tb_record(textbook_id, class_name, quantity, record_type, operator) VALUES (?, ?, ?, 1, ?); String updateSql UPDATE tb_stock SET quantity quantity - ? WHERE textbook_id ? AND quantity ?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, qty); ps.setInt(2, textbookId); ps.setInt(3, qty); if (ps.executeUpdate() 0) { conn.rollback(); return false; // 库存不足 } } try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, textbookId); ps.setString(2, className); ps.setInt(3, qty); ps.setString(4, operator); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { if (conn ! null) try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } }逻辑说明UPDATE语句里带quantity ?条件利用数据库行锁保证并发下不会超领executeUpdate()返回 0 说明库存不够直接回滚。先更新库存再插记录是因为更新失败时不需要清理已插入的记录。参数上qty必须为正整数界面层要校验operator存经办人姓名方便追溯。事务提交后恢复autoCommit再关闭连接避免连接池回收时状态异常。4.2 库存预警的触发时机预警不需要定时任务在每次库存变动后查一次当前库存和阈值即可。低于阈值时在主界面状态栏或弹窗提示不要每次领用都弹容易烦。public ListString checkWarn() { String sql SELECT t.book_name, s.quantity, s.warn_line FROM tb_stock s JOIN tb_textbook t ON s.textbook_id t.id WHERE s.quantity s.warn_line; ListString warns new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { warns.add(rs.getString(book_name) 库存 rs.getInt(quantity) 低于预警线 rs.getInt(warn_line)); } } catch (SQLException e) { e.printStackTrace(); } return warns; }逻辑说明用JOIN一次查出教材名和库存避免在 Java 里循环查教材表。quantity warn_line用严格小于等于阈值时不报警符合「低于预警线」的语义。返回字符串列表界面层可以拼成一行显示在状态栏也可以弹窗。参数上warn_line每本教材可单独设置新书入库时默认 10热门教材可以调高。5. 避坑与排查教材管理系统上线后最常翻车的五件事5.1 中文乱码数据库、连接、界面三处都要对现象教材名称在数据库里正常Swing 表格里显示问号或方块。原因MySQL 连接串没指定字符集或者建表时用了 latin1。解决连接串加useUnicodetruecharacterEncodingutf8建表和数据库都用utf8mb4Swing 字体用支持中文的「微软雅黑」或「宋体」。5.2 库存对不上流水和库存没在同一事务现象领用记录有但库存没减或者库存减了记录没插。原因两步操作各自提交中间抛异常。解决按 4.1 的写法setAutoCommit(false)更新库存和插记录同事务失败一起回滚。排查时用SELECT SUM(quantity) FROM tb_record WHERE textbook_id? AND status1和库存表对比不一致就说明有历史脏数据。5.3 Swing 界面假死数据库操作放在了 EDT现象点击查询后窗口卡住拖动无响应。原因actionPerformed里直接执行了耗时 SQL。解决用SwingWorker把查询放doInBackground结果在done里更新表格。参数上SwingWorker的泛型第一个是最终结果类型第二个是中间结果类型不需要中间结果时用Void。5.4 分页查询越翻越慢offset 过大现象前几页很快翻到几百页后明显变慢。原因LIMIT offset, size会扫描 offset 之前的行。解决教材量不大时忽略量超过几万条时改用「上一页最大 ID」做游标WHERE id lastId ORDER BY id DESC LIMIT size。学校场景一般用不上但要知道边界。5.5 外键约束导致删不掉教材现象删除教材时报外键约束错误。原因tb_stock和tb_record引用了tb_textbook。解决不要物理删除教材加is_deleted字段做逻辑删除查询时过滤。如果一定要删先删流水和库存但流水是审计数据不建议删。6. 报表导出与批量导入两个让系统真正被用起来的技巧教材管理系统能不能被教务老师接受往往取决于两个附加功能能不能把领用汇总导出成 Excel能不能批量导入教材。这两块做不好老师还是要手工抄一遍系统就白做了。导出用 Apache POI 最稳XSSFWorkbook写 xlsx。下面是一个按班级汇总领用量的导出片段public void exportClassSummary(String className, File file) throws IOException { String sql SELECT t.book_name, t.isbn, SUM(r.quantity) AS total FROM tb_record r JOIN tb_textbook t ON r.textbook_id t.id WHERE r.class_name ? AND r.status 1 AND r.record_type 1 GROUP BY t.id, t.book_name, t.isbn; try (Workbook wb new XSSFWorkbook()) { Sheet sheet wb.createSheet(领用汇总); Row header sheet.createRow(0); header.createCell(0).setCellValue(教材名称); header.createCell(1).setCellValue(ISBN); header.createCell(2).setCellValue(领用数量); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, className); try (ResultSet rs ps.executeQuery()) { int rowIdx 1; while (rs.next()) { Row row sheet.createRow(rowIdx); row.createCell(0).setCellValue(rs.getString(book_name)); row.createCell(1).setCellValue(rs.getString(isbn)); row.createCell(2).setCellValue(rs.getInt(total)); } } } try (FileOutputStream fos new FileOutputStream(file)) { wb.write(fos); } } }逻辑说明SQL 里record_type 1只统计领用status 1排除已撤销记录GROUP BY t.id按教材聚合。POI 的XSSFWorkbook实现Closeable用 try-with-resources 自动关闭。参数上file由界面层用JFileChooser选择默认文件名带班级和日期。导出后建议自动打开文件所在目录减少老师找文件的步骤。批量导入教材用JFileChooser选 Excel逐行读每行校验 ISBN 是否重复重复的跳过并记录行号最后统一提示成功多少条、跳过多少条。导入时不要每行开一个事务攒 100 行提交一次速度差很多。我自己的习惯是任何给非技术用户用的桌面系统导出和导入必须做而且导入的失败提示要具体到行号和原因否则老师只会说「导不进去」你连哪一行错了都不知道。这套 JavaSwingMysql 教材管理系统看起来老派但在没有运维、没有网络的办公环境里它是最省心的交付方式。把事务、分页、导出这三块做扎实系统就能真正跑起来。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?