Java+Swing+MySQL教材管理系统源码实战:从跑通到避坑
简介这份资源是面向高校学生与Java初学者的一套教材管理系统源代码适合作为课程设计、毕业设计或Swing桌面开发练手项目。系统围绕管理员对教材的日常管理展开涵盖教材信息录入、按教材号查询、入库与出库、库存查询以及统计打印等核心业务出库时通过教材号删除对应记录查询则输出教材完整信息功能闭环较为完整。压缩包共7个文件全部为java源码整体约10KB体量轻巧便于阅读与二次修改其中包含登录、图书录入、图书浏览、查询与数据库连接等模块结构清晰适合对照学习Swing界面与MySQL交互的写法。目前已有1193人学习下载可作为课设参考模板帮助读者快速理解桌面端管理系统的分层思路、数据库操作流程与调试方法也可在此基础上扩展借阅、用户权限等功能。1. 从一份教材管理系统源码说起JavaSwingMySQL 到底能跑出什么很多同学在课程设计或毕业设计阶段会拿到一份「JavaEclipseSwingMySql 教材管理系统源代码」第一反应往往是双击导入、点运行然后发现连不上数据库、界面乱码、按钮点了没反应。这份源码本质上是一个典型的 C/S 桌面应用Swing 负责前端窗口和控件Java 负责业务逻辑MySQL 负责持久化教材信息、库存、出入库记录。它解决的核心问题是——让教材管理员不依赖浏览器在本地就能完成教材入库、领用、归还、查询和统计。适合谁适合正在学 Java 基础、面向对象编程、JDBC 连接、Swing 事件驱动的人也适合需要快速交付一个可演示课程设计的人。但要注意这类源码质量参差不齐直接跑通只是第一步真正值钱的是你能看懂它的分层、改掉它的硬编码、补上它缺失的校验。下面我按「先跑通、再拆解、后避坑」的顺序把这份源码从环境到代码讲透。2. 把源码跑起来Eclipse 导入、MySQL 建库、JDBC 连接三件事2.1 环境准备与 Eclipse 导入项目的正确姿势拿到源码压缩包后先别急着 Import。我一般会先看目录结构如果根目录下有.project和.classpath说明是 Eclipse 工程可以直接用File → Import → General → Existing Projects into Workspace导入如果只有src和lib那就新建一个 Java Project再把src拷进去、把lib下的 jar 加到 Build Path。这里有个血泪经验很多源码用的是 JDK 8而你本地装的是 JDK 17 或 21导入后满屏红叉。解决办法是右键项目 → Properties → Java Build Path → Libraries把 JRE 换成 1.8或者改源码里的语法兼容。Eclipse 安装教程网上很多但关键就一句装完记得在eclipse.ini里把-Dfile.encodingUTF-8加上否则中文注释和界面文字会变问号。导入后先别运行检查lib目录下有没有mysql-connector-java-x.x.x.jar。如果没有去 MySQL 官网下载对应版本注意 8.0 以上用mysql-connector-j-8.x.x.jar驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.DriverURL 也要加时区和 SSL 参数。这一步不做后面必报ClassNotFoundException或Communications link failure。# 查看本地 MySQL 版本决定用哪个驱动 mysql --version # 输出示例mysql Ver 8.0.36 for Win64 on x86_64上面命令用来确认 MySQL 大版本。5.7 和 8.0 在驱动、URL、认证插件上都有差异先确认版本再选驱动能省掉一半的连不上问题。2.2 MySQL 建库建表从 SQL 脚本到字符集设置源码里通常会带一个.sql文件但很多人直接双击运行会报错因为里面可能写了CREATE DATABASE却没指定字符集。我一般手动建库再导入表结构。下面是一份教材管理系统最小可用的建库脚本字段按常见需求设计你可以对照源码里的实体类调整。-- 创建数据库强制 utf8mb4避免中文乱码 CREATE DATABASE textbook_ms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE textbook_ms; -- 教材表 CREATE TABLE tb_textbook ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 教材名称, author VARCHAR(50) COMMENT 作者, publisher VARCHAR(100) COMMENT 出版社, isbn VARCHAR(20) UNIQUE COMMENT ISBN号, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 单价, stock INT DEFAULT 0 COMMENT 库存数量, category VARCHAR(50) COMMENT 分类 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 领用记录表 CREATE TABLE tb_record ( id INT PRIMARY KEY AUTO_INCREMENT, textbook_id INT NOT NULL, user_name VARCHAR(50) NOT NULL COMMENT 领用人, amount INT NOT NULL COMMENT 领用数量, record_time DATETIME DEFAULT CURRENT_TIMESTAMP, status VARCHAR(20) DEFAULT 已领用, FOREIGN KEY (textbook_id) REFERENCES tb_textbook(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建库时最容易翻车的是字符集。如果数据库用latin1Swing 界面输入的中文存进去就是乱码查出来也是问号。所以从库到表到连接 URL 都要统一utf8mb4。另外stock字段建议设默认值 0对应热词里提到的「mysql设置默认值为0」避免插入时 null 导致库存计算异常。外键约束看源码需求如果源码里没做级联删除建议先不加外键否则删教材时会被记录表拦住。2.3 JDBC 连接封装URL 参数、驱动类与连接池取舍源码里最常见的写法是写一个DBUtil或BaseDao类里面硬编码url、user、password。跑通阶段可以这样但你要知道每个参数的含义。下面是一个标准封装示例import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 8.0 驱动类名5.7 用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关闭 SSL 警告serverTimezone 解决时区报错 private static final String URL jdbc:mysql://localhost:3306/textbook_ms ?useUnicodetruecharacterEncodingutf8mb4 useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动未加载检查 lib 下是否有 connector jar, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码的关键在 URL 参数。useSSLfalse解决 MySQL 8.0 默认要求 SSL 导致的连接失败对应热词里的「mysql ssl连接错误」serverTimezoneAsia/Shanghai解决The server time zone value异常allowPublicKeyRetrievaltrue解决 8.0 的 caching_sha2_password 认证问题。如果你用连接池如 Druid、HikariCP把getConnection换成从 DataSource 取即可但课程设计阶段没必要直连更直观。注意密码不要提交到公开仓库这是基本习惯。3. Swing 界面与事件驱动教材管理系统的前端骨架怎么搭3.1 主窗口布局JFrame、JTable 与菜单栏的组合Swing 做管理系统核心就三块JFrame主窗口、JTable数据表格、JMenuBar菜单栏。源码里通常有一个MainFrame类继承 JFrame构造方法里设置setSize、setLocationRelativeTo(null)居中、setDefaultCloseOperation(EXIT_ON_CLOSE)。表格用DefaultTableModel承载数据列名和数据库字段对应。下面是一个最小主界面骨架import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; public class MainFrame extends JFrame { private JTable table; private DefaultTableModel tableModel; public MainFrame() { setTitle(教材管理系统); setSize(900, 600); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 菜单栏 JMenuBar menuBar new JMenuBar(); JMenu menu new JMenu(教材管理); menu.add(new JMenuItem(新增教材)); menu.add(new JMenuItem(领用登记)); menuBar.add(menu); setJMenuBar(menuBar); // 表格模型列名与数据库字段对应 String[] columns {ID, 教材名称, 作者, 出版社, ISBN, 单价, 库存}; tableModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格只读编辑走弹窗 } }; table new JTable(tableModel); add(new JScrollPane(table), BorderLayout.CENTER); setVisible(true); } }这里有个细节DefaultTableModel重写isCellEditable返回 false防止用户直接在表格里改数据导致和数据库不同步。菜单项要绑定ActionListener点击后弹出JDialog做新增或领用。布局用BorderLayout把表格放中间工具栏或按钮放北边或南边。如果你看到源码里用绝对定位setBounds那说明作者没学好布局管理器窗口拉伸后控件会错位建议改成BorderLayoutGridBagLayout。3.2 事件监听与数据刷新按钮点击后表格怎么更新Swing 是单线程模型所有界面更新必须在事件分发线程EDT里做。常见错误是在按钮监听里直接查数据库、更新表格数据量大时界面卡死。正确做法是查询逻辑放后台线程SwingWorker结果回到 EDT 更新tableModel。下面演示新增教材后刷新表格的流程JButton addBtn new JButton(新增教材); addBtn.addActionListener(e - { // 弹出输入对话框实际项目用自定义 JDialog String name JOptionPane.showInputDialog(this, 请输入教材名称); if (name null || name.trim().isEmpty()) { JOptionPane.showMessageDialog(this, 教材名称不能为空); return; } // 后台执行插入避免阻塞 EDT new SwingWorkerVoid, Void() { Override protected Void doInBackground() throws Exception { String sql INSERT INTO tb_textbook(name, stock) VALUES(?, 0); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, name.trim()); ps.executeUpdate(); } return null; } Override protected void done() { loadTableData(); // 回到 EDT 刷新表格 } }.execute(); });SwingWorker的doInBackground在后台线程跑 JDBCdone在 EDT 执行保证线程安全。loadTableData方法里清空tableModel再重新查库填充。注意每次操作后都要刷新否则用户以为没保存。如果源码里用Vector存数据、手动fireTableDataChanged原理一样但DefaultTableModel更省事。热词里提到的「swing带勾选框的tree」属于进阶控件教材管理系统一般用不到但如果你要加分类树可以用JTree加CheckBoxTreeCellRenderer那是另一个话题。4. 避坑与排查源码跑不通时先查这五个地方4.1 数据库连不上从驱动到权限逐层排查现象启动报Communications link failure或Access denied for user。原因通常有三层驱动 jar 没加、URL 参数不对、MySQL 用户权限不够。解决顺序是——先确认lib下有 connector jar 且已 Build Path再检查 URL 端口 3306 是否被占用或 MySQL 没启动最后用命令行mysql -u root -p登录执行SELECT user, host FROM mysql.user;看 root 是否只允许 localhost。如果是 8.0 的caching_sha2_password插件导致可以ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;临时切换但生产环境不建议。4.2 中文乱码库、表、连接、JVM 四处都要 UTF-8现象界面输入中文存库后变问号或查出来是乱码。原因字符集不统一。解决建库用utf8mb4表用utf8mb4JDBC URL 加characterEncodingutf8mb4Eclipse 工作空间编码设为 UTF-8JVM 启动参数加-Dfile.encodingUTF-8。四处缺一不可。如果已经存了乱码数据只能重新录入因为乱码是不可逆的。4.3 主类找不到Eclipse 运行配置与包名不匹配现象点运行报找不到或无法加载主类。原因源码包名和实际目录不一致或者运行配置里 Main class 选错。解决右键项目 → Build Path → Source确认源码根目录再右键主类 → Run As → Java Application。如果源码里包名是com.textbook.view但文件放在src/view下就会报错需要手动改包声明或移动文件。4.4 表格数据不显示查询结果集映射漏字段现象界面出来了表格空白。原因ResultSet遍历时字段名写错或者tableModel没addRow。解决在while(rs.next())里打印rs.getString(name)确认有数据检查tableModel.addRow是否在循环内检查列顺序和columns数组是否一致。常见错误是rs.getString(1)索引从 1 开始有人写成 0 直接异常。4.5 按钮点击无反应事件监听未绑定或异常被吞现象点按钮没反应控制台也没报错。原因addActionListener没加或者监听里抛异常被try-catch吞了。解决在监听方法第一行加System.out.println(clicked)确认是否触发检查catch块里是否只写了e.printStackTrace()但没弹窗提示。Swing 的异常默认打到控制台用户看不到建议用JOptionPane把错误弹出来。5. 从能跑到好用源码改造的三个进阶技巧5.1 把硬编码 SQL 换成 DAO 分层原始源码经常在按钮监听里直接写 SQL改一个字段要翻好几个文件。我一般会抽一个TextbookDao类方法包括findAll()、insert(Textbook t)、updateStock(int id, int delta)。实体类Textbook和表字段一一对应。这样界面层只调 DAO业务逻辑清晰也方便后面换数据库。下面是一个 DAO 方法示例public ListTextbook findAll() throws SQLException { ListTextbook list new ArrayList(); String sql SELECT id, name, author, publisher, isbn, price, stock FROM tb_textbook; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Textbook t new Textbook(); t.setId(rs.getInt(id)); t.setName(rs.getString(name)); t.setStock(rs.getInt(stock)); list.add(t); } } return list; }try-with-resources自动关闭连接、语句、结果集避免资源泄漏。返回List而不是ResultSet因为ResultSet在连接关闭后失效这是新手常踩的坑。5.2 库存扣减用事务保证一致性领用教材时要先查库存够不够再扣减再插记录。这三步必须在一个事务里否则并发下会超卖。MySQL 事务处理用conn.setAutoCommit(false)成功后commit异常rollback。下面是一个扣库存的事务方法public boolean lendBook(int textbookId, String userName, int amount) throws SQLException { String checkSql SELECT stock FROM tb_textbook WHERE id? FOR UPDATE; String updateSql UPDATE tb_textbook SET stockstock-? WHERE id? AND stock?; String insertSql INSERT INTO tb_record(textbook_id, user_name, amount) VALUES(?,?,?); try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement check conn.prepareStatement(checkSql); PreparedStatement update conn.prepareStatement(updateSql); PreparedStatement insert conn.prepareStatement(insertSql)) { check.setInt(1, textbookId); try (ResultSet rs check.executeQuery()) { if (!rs.next() || rs.getInt(stock) amount) { conn.rollback(); return false; } } update.setInt(1, amount); update.setInt(2, textbookId); update.setInt(3, amount); if (update.executeUpdate() 0) { conn.rollback(); return false; } insert.setInt(1, textbookId); insert.setString(2, userName); insert.setInt(3, amount); insert.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } } }FOR UPDATE行锁防止并发查库存时读到旧值UPDATE ... WHERE stock?再兜底一次。事务提交前任何一步失败都回滚保证记录和库存一致。这是教材管理系统从「能跑」到「能用」的关键一步。5.3 用表格排序和筛选提升查询体验教材多了以后用户需要按名称、库存排序。JTable自带RowSorter一行代码就能开启table.setAutoCreateRowSorter(true);。如果要在数据库层排序SQL 加ORDER BY stock ASC即可对应热词里的「mysql排序」。筛选可以用TableRowSorter加RowFilter根据输入框内容过滤。注意排序后行索引会变取选中行数据要用table.convertRowIndexToModel(table.getSelectedRow())否则删错记录。这个坑我踩过删了不该删的教材后悔药都没得吃。最后说个习惯每次改完源码先备份一份能跑的版本再动刀。我一般用 Git 本地仓库提交信息写清楚「修复库存事务」还是「调整表格排序」。这样翻车了能回退也方便对比自己改了什么。希望帮到你。本文还有配套的精品资源点击获取