
简介这是一套面向高校计算机相关专业学生的 JavaSwingMySQL 图书管理系统完整源码包适合用作 Java 期末大作业、课程设计或自学练手项目。项目采用经典 MVC 分层结构涵盖 Model、View、Controller、Tool 等模块并附带数据库脚本、E-R 图、需求分析文档与运行说明方便读者理解图书管理业务的完整实现流程。压缩包共 50 个文件以 40 个 Java 源文件为核心另含 1 个 sql 建库脚本、1 个 mysql-connector 驱动 jar、若干图片素材及 md 说明文档整体约 2.27MB结构清晰、便于按模块查阅。该资源已经本地编译验证可运行评审得分达 98 分难度适中内容经助教老师审定能够满足学习、作业与课程设计需求。目前已有 181 人学习关注适合希望快速掌握 Swing 界面开发与 JDBC 数据库操作的读者参考使用。1. JavaSwingMysql图书管理系统一套能跑通、能讲清的桌面端实战骨架很多同学做课程设计或毕业设计时第一反应是去搜「图书管理系统源码」下载下来发现要么跑不起来要么代码结构一团乱改都不知道从哪下手。这套 JavaSwingMysql 的组合之所以被反复提起是因为它把桌面端开发最核心的三件事串成了一条线Swing 负责界面交互JDBC 负责数据通道Mysql 负责持久化存储。你拿到的不只是一堆能运行的代码而是一个可以拆开、替换、扩展的最小闭环。它适合正在学 Java 基础、需要交一份像样课设的在校生也适合想从控制台程序过渡到带界面应用的初学者。接下来我会按「环境怎么搭、表怎么建、界面怎么连数据、坑怎么绕」的顺序把这套东西讲透让你不仅能跑起来还能在答辩时说出每一层为什么这么写。2. 环境搭建与数据库准备从零把架子立起来2.1 JDK 与 IDE 的选择逻辑这套系统对 JDK 版本并不挑剔JDK 8 到 JDK 17 都能跑但我的建议是直接用 JDK 8 或 JDK 11。原因很简单Swing 是 Java 的老牌 UI 库高版本 JDK 虽然兼容但某些 IDE 的图形化设计器对高版本支持不稳定容易在拖拽控件时出现预览与实际运行不一致的玄学问题。IDE 用 IntelliJ IDEA 社区版就够了不需要旗舰版。新建项目时选 Java 项目不要选 Maven 或 Gradle因为这套源码通常是传统的 lib 目录加 jar 包结构用构建工具反而要多写配置文件。导入项目后第一件事是检查Project Structure里的Modules确认src目录被标记为 Sources Root。如果源码里有一个lib文件夹里面放着mysql-connector-java-x.x.x.jar你需要手动把它加到 Libraries 里。这一步漏掉的话编译时不会报错但运行到数据库连接那一步会直接抛ClassNotFoundException新手很容易卡在这里。2.2 Mysql 建库建表三张核心表就够用图书管理系统的数据模型并不复杂核心就三张表用户表、图书表、借阅记录表。下面这段 SQL 可以直接在 Navicat 或命令行里执行。注意字符集用utf8mb4否则书名里出现生僻字或特殊符号时会变成问号。-- 创建数据库指定字符集 CREATE DATABASE library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_db; -- 用户表区分管理员和普通读者 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT reader, -- admin 或 reader created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 图书表记录库存和基本信息 CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), total INT DEFAULT 1, -- 总库存 available INT DEFAULT 1, -- 可借数量 location VARCHAR(50) -- 书架位置 ); -- 借阅记录表谁借了哪本书什么时候还 CREATE TABLE borrow_records ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, book_id INT, borrow_date DATE, return_date DATE, status VARCHAR(20) DEFAULT borrowed, -- borrowed 或 returned FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (book_id) REFERENCES books(id) );建完表后插入一条管理员账号密码先用明文后面讲登录模块时再说怎么改成哈希。INSERT INTO users (username, password, role) VALUES (admin, 123456, admin);这条语句执行完你就有了一张进入系统的门票。2.3 JDBC 连接配置把黑匣子打开数据库连不上是这套系统最高频的翻车点。常见做法是把连接信息写在一个db.properties文件里而不是硬编码在 Java 类中。这样换数据库地址时不用重新编译。# db.properties drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password你的密码这里有几个参数必须解释清楚。useSSLfalse是为了避免本地开发时出现 SSL 连接错误Mysql 8.0 以上版本默认要求 SSL不加这个参数会报javax.net.ssl.SSLException。serverTimezoneAsia/Shanghai解决时区不一致导致的时间差 8 小时问题。allowPublicKeyRetrievaltrue是 Mysql 8.0 的 caching_sha2_password 认证插件需要的不加会提示Public Key Retrieval is not allowed。对应的 Java 工具类这样写public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream is DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(is); url props.getProperty(url); username props.getProperty(username); password props.getProperty(password); Class.forName(props.getProperty(driver)); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }静态代码块保证驱动只加载一次getConnection每次调用返回新连接。注意不要写成单例连接Swing 的事件线程和后台线程同时操作数据库时单例连接会出问题。3. Swing 界面与数据交互让按钮真正驱动数据库3.1 登录窗口的事件绑定与校验Swing 的组件模型是事件驱动的按钮点击后执行什么逻辑全靠ActionListener绑定。登录窗口通常包含用户名输入框、密码输入框、登录按钮和注册链接。下面这段代码展示了如何把登录按钮和数据库查询串起来。loginButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(null, 用户名和密码不能为空); return; } String sql SELECT id, role FROM users WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { int userId rs.getInt(id); String role rs.getString(role); // 根据角色打开不同主窗口 if (admin.equals(role)) { new AdminMainFrame(userId).setVisible(true); } else { new ReaderMainFrame(userId).setVisible(true); } dispose(); // 关闭登录窗口 } else { JOptionPane.showMessageDialog(null, 用户名或密码错误); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, 数据库连接失败); } } });这里用PreparedStatement而不是Statement拼接字符串是为了防 SQL 注入。密码字段用getPassword()返回 char 数组比getText()更安全虽然 Swing 的密码框本身防护有限但习惯要养好。登录成功后dispose()关闭当前窗口避免任务管理器里残留进程。3.2 图书表格的加载与实时刷新主界面通常用一个JTable展示图书列表。数据从books表查出来塞进DefaultTableModel再设置给JTable。这里的关键是刷新逻辑新增或删除图书后表格要能立刻反映变化而不是重启程序才更新。public void loadBooks() { String[] columns {ID, ISBN, 书名, 作者, 出版社, 总库存, 可借}; DefaultTableModel model new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格只读防止误改 } }; String sql SELECT id, isbn, title, author, publisher, total, available FROM books; try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { while (rs.next()) { model.addRow(new Object[]{ rs.getInt(id), rs.getString(isbn), rs.getString(title), rs.getString(author), rs.getString(publisher), rs.getInt(total), rs.getInt(available) }); } } catch (SQLException e) { e.printStackTrace(); } bookTable.setModel(model); }每次增删改操作完成后重新调用loadBooks()即可刷新。如果数据量大可以考虑只更新受影响的行但课设级别几百条数据全量刷新完全够用。注意isCellEditable返回 false否则用户双击单元格就能改数据但数据库不会同步造成界面和实际不一致。3.3 借书与还书的库存扣减逻辑借书操作不是简单插一条记录就完事必须同时更新books表的available字段并且要保证两个操作在同一个事务里。否则可能出现记录插入了但库存没扣或者库存扣了但记录没插入的情况。public boolean borrowBook(int userId, int bookId) { String checkSql SELECT available FROM books WHERE id ? FOR UPDATE; String insertSql INSERT INTO borrow_records (user_id, book_id, borrow_date, status) VALUES (?, ?, CURDATE(), borrowed); String updateSql UPDATE books SET available available - 1 WHERE id ? AND available 0; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 锁定该行防止并发借同一本书 PreparedStatement checkPs conn.prepareStatement(checkSql); checkPs.setInt(1, bookId); ResultSet rs checkPs.executeQuery(); if (!rs.next() || rs.getInt(available) 0) { conn.rollback(); return false; } PreparedStatement insertPs conn.prepareStatement(insertSql); insertPs.setInt(1, userId); insertPs.setInt(2, bookId); insertPs.executeUpdate(); PreparedStatement updatePs conn.prepareStatement(updateSql); updatePs.setInt(1, bookId); int affected updatePs.executeUpdate(); if (affected 0) { conn.rollback(); return false; } 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) { e.printStackTrace(); } } } }FOR UPDATE是行级锁保证在事务提交前其他连接不能修改这行数据。available 0的条件放在 UPDATE 语句里避免先查后改的竞态条件。还书逻辑类似把available 1同时更新借阅记录的状态和归还日期。4. 避坑与排查那些让系统跑不起来的细节4.1 中文乱码从数据库到界面全链路排查现象图书标题在数据库里看是正常的但在 Swing 表格里显示成问号或方块。原因通常出在 JDBC 连接的字符集参数上。Mysql 8.0 的驱动默认使用 UTF-8但如果连接 URL 里没有显式指定某些环境会回退到 latin1。解决方法是把 URL 改成jdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai。另外检查数据库和表的字符集是否都是utf8mb4用SHOW CREATE TABLE books;确认。4.2 驱动版本不匹配ClassNotFoundException 的两种面孔现象运行时报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver。原因是 Mysql 5.x 的驱动类是com.mysql.jdbc.Driver8.x 是com.mysql.cj.jdbc.Driver。如果你用的 jar 包是 8.x 但配置文件里写的是旧类名就会找不到。反过来也一样。解决方法是看lib目录下 jar 包的文件名mysql-connector-java-8.0.x.jar对应新类名5.1.x对应旧类名。不确定的话两个都试一下或者直接升级到 8.0 驱动并改配置。4.3 界面卡死EDT 线程里做数据库查询的后果现象点击查询按钮后界面冻结几秒按钮点不动窗口拖不动。原因是 Swing 的事件分发线程EDT被数据库查询阻塞了。Swing 的所有界面更新都必须在 EDT 里执行但耗时操作不能放在 EDT 里。解决方法是把数据库查询放到SwingWorker的doInBackground里查询完成后在done方法里更新表格。课设级别数据量小可能感觉不到卡顿但一旦数据上千条这个问题就会暴露。4.4 外键约束导致删除失败现象删除一本图书时抛出Cannot delete or update a parent row: a foreign key constraint fails。原因是borrow_records表里有这本书的借阅记录外键约束阻止删除。解决方法有两种一是先删除关联的借阅记录再删图书二是把外键的ON DELETE行为改成CASCADE。我一般推荐第一种因为借阅历史本身有保留价值不应该跟着图书一起消失。可以在删除前先查一下有没有未归还的记录有的话提示用户先处理。4.5 密码明文存储的隐患与改进现象数据库里能看到所有用户的密码包括管理员的。原因是最初建表时为了简单直接存了明文。这在课设里不算致命但答辩时老师很可能问「密码安全怎么考虑的」。改进方法是用MessageDigest做 MD5 哈希或者用BCrypt加盐哈希。MD5 虽然不安全但比明文强实现也简单。注册时存哈希值登录时把输入的密码哈希后比对。注意 MD5 哈希后是 32 位十六进制字符串数据库密码字段长度要够。5. 从能跑到能讲源码阅读顺序与二次开发切入点拿到一套源码不要急着改界面颜色或加花哨功能。先按「入口类 → 工具类 → 实体类 → 业务窗口」的顺序读一遍。入口类通常是Main.java或LoginFrame.java从这里能看出程序启动流程。工具类就是DBUtil它决定了数据库怎么连。实体类对应User、Book这些 POJO字段和表结构一一对应。业务窗口是AdminMainFrame、ReaderMainFrame里面藏着增删改查的具体实现。读完之后找一个最小的功能点做二次开发比如给图书表加一个「按书名模糊搜索」的输入框。这个改动涉及三个地方界面上加一个JTextField和查询按钮业务层加一个searchBooks(String keyword)方法SQL 里用LIKE %keyword%。改完跑通你就理解了数据从界面到数据库再回到界面的完整链路。这比漫无目的地改十处代码都有用。验证系统是否真正跑通不要只看登录成功。按这个清单走一遍用管理员账号登录新增一本书确认表格刷新且数据库里有记录用读者账号登录借这本书确认可借数量减一再借同一本书直到可借为零确认提示「库存不足」归还一本确认可借数量恢复。这套流程走完说明事务、外键、界面刷新都没问题。我自己的习惯是每改完一个功能就在README里记一笔「改了什么、为什么改、怎么验证的」。课设答辩时老师问「你做了什么」直接翻记录比临时回忆靠谱得多。这套系统不大但五脏俱全把它吃透以后遇到任何桌面端加数据库的项目你都能快速搭出骨架。希望帮到你。本文还有配套的精品资源点击获取