简介:这是一套面向高校计算机相关专业学生的Java+Swing+Mysql图书管理系统完整源码包,适合作为Java期末大作业、课程设计或自学练手项目。项目采用经典MVC分层结构,涵盖Model、View、Controller、Tool等模块,并附有数据库脚本、E-R图与需求分析文档,方便理解系统设计与实现思路。压缩包共50个文件,以40个java源文件为核心,另含sql建库脚本、mysql驱动jar包、界面图片素材及说明文档,整体约2.27MB,结构清晰、便于导入运行。该资源已经本地编译验证可运行,评审得分达98分,难度适中,内容经助教老师审定,能满足学习与作业提交需求。目前已有180人学习下载,读者可据此快速掌握Swing界面开发、JDBC数据库连接与增删改查等关键技能,并直接用于课程设计参考。
1. Java+Swing+MySQL 图书管理系统:一套能直接跑起来、也能写进简历的桌面级项目
很多同学做课程设计时都卡在同一个地方:想找一个结构完整、能编译、能连数据库、还能讲清楚设计思路的项目,结果搜到的要么是半成品,要么是只有界面没有业务逻辑,要么数据库脚本缺失。Java+Swing+MySQL 图书管理系统这个组合之所以年年被搜,是因为它刚好覆盖了 Java 基础、面向对象编程、JDBC、Swing 事件驱动、MySQL 建表与查询这几块核心能力,而且不依赖 Web 容器,本地双击就能跑。它适合三类人:正在做 Java 课程设计的学生、准备把项目写进简历的求职者、以及想用一个小型桌面系统练手数据库连接池和分层设计的初学者。下面我按实际落地顺序,把环境、建库、分层、界面、排错和进阶验证一次讲透。
2. 环境与数据库先落地:JDK、MySQL、驱动版本怎么配才不翻车
2.1 为什么桌面项目反而更容易在环境上踩坑
Web 项目通常有 Maven 帮你管依赖,桌面项目很多人是手动导入 jar 包,结果第一步就卡住。Java+Swing+MySQL 图书管理系统的运行链路其实很短:JDK 负责编译运行,Swing 负责界面渲染,JDBC 负责和 MySQL 通信,中间靠一个驱动 jar 包连接。链路短意味着每一环都必须对,任何一环版本不匹配都会直接报错。
我一般建议用 JDK 8 或 JDK 11 这两个长期支持版本,原因是大量课程设计源码默认用 JDK 8 编译,用高版本 JDK 打开可能出现模块化相关的警告甚至编译失败。MySQL 建议用 5.7 或 8.0,两者在 SQL 语法上差异不大,但驱动类名和连接串参数有区别,后面会具体说。IDE 用 IntelliJ IDEA 或 Eclipse 都行,关键是能把 jar 包加进 classpath。
提示:不要一上来就追最新版本。课程设计类项目追求的是稳定复现,不是版本尝鲜。
2.2 建库建表:图书管理系统最少需要几张表
一个能跑通的图书管理系统,核心表通常有四张:用户表、图书表、借阅记录表、分类表。下面这套建表语句是我常用的最小可用结构,字段命名尽量直白,方便后面写 JDBC 时对照。
-- 创建数据库,字符集用 utf8mb4 避免中文乱码 CREATE DATABASE library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_db; -- 用户表:区分管理员和普通读者 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role TINYINT DEFAULT 0 COMMENT '0读者 1管理员', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 图书表:isbn 唯一,stock 表示可借库存 CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), category_id INT, stock INT DEFAULT 0, total INT DEFAULT 0 ); -- 借阅记录表:status 0借出 1已还 CREATE TABLE t_borrow ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP, return_date DATETIME, status TINYINT DEFAULT 0 ); -- 分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL );这段脚本的逻辑是:用户和图书是多对多关系,通过借阅记录表拆开;库存字段 stock 和总量 total 分开,是为了支持“借出后库存减一、归还后库存加一”这种最常见的业务规则。参数上要注意utf8mb4必须显式指定,否则中文书名插入后可能变成问号。AUTO_INCREMENT主键让 JDBC 插入后能通过getGeneratedKeys拿到新 ID,后面做借阅关联时会用到。
2.3 JDBC 驱动导入与连接串写法
MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver,5.7 是com.mysql.jdbc.Driver,这是最容易搞混的一点。连接串推荐写成下面这样:
// MySQL 8.0 连接串,注意时区和 SSL 参数 private static final String URL = "jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; // 加载驱动,静态代码块保证只执行一次 static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } }serverTimezone不写会在 MySQL 8.0 下报时区错误,useSSL=false是本地开发省去证书配置。characterEncoding=utf8和建库时的 utf8mb4 配合,保证中文不乱码。如果你用的是 MySQL 5.7,把驱动类名换成旧的那个,连接串里serverTimezone可以去掉。
3. 分层设计与核心业务:从实体类到借阅逻辑怎么写才不乱
3.1 为什么图书管理系统一定要分层
很多人写课程设计喜欢把 SQL 直接写在按钮的监听事件里,结果一个actionPerformed方法几百行,改一个字段要翻半天。图书管理系统虽然小,但业务规则并不少:借书要判断库存、还书要更新记录、查询要支持多条件。不分层的话,这些逻辑会散落在各个界面类里,后期加一个“续借”功能就得改五六个文件。
我一般按 entity、dao、service、view 四层来组织。entity 放实体类,dao 封装所有 JDBC 操作,service 写业务规则,view 只负责界面和事件转发。这样做的直接好处是:换数据库只改 dao,改业务规则只改 service,调界面只动 view。
3.2 实体类与 DAO 的通用写法
实体类就是字段加 getter/setter,这里不展开。重点看 DAO 层,下面是一个图书查询和库存更新的典型实现:
public class BookDao { // 根据书名模糊查询,返回列表 public List<Book> findByTitle(String keyword) { List<Book> list = new ArrayList<>(); String sql = "SELECT * FROM t_book WHERE title LIKE ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + keyword + "%"); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Book b = new Book(); b.setId(rs.getInt("id")); b.setTitle(rs.getString("title")); b.setStock(rs.getInt("stock")); list.add(b); } } } catch (SQLException e) { e.printStackTrace(); } return list; } // 借书:库存减一,返回影响行数 public int decreaseStock(int bookId) { String sql = "UPDATE t_book SET stock = stock - 1 WHERE id = ? AND stock > 0"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, bookId); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }decreaseStock里的AND stock > 0是关键,它把库存判断放进了 SQL,避免先查再改导致的并发超借。返回的影响行数为 0 就说明库存不足,service 层据此提示用户。try-with-resources保证连接和语句自动关闭,这是 JDBC 里最容易被忽略的资源泄漏点。
3.3 借阅业务的事务控制
借书这个动作涉及两张表:插入借阅记录、扣减库存。两步必须在一个事务里,否则可能出现扣了库存但没记录,或者有记录但库存没扣。service 层这样写:
public boolean borrowBook(int userId, int bookId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 第一步:扣库存 String sql1 = "UPDATE t_book SET stock = stock - 1 WHERE id = ? AND stock > 0"; PreparedStatement ps1 = conn.prepareStatement(sql1); ps1.setInt(1, bookId); if (ps1.executeUpdate() == 0) { conn.rollback(); return false; // 库存不足 } // 第二步:插入借阅记录 String sql2 = "INSERT INTO t_borrow(user_id, book_id) VALUES(?, ?)"; PreparedStatement ps2 = conn.prepareStatement(sql2); ps2.setInt(1, userId); ps2.setInt(2, bookId); ps2.executeUpdate(); conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }事务的边界就是setAutoCommit(false)到commit()之间。任何一步失败都要rollback(),否则数据会处于中间状态。这里没有用连接池,每次新建连接,对课程设计够用;如果要做成简历亮点,可以换成 Druid 或 HikariCP,后面进阶部分会说。
4. Swing 界面与事件绑定:让按钮真的能查到数据
4.1 界面布局的常见选型
Swing 布局管理器很多,图书管理系统最常用的是BorderLayout加JTable。主窗口用 BorderLayout,北部放搜索栏,中部放表格,南部放操作按钮。表格用DefaultTableModel,这样增删改查后刷新数据只需要重新设置模型。
// 初始化图书表格 String[] columns = {"ID", "ISBN", "书名", "作者", "库存"}; DefaultTableModel model = new DefaultTableModel(columns, 0) { @Override public boolean isCellEditable(int row, int column) { return false; // 表格只读,防止误改 } }; JTable table = new JTable(model); JScrollPane scrollPane = new JScrollPane(table); frame.add(scrollPane, BorderLayout.CENTER); // 查询按钮事件 JButton searchBtn = new JButton("查询"); searchBtn.addActionListener(e -> { String keyword = searchField.getText().trim(); List<Book> books = bookService.findByTitle(keyword); model.setRowCount(0); // 清空旧数据 for (Book b : books) { model.addRow(new Object[]{b.getId(), b.getIsbn(), b.getTitle(), b.getAuthor(), b.getStock()}); } });isCellEditable返回 false 是血泪经验,不覆盖这个方法的话用户双击单元格就能改数据,但改的只是界面模型,数据库没变,造成界面和库不一致。model.setRowCount(0)每次查询前清空,避免结果叠加。
4.2 事件线程与界面卡顿
Swing 是单线程模型,所有界面更新必须在事件分发线程(EDT)里做。如果你在按钮事件里直接执行一个耗时查询,界面会假死。图书管理系统数据量小,通常感觉不到,但一旦加上导出 Excel 或批量导入,就会明显卡顿。正确做法是把耗时操作放到SwingWorker里:
new SwingWorker<List<Book>, Void>() { @Override protected List<Book> doInBackground() { return bookService.findAll(); // 后台线程查数据库 } @Override protected void done() { try { List<Book> books = get(); // 这里回到 EDT,安全更新表格 refreshTable(books); } catch (Exception e) { e.printStackTrace(); } } }.execute();doInBackground在后台线程跑,done自动回到 EDT,这样界面不会卡。参数上注意get()会抛出异常,必须捕获,否则后台的 SQL 异常会被吞掉,排查时找不到原因。
4.3 登录与权限控制的最小实现
登录界面通常是一个JDialog,输入用户名密码后查t_user表,根据 role 字段决定主界面显示哪些按钮。普通读者隐藏“图书管理”和“用户管理”菜单,管理员全部显示。这里不要在界面上做真正的安全控制,因为桌面程序反编译很容易,权限只是交互层面的区分,课程设计够用。
5. 避坑与排查:这几个错误几乎每个人都会遇到
5.1 中文乱码:现象是书名显示问号
现象:插入或查询中文书名时,数据库里是问号,或者界面显示乱码。原因通常是三处字符集不一致:建库时没指定 utf8mb4、连接串没写 characterEncoding、或者 Swing 组件字体不支持中文。解决方法是建库语句加DEFAULT CHARACTER SET utf8mb4,连接串加useUnicode=true&characterEncoding=utf8,界面字体设置成“微软雅黑”或“宋体”。
5.2 驱动报错:No suitable driver found
现象:运行时报No suitable driver found for jdbc:mysql://...。原因有两个:一是驱动 jar 没加到 classpath,二是驱动类名写错。MySQL 8.0 必须用com.mysql.cj.jdbc.Driver,写成旧的com.mysql.jdbc.Driver在 8.0 驱动下会报类找不到。解决方法是确认 jar 包在项目依赖里,并且Class.forName的类名和驱动版本匹配。
5.3 时区异常:The server time zone value is unrecognized
现象:MySQL 8.0 下连接时报时区错误。原因是 8.0 驱动要求明确指定时区。解决方法是在连接串加serverTimezone=Asia/Shanghai,或者改成serverTimezone=GMT%2B8。这个问题在 5.7 驱动里不常见,所以换版本时容易突然翻车。
5.4 库存超借:并发下库存变成负数
现象:两个人同时借同一本书,库存只剩一本,结果两个人都借成功了。原因是先查库存再更新,两步之间有时间窗口。解决方法是用UPDATE t_book SET stock = stock - 1 WHERE id = ? AND stock > 0,把判断和更新合并成一条原子 SQL,根据影响行数判断是否成功。
5.5 界面数据不同步:改了库但表格没刷新
现象:借书成功后,数据库库存已经减了,但表格里还显示旧库存。原因是表格模型没有重新加载。解决方法是在每次业务操作成功后调用刷新方法,重新查询并setRowCount(0)后填充。不要试图手动改单元格值,那样容易和数据库不一致。
6. 进阶验证:把课程设计变成能讲清楚的项目经历
6.1 用连接池替换手写连接
课程设计里每次DriverManager.getConnection都新建物理连接,性能差且无法体现工程能力。换成 HikariCP 只需要改 DBUtil:
public class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"); config.setUsername("root"); config.setPassword("你的密码"); config.setMaximumPoolSize(10); // 最大连接数 config.setMinimumIdle(2); // 最小空闲连接 config.setConnectionTimeout(30000); // 连接超时 30 秒 dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }maximumPoolSize设成 10 对桌面项目足够,connectionTimeout防止数据库没启动时界面无限等待。替换后 DAO 层代码完全不用改,因为getConnection的签名没变,这就是分层的好处。
6.2 用单元测试验证借阅逻辑
不要只靠点界面验证。给 service 层写 JUnit 测试,能提前发现事务和边界问题:
@Test public void testBorrowWhenStockIsZero() { // 先把某本书库存改成 0 bookDao.updateStock(1, 0); boolean result = borrowService.borrowBook(1, 1); assertFalse("库存为 0 时不应借阅成功", result); } @Test public void testBorrowThenReturn() { boolean borrow = borrowService.borrowBook(1, 2); assertTrue(borrow); boolean returned = borrowService.returnBook(1, 2); assertTrue(returned); // 验证库存恢复 assertEquals(5, bookDao.findById(2).getStock()); }测试用例要覆盖库存为 0、重复借阅、归还后库存恢复这三种情况。跑通这些测试,你在面试时就能说清楚事务边界和并发控制,而不是只说“我做了个增删改查”。
6.3 一个具体技巧:用表格行选中代替输入 ID
很多源码让用户手动输入图书 ID 来借书,体验差还容易输错。正确做法是从JTable获取选中行:
int selectedRow = table.getSelectedRow(); if (selectedRow == -1) { JOptionPane.showMessageDialog(frame, "请先选中一本书"); return; } // 转换视图行号到模型行号,排序后必须这样取 int modelRow = table.convertRowIndexToModel(selectedRow); int bookId = (int) model.getValueAt(modelRow, 0);convertRowIndexToModel是排序场景下的后悔药,如果表格启用了RowSorter,直接用getSelectedRow拿到的行号和模型行号不一致,会借错书。这个坑我在第一次做带排序的表格时踩过,排查了半天才发现是行号映射问题。
这套项目真正值钱的地方不是界面多漂亮,而是你能把分层、事务、连接池、单元测试这些点讲清楚。我自己的习惯是每做完一个功能就写一个测试用例,跑通了再点界面,这样出问题时能快速定位是 SQL 错了还是事件绑定错了。希望帮到你。
本文还有配套的精品资源,点击获取