Java Swing+MySQL图书管理系统3.0:从界面到事务的完整实战解析

发布时间:2026/10/7 16:49:56
Java Swing+MySQL图书管理系统3.0:从界面到事务的完整实战解析 简介一套基于Java Swing与MySQL数据库的图书管理系统项目资源面向需要完成课程设计或毕业设计的Java初学者。系统覆盖用户登录、图书类别添加与维护、图书信息添加与维护等核心功能以桌面窗体配合关系型数据库清晰演示了Swing界面与数据持久化的联动开发思路。压缩包共105个文件大小3.51MB其中包含14个Java源文件、43个class编译文件、4个jar依赖库、36张PNG界面截图、1份SQL建表脚本以及properties配置、工程与类路径文件SQL脚本包含建库建表语句可直接导入MySQL配合properties配置即可连接运行。目前已有659人学习下载。读者可从源码中理解登录校验、图书类别与图书信息的增删改查、DAO数据访问层的分层写法附带的界面截图与配置文件还有助于检查运行依赖、快速搭建本地环境整体适合作为练手项目与答辩参考。1. 图书管理系统不稀奇为什么 3.0 值得一次认真对待如果你在学校机房或公司工位上见过那种“图书借还”的小窗口程序那多半就是 JavaSwingMySQL 图书管理系统。这个组合几乎是课程设计和毕业设计里出场率最高的一套方案Swing 负责桌面界面MySQL 负责数据落盘Java 负责中间粘合。市面上 1.0 和 2.0 版本遍地都是能借书、能还书、能查书勉强交差而 3.0 的定位是把“能跑”变成“能交付”——加上用户登录和权限区分、借阅超期计算、统计报表、批量导入这些真正常被问到“你这系统还有什么功能”的点。这篇就是用一套可复现的完整方案把这个系统的设计思路、编码顺序、参数配置和踩坑点一次讲透适合正在做课设、准备毕设答辩或者想拿桌面开发练 Java 基础的人。2. 选型先立住Swing 做界面、MySQL 做存储3.0 的功能边界在哪2.1 为什么选 Swing而不是 JavaFX 或 Web 前端Swing 经常被吐槽“界面丑”但丑不丑是设计问题不是能力问题。在做图书管理系统这类内部工具时Swing 有一个非常现实的优点它是 JDK 自带的 GUI 框架不需要像 JavaFX 那样单独配置运行环境也不需要像 Spring Boot Vue 那样搭前后端两套工程。你只要装好 JDK配好JAVA_HOMEjavac就能编译java就能跑。这对课程设计答辩和机房部署来说省掉了一整条依赖链。更关键的是Swing 的事件模型和绘制模型足够教学化。JButton的addActionListener、JTable的TableModel、JTree的节点渲染这些知识点在面试里也经常被拿出来问。相比之下JavaFX 虽然现代的 CSS 样式和控件更漂亮但它的Property体系和 FXML 分离思路对新手来说理解成本更高Web 方案呢你要处理的跨域、会话、前端构建工具就又引入另一批问题。图书管理系统的核心是单机或局域网内小规模并发Swing 桌面程序完全扛得住。MySQL 这边同理。它是课设里最“讲道理”的数据库安装包在 Windows 10 上有图形化安装向导CREATE DATABASE建库、CREATE TABLE建表、JDBC 连接每一步都能用命令验证。SQLite 文件型数据库虽然更轻但“文件即数据库”的模型在展示阶段不够有说服力SQL Server 又太重。所以这个组合不是“旧技术”而是“刚好的技术”。2.2 从 1.0 到 3.0功能边界怎么划我见过大量 1.0 和 2.0 版本的图书管理系统大多只有四张表管理员表、图书表、读者表、借阅表。功能上就是登录、增删改查、借书还书连超期天数都是人工算的。3.0 的升级点应该集中在以下几个方向功能域1.0/2.0 常见形态3.0 建议做到的用户体系一个管理员账号写死管理员/读者两种角色登录后按角色显示菜单图书管理手工录入单本书支持 CSV 批量导入同一 ISBN 自动累加库存借阅流程借书、还书各一个按钮借出时校验库存和读者在借数量系统计算应还日期超期处理人工看日期借阅记录按状态区分在借/已还/超期界面高亮统计查询无借阅排行、分类占比、超期未还列表界面结构单窗口堆按钮左侧分类树 右侧表格的主从结构用表格定好边界之后数据库表设计才不会乱。这里我一般会再加一张分类表方便左侧用 Swing 的JTree做带勾选框的分类树——这也是我在做 3.0 时花时间最多的地方后面专门讲。2.3 开发环境基线环境选择直接影响后面排错。我在 Windows 10 上常用的组合是JDK 8 或 JDK 11 MySQL 8.0 IDEA Community MySQL Connector/J 8.0 的 jar 包。JDK 8 在 Swing 的兼容性上最稳MySQL 8.0 在安装时选utf8mb4字符集连接驱动必须用com.mysql.cj.jdbc.Driver旧版驱动com.mysql.jdbc.Driver在 8.0 里会直接报错。这些参数会在避坑章展开先记着“三处编码一致”这个原则。3. 把数据层立住六张核心表、JDBC 封装与高频 SQL3.1 表结构设计从分类到借阅流水图书管理系统最怕设计成“一本书一条记录”。现实情况是《Java 编程思想》有 5 本ISBN 一样应该用库存数字表达而不是复制五行记录。所以我的核心表一定是book表带total_count和remaining_count两个字段借出时扣remaining_count还回时加回来。一个最小可运行的 3.0 系统需要这六张表CREATE DATABASE library_db DEFAULT CHARACTER SET utf8mb4; USE library_db; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL UNIQUE ); CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(32) NOT NULL, title VARCHAR(128) NOT NULL, author VARCHAR(64), publisher VARCHAR(64), category_id INT, total_count INT NOT NULL DEFAULT 1, remaining_count INT NOT NULL DEFAULT 1, INDEX idx_book_category (category_id), CONSTRAINT fk_book_category FOREIGN KEY (category_id) REFERENCES category(id) ); CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(32) NOT NULL UNIQUE, name VARCHAR(64) NOT NULL, phone VARCHAR(32) ); CREATE TABLE admin_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 0 ); CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, status TINYINT NOT NULL DEFAULT 0, INDEX idx_borrow_reader (reader_id), INDEX idx_borrow_status (status), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id), CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES reader(id) );这里有几个设计意图要说明。category_id只存外键不存分类名是为了让左侧分类树可以动态从category表读取借阅记录的status用TINYINT0 表示在借、1 表示已还、2 表示超期未还状态字段加索引是因为“查所有超期未还”是高频操作admin_user的role字段0 管理员、1 读者登录后根据角色决定哪些窗口可见。注意FOREIGN KEY不能省很多课设回避外键导致程序删数据时删出脏记录后面数据对不上。3.2 JDBC 工具类不做连接池但要做统一关闭课程设计阶段不必上 HikariCP 连接池但一个统一的DbUtil类必须写。它的职责是加载驱动、提供连接、统一释放资源。别小看这个类90% 的“系统跑两天就卡死”都是连接没关。import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动未加载检查 jar 是否放入 classpath, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { /* 忽略 */ } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { /* 忽略 */ } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { /* 忽略 */ } } } }参数解释很重要。serverTimezoneAsia/Shanghai不写的话MySQL 8.0 和 JDBC 驱动会因时区不一致报The server time zone value错误characterEncodingutf8和建库时的utf8mb4配合保证中文写入不乱码useSSLfalse是避免本地连接时做 TLS 握手既慢又可能出现证书警告。close方法里ResultSet、Statement、Connection按逆序关闭否则连接无法真正释放这是新手最容易翻车的地方。3.3 高频 SQL图书检索、借阅排行与事务处理图书管理系统的 SQL 不需要多花哨但几个高频场景要写对。第一个是图书搜索用户输关键字要同时匹配书名、作者、出版社用LIKE配合OR是课设标准做法SELECT b.id, b.title, b.author, b.publisher, c.name AS category_name, b.total_count, b.remaining_count FROM book b LEFT JOIN category c ON b.category_id c.id WHERE b.title LIKE CONCAT(%, ?, %) OR b.author LIKE CONCAT(%, ?, %) OR b.publisher LIKE CONCAT(%, ?, %) ORDER BY b.id DESC;这里要点是不要直接拼字符串用PreparedStatement的?占位符防止 SQL 注入。LEFT JOIN而不是INNER JOIN是因为有些书可能还没分配分类内连接会把它们丢掉。第二个是借阅排行也就是热词里常搜的“mysql 排序”相关场景。按借阅次数降序排SELECT b.title, COUNT(br.id) AS borrow_times FROM book b LEFT JOIN borrow_record br ON b.id br.book_id GROUP BY b.id, b.title ORDER BY borrow_times DESC, b.title ASC LIMIT 10;GROUP BY b.id, b.title要写全MySQL 5.7 之后默认开了ONLY_FULL_GROUP_BY只GROUP BY b.id而 select 里带b.title会直接报错。ORDER BY用别名borrow_timesMySQL 允许按 select 别名排序比再包一层子查询清爽得多。第三个是借书操作的事务。“借书成功”在数据上等于三件事同时完成插入一条borrow_record、book表的remaining_count减一、reader表如果有在借数量限制还要加一。这三步必须在一个事务里否则中途断电就会出现“记录有了但库存没减”的灵异事件Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); String insertSql INSERT INTO borrow_record(book_id, reader_id, borrow_date, due_date, status) VALUES(?, ?, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY), 0); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, bookId); ps.setInt(2, readerId); ps.executeUpdate(); } String updateSql UPDATE book SET remaining_count remaining_count - 1 WHERE id ? AND remaining_count 0; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); int rows ps.executeUpdate(); if (rows 0) { throw new SQLException(库存不足借书失败); } } conn.commit(); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { /* 忽略 */ } } throw e; } finally { if (conn ! null) { try { conn.setAutoCommit(true); } catch (SQLException e) { /* 忽略 */ } } DbUtil.close(conn, null, null); }setAutoCommit(false)开启事务commit提交异常时rollback回滚。UPDATE ... WHERE remaining_count 0这个条件很关键它让数据库在并发借阅时自己拦住超借而不是靠 Java 代码先查再判断——先查再判断有竞态条件两个人同时借最后一本书时可能都通过检查。还书操作同理把return_date设为当前日期、status改为 1、remaining_count加一三条语句放一个事务里。4. Swing 界面按套路搭登录窗、主窗体、表格与勾选树4.1 登录窗口到主界面角色决定可见操作Swing 的界面代码没有太多“设计模式”可言但流程尽量固定下来。最省事的方式是先做一个LoginFrame验证用户名密码后根据role打开不同的主面板。登录窗口的按钮监听里点击事件必须拿SwingUtilities的线程模型说事数据库查询是耗时操作不能在事件线程里做否则窗口会假死。正确骨架是loginButton.addActionListener(e - { loginButton.setEnabled(false); new SwingWorkerBoolean, Void() { Override protected Boolean doInBackground() { String sql SELECT role FROM admin_user WHERE username ? AND password ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, usernameField.getText().trim()); ps.setString(2, new String(passwordField.getPassword())); try (ResultSet rs ps.executeQuery()) { return rs.next() ? rs.getInt(role) : -1; } } catch (SQLException ex) { return -1; } } Override protected void done() { loginButton.setEnabled(true); try { int role get(); if (role 0) { new AdminMainFrame().setVisible(true); } else if (role 1) { new ReaderMainFrame().setVisible(true); } else { JOptionPane.showMessageDialog(LoginFrame.this, 用户名或密码错误); } dispose(); } catch (Exception ex) { JOptionPane.showMessageDialog(LoginFrame.this, 登录异常); } } }.execute(); });SwingWorker的doInBackground跑数据库查询done切回界面线程更新 UI。这是 Swing 高并发操作的标准解法也是解决“界面假死”的万能钥匙后面避坑章还会再提。密码框用getPassword()拿字符数组而不是getText()拿字符串除了安全习惯也避免密码里有空格时被trim()误伤——注意这里用户名做了trim密码没做。4.2 JTable 表格模型不要让图书列表死掉主窗体的核心通常是一张JTable但新手容易直接把数据塞进DefaultTableModel然后不闻不问。图书管理系统的数据是动态的借了书库存变少、还了书状态变更表格应该跟着刷新。我一般封装一个refreshBookTable(String keyword)方法private void refreshBookTable(String keyword) { String sql SELECT b.id, b.isbn, b.title, b.author, c.name AS category_name, b.total_count, b.remaining_count FROM book b LEFT JOIN category c ON b.category_id c.id WHERE b.title LIKE ? OR b.author LIKE ? ORDER BY b.id DESC; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); try (ResultSet rs ps.executeQuery()) { DefaultTableModel model (DefaultTableModel) bookTable.getModel(); model.setRowCount(0); while (rs.next()) { model.addRow(new Object[]{ rs.getInt(id), rs.getString(isbn), rs.getString(title), rs.getString(author), rs.getString(category_name), rs.getInt(total_count), rs.getInt(remaining_count) }); } } } catch (SQLException e) { JOptionPane.showMessageDialog(this, 查询失败: e.getMessage()); } }setRowCount(0)是清空表格最快的办法比先getRowCount()再一行行removeRow干净。addRow传入Object[]JTable会自动调用各列的toString渲染。这里有一个很实用的细节如果想让“剩余库存不足”的行显示红色需要自定义DefaultTableCellRenderer覆写getTableCellRendererComponent判断第 6 列的值这是 3.0 版“超期高亮”功能的老祖宗。4.3 Swing 带勾选框的 Tree图书分类树这样加“swing 带勾选框的 tree”是很多人在做的功能。JTree默认的节点是普通文本要让它带复选框不能直接设置某个属性而是要自定义TreeCellRenderer和TreeCellEditor。这里给一个复杂度适中、课设答辩够用的做法节点用DefaultMutableTreeNode渲染器返回一个JCheckBox。import javax.swing.*; import javax.swing.tree.*; class CheckBoxNodeRenderer extends JPanel implements TreeCellRenderer { private final JCheckBox checkBox new JCheckBox(); public CheckBoxNodeRenderer() { setOpaque(false); } Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { Object nodeObj ((DefaultMutableTreeNode) value).getUserObject(); if (nodeObj instanceof CategoryNode) { CategoryNode cn (CategoryNode) nodeObj; checkBox.setText(cn.getName()); checkBox.setSelected(cn.isSelected()); } else { checkBox.setText(String.valueOf(value)); checkBox.setSelected(false); } return checkBox; } }配套的CategoryNode只是一个普通类持有分类名和选中状态两个字段。最关键的是getTreeCellRendererComponent返回的是JCheckBox单击事件要自己加MouseListener去翻转选中状态并触发表格刷新。这里的坑是JCheckBox的点击事件在TreeCellRenderer里不会被自动处理必须手工在JTree上注册鼠标监听判断点击位置是否落在节点内部然后更新DefaultMutableTreeNode的userObject并调用tree.repaint()。这套组合做出来后左侧分类树可以多选几个分类右侧JTable联动刷新效果比普通单选树高级一个档次而且你在答辩时还能解释“自定义渲染器”和“鼠标命中检测”两个细节面试聊 Swing 时这都是加分点。5. 避坑清单界面卡死、JDBC 连不上、编码乱码的排查记录5.1 界面假死点按钮后整个窗口白屏现象点击“查询图书”后窗口立刻失去响应标题栏出现“未响应”。原因数据库查询在 AWT 事件分发线程EDT里同步执行查询期间界面无法重绘看起来就是卡死。解决所有涉及数据库访问、文件读取的操作放进SwingWorker的doInBackground耗时结束后再回到done里更新组件。血泪经验如果只在一处改了而还书、借书、刷新三个按钮都直连数据库那假死会在几个入口轮着出现排查时建议全局搜索executeQuery逐一把调用点包进SwingWorker。5.2 中文乱码标题、输入框、数据库三处不一致现象窗口标题正常但JTable里查到的中文书名变成“???”或者存进去是中文重启后变乱码。原因JDBC URL 的characterEncoding、数据库表字符集、JVM默认编码三者不一致。解决建库时统一DEFAULT CHARACTER SET utf8mb4JDBC URL 加characterEncodingutf8JTable数据源是数据库来的不乱键头乱内容就是数据库的锅。还有一个易忽略点在CASEutf8和utf8mb4的关系是前者是后者的子集存 emoji 或特殊符号必须utf8mb4。别问我怎么知道的我的 2.0 版本因为把表建成latin1导入数据时半个书名都是问号最后ALTER TABLE book CONVERT TO CHARACTER SET utf8mb4才救回来。5.3 MySQL 连不上时区、SSL、驱动版本三连炸现象Class.forName不报错但DriverManager.getConnection抛Communications link failure或The server time zone value Öйú±ê׼ʱ¼ä。原因分三类一是连接串没写serverTimezone二是useSSL没关本地 MySQL 8.0 的 TLS 配置和 JDBC 驱动协商失败三是驱动 jar 用的还是mysql-connector-java 5.x类名是旧的com.mysql.jdbc.Driver而 MySQL 8.0 服务端已经不带旧驱动兼容。解决连接串统一写成jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8驱动类用com.mysql.cj.jdbc.Driverjar 包用mysql-connector-java-8.0.33.jar这类 8.x 版本。再补一条 Windows 上的经验安装 MySQL 8.0 时如果选了Use Strong Password EncryptionJDBC 连接没问题如果选了老式mysql_native_password有的驱动版本会告警但不阻塞别把告警当错误去乱改配置。5.4 数据对不上外键约束和事务缺失的连锁反应现象借书三次后borrow_record里有三条记录但book.remaining_count只减了两次或者图书删除了借阅记录还在。原因借书操作没走事务INSERT和UPDATE不是原子的中间一条失败另一条照样提交删除图书时没有用外键约束导致孤儿记录。解决借还流程全部用本章 3.3 的事务模板包起来建表时保留FOREIGN KEY约束删除图书前先检查borrow_record里有没有在借记录。这里有个实用场景实现“删除图书”时我先查status 0的记录存在就弹窗提示“该书尚有未归还记录”而不是直接DELETE——这种提示在答辩演示时非常加分。5.5 Docker 或旧安装包导致的版本幻觉现象本地跑得好好的换一台 Windows 机器部署就连不上数据库。原因目标机器上装的 MySQL 是 5.7而你的连接串里写了serverTimezoneMySQL 5.7 的时区表和 8.0 不一样会报Unknown or incorrect time zone。解决部署前先SELECT VERSION();确认目标机版本连接串里serverTimezone可以改成useLegacyDatetimeCodefalseserverTimezoneGMT%2B8。另外很多教程让人在 Windows 上装 Docker 跑 MySQL但对课程设计来说没必要直接去官网下mysql-installer-community-8.0.x.msi一路 Next记住 root 密码就行——Docker 在 Windows 上的网络模式和文件挂载会让你在排错时多绕一大圈。6. 把 3.0 收尾成一版拿得出手的系统批量导入与超期高亮6.1 用 JFileChooser 加 SwingWorker 做 CSV 批量导入3.0 版最提气的一个功能是“一键导入图书数据”。我习惯用JFileChooser选.csv文件然后逐行解析插入。关键代码骨架JFileChooser chooser new JFileChooser(); chooser.setFileFilter(new FileNameExtensionFilter(CSV 文件, csv)); if (chooser.showOpenDialog(this) JFileChooser.APPROVE_OPTION) { File file chooser.getSelectedFile(); new SwingWorkerVoid, Void() { Override protected Void doInBackground() { try (BufferedReader reader new BufferedReader(new InputStreamReader( new FileInputStream(file), StandardCharsets.UTF_8))) { String line; boolean firstLine true; while ((line reader.readLine()) ! null) { if (firstLine) { firstLine false; continue; } String[] cols line.split(,); if (cols.length 4) continue; // 执行 INSERTISBN 相同则 UPDATE total_count } } catch (IOException ex) { // 记录错误行号 } return null; } }.execute(); }CSV 文件编码建议统一UTF_8如果用 Excel 另存的 CSV 是GBK就会乱码。我的 3.0 里做了一个折中开头检测 BOM没有 BOM 就先用GBK解码失败再换UTF_8——这招叫“后悔药”比写死一种编码扛得住更多现场情况。6.2 借阅超期高亮给自己留一个可演示的亮点最后收尾在超期高亮上。实现方式分两步一是 SQL 里把超期记录状态标记为 2二是JTable的渲染器根据状态改变前景色。我在refreshBookTable之外还写了loadOverdueRecords查询所有status 2的记录渲染器里对状态列做if (status 2) { setForeground(Color.RED); }。这个小功能在答辩时的效果非常直观也呼应了当初在 2.0 里“人工算超期”的痛点升级。每次做完这类项目我都会养成的习惯是把连接串、驱动版本、建表 SQL 单独存成一个docs/文件夹和源码一起提交。因为隔三个月再回来看最坑你的往往不是代码逻辑而是那些当初配好就忘了的环境参数。希望这篇能帮你把 JavaSwingMySQL 图书管理系统 3.0 做成一个真正能演示、能讲清楚、能扛住追问的完整作品而不是又一个交了作业就删的工程。本文还有配套的精品资源点击获取