news 2026/10/26 23:44:03

Java图书管理系统GUI版:Swing+JDBC+MySQL课设实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java图书管理系统GUI版:Swing+JDBC+MySQL课设实战解析

简介:面向计算机专业学生的Java课程设计/期末大作业参考实现,提供一套完整可运行的图书管理GUI系统,覆盖图书入库、借阅归还、查询统计等核心业务,附数据库建表脚本与初始数据,可直接导入使用。压缩包共37个文件,以29个Java源码文件为主体,配合SQL数据库脚本、XML工程配置、PNG/JPG界面素材及README说明文档,整体大小2.23MB,结构清晰便于查阅与二次开发。系统按功能模块划分窗口界面,JDBC连接数据库的实现方式对理解图形界面与数据持久化衔接很有帮助,适合用来巩固Java基础语法、事件处理等内容。已有87人学习,作为完成并获评98分的课设案例,能为准备这类项目的读者提供完整的代码参考与功能对照。

1. Java 图书管理系统 GUI 版:为什么它是课设季最值得拆的参考

每到期末,总有一批人卡在同一个地方:课程设计题目是「图书管理系统」,要求用 Java 写图形界面,还要连数据库。自己从 Swing 布局啃起,再写 JDBC,光是把 JFrame、JTable 和事件监听理顺就要两三天,更别提借阅、归还时的状态流转。这份 Java 图书管理系统(GUI 图形界面版)源码,恰好把这条链路完整走了一遍:界面用 Java Swing 搭建,业务逻辑按面向对象拆分,数据落在 MySQL 关系型数据库里,通过 JDBC 统一访问。它自带 Maven 工程、数据库创建脚本、界面图片资源和 README 说明,适合计算机专业学生做课程设计、期末大作业或项目实战练习,也适合想快速搞懂「Java 桌面应用到底怎么分层」的初学者。我拿它当脚手架,省掉了从空白项目起步的大部分试错成本。

2. 先把工程拆开看:从 pom.xml 到 src/main 的模块划分与启动方式

2.1 源码包里到底有什么

拿到压缩包以后,先别急着双击 .java 文件。这一步很多人会走弯路:在解压目录里胡乱点开几个类,结果连哪个是入口都没找到。先按文件清单过一遍,你会对这个项目的成色有个准确判断。

文件 / 目录作用
java图书管理系统(GUI)图形界面版.zip压缩包主体,包含源码、图片资源和数据库脚本
数据库创建脚本.sql建库、建表、初始化数据用的 SQL 脚本,是整个系统的数据地基
pom.xmlMaven 工程配置文件,声明了项目依赖和打包方式
src/mainJava 源代码目录,里面按包名组织各类、接口和工具类
logo.png、bg.jpg界面中使用的图标和背景图,放在资源目录下供界面加载
G%U6ZRK3J]]EWEEUW3_4QYO.png一个文件名较奇怪的图片资源,多半是界面截图或图标素材
README.md项目说明文档,一般会写运行环境、启动步骤和注意事项
.idea、vcs.xmlIDEA 集成本地配置,不影响代码运行,忽略即可

这份资源的核心是两样东西:src/main 下的 Java 源码,以及数据库创建脚本.sql。前者决定系统长什么样、能做什么操作;后者决定数据能不能安稳落盘。理解了这两条线,剩下的图片资源、README 不过是辅助。

有一点值得注意:pom.xml 的存在说明这是一个 Maven 管理的项目,而不是随手把 jar 包塞进 lib 目录的传统课设。Maven 的好处是依赖统一从中央仓库拉取,换一台电脑也能快速复现环境,这一点对你之后在实验室、宿舍、答辩教室来回切换机器非常友好。

2.2 Maven 依赖:为什么 pom.xml 决定了你能不能跑起来

很多新手第一次导入 Maven 项目时,习惯性忽略 pom.xml,以为只要把 src/main 下的代码复制到一个新工程里就能跑。结果编译时飘红一片,几乎都在报「程序包不存在」,原因就是缺了依赖。Maven 的核心逻辑是:你在 pom.xml 里声明我需要哪些库,它自动帮你下载并放进 classpath。

这种课设项目的 pom.xml 通常不会太复杂,核心就是要有一个 MySQL 驱动依赖。常见写法大概长这样:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.library</groupId> <artifactId>library-system</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <!-- MySQL 驱动,8.x 对应 MySQL 8,5.x 请换成 5.1.49 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies> </project>

这里的核心参数是<groupId>、<artifactId>、<version>,三者联合定位一个唯一的 jar 包版本。如果你本机 MySQL 是 5.7 或更早,驱动版本建议换成 5.1.49 附近;如果是 MySQL 8.0,用 8.0.x 的驱动就对了。mysql-connector-java 8.x 在 8.0.31 之后已经把旧版包名 com.mysql.jdbc.Driver 移除了,只保留 com.mysql.cj.jdbc.Driver,这块我放到第 5 章再展开。

注意:IDEA 导入 Maven 项目后,第一次会自动下载依赖。如果网络慢,右下角会一直转圈,这时候不要急着关 IDEA,否则依赖缓存不完整,后面会报各种莫名其妙的缺类错误。

2.3 导入 IDEA 的两种姿势与启动入口

拿到压缩包,第一步是解压。Windows 上直接右键解压,或者用命令行也可以:

unzip java图书管理系统(GUI)图形界面版.zip -d library-system cd library-system ls -la pom.xml src/main README.md

解压后进入目录,确认 pom.xml 和 src/main 都在。然后用 IDEA 打开工程:File → Open,选中 pom.xml 文件,IDEA 会弹出一个提示,问你是否 Open as Project,选「Open as Project」即可。如果你是第一次用 IDEA,可能需要设置一下项目的 JDK:File → Project Structure → Project → SDK,选择 JDK 8 或 JDK 11,具体看源码里的语法兼容程度。

导入完成后,找到带main方法的类,它通常是登录窗口或主界面的启动类。一个典型的 Java Swing 程序入口是这样写的:

public class MainApp { public static void main(String[] args) { // 先设置系统外观,让 Swing 窗口更接近当前操作系统的原生风格 try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } // 启动登录窗口 new LoginFrame().setVisible(true); } }

设置 LookAndFeel 这步是很多课设容易漏掉的地方。默认的 Metal 风格窗口比较老旧,换成系统风格之后,按钮和文本框会顺眼不少。而new LoginFrame().setVisible(true)里的 LoginFrame 是登录窗体的类名,不同项目可能叫 LoginUI、LoginDialog,你只要在 src/main 里找一个继承了 JFrame 的类就行。

严格来说,Swing 程序里 IO 和 UI 操作应该放在事件分发线程上,标准做法是:

SwingUtilities.invokeLater(() -> new LoginFrame().setVisible(true));

不过很多课程设计都直接在 main 里 new,简单场景下也能跑,不会出大问题。如果你想把项目做规范一点,建议用 invokeLater 包一层,答辩时加分。

2.4 分模块看代码:UI、DAO、DBUtil 各司其职

把源码目录展开,你会发现一个完整的课设通常按职责分三层:界面层、数据访问层、工具层。界面层以 Frame、Panel 结尾,负责画窗口、摆控件、响应用户操作;数据访问层以 Dao 或 DAO 结尾,负责把 SQL 发给数据库;DBUtil 是工具类,专门管理数据库连接。

为什么课设也要分包?因为如果你把 SQL 和界面代码写在一个类里,比如在按钮的 actionPerformed 里直接拼 SQL,代码会越写越烂,后面想加一个「归还图书」功能,就得在界面类里前后翻几百行。分层之后,界面只管事件,DAO 只管增删改查,DBUtil 只管连接。这个习惯在校招面试里也是考官爱问的点。

举个例子,最简单的 DBUtil 打开连接的方式是这样:

Connection conn = DBUtil.getConnection();

这句代码在两个层里都会出现:DAO 层拿它执行 SQL,界面层有时候也会直接拿它跑个简单查询。但要注意,连接对象不能泄漏,用完必须关闭。推荐用 try-with-resources 语法,让 JVM 自动关闭资源,这个在第 3 章里会给出完整写法。

3. 数据库脚本先跑通:建表语句、JDBC 连接与中文编码的三场硬仗

3.1 数据库脚本:先读懂再执行

数据库创建脚本.sql 是整个系统的地基。如果你拿到手直接执行,大概率会遇到一个经典问题:脚本里可能包含建库语句,也可能不含;可能指定了 utf8mb4,也可能默认字符集。在跑之前,先用文本编辑器打开它,重点确认三件事:数据库名、字符集、表之间的外键关系。

一个典型的图书管理系统脚本会包含三张核心表:图书表、用户表、借阅记录表。表结构大致是这样的:

CREATE DATABASE IF NOT EXISTS db_library DEFAULT CHARSET utf8mb4; USE db_library; CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), price DECIMAL(10,2), stock INT DEFAULT 1, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT 'student' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_borrow ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME NULL, status TINYINT DEFAULT 0, FOREIGN KEY (book_id) REFERENCES t_book(id), FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

三张表的关键参数值得细看。book 表的 stock 字段是库存量,借阅时必须检查它大于 0,否则不能出借。borrow 表的 status 字段用 0 和 1 区分借出和已还,这个字段是整个借还流程的状态机。t_borrow 对 t_book、t_user 建立外键,能保证借阅记录不会指向一个不存在的图书或用户,但也带来了插入顺序的约束:必须先有图书和用户,才能插入借阅记录。

字符集方面,建库建表都选了 utf8mb4,而不是历史课程里常见的 latin1 或 gbk。utf8mb4 是 UTF-8 的超集,支持中文、特殊符号和 emoji,录入「《数据结构(C语言版)》」这类夹杂括号、中文的标题才不会乱码。

3.2 执行脚本的两种方式

执行脚本最简单的方式是命令行:

mysql -u root -p < 数据库创建脚本.sql

输入命令后回车,会提示输入 root 用户的密码。如果脚本里的建库语句带了IF NOT EXISTS,重复执行也不会报错。这行命令会把整个 SQL 文件按顺序逐条执行,建库、建表、插入初始化数据一次完成。

如果你平时习惯用图形化管理工具,也可以用 Navicat 或 MySQL Workbench:新建连接,打开数据库,然后直接把脚本内容粘贴到查询窗口,点击运行。这两条路线都可行,命令行更原汁原味,图形工具更直观。我一般会先用命令行跑一遍,再用 Navicat 刷新看看表结构,确认表确实创建成功。

3.3 JDBC 连接:DBUtil 的参数决定你能连上哪台数据库

Java 程序写好后,真正到连接数据库时,代码里的连接参数会成为第一个坑。DBUtil 类一般长这样:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/db_library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL 驱动加载失败,请检查 pom.xml 依赖", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

这个类里的 URL 参数是重点。localhost:3306表示本机 MySQL,端口 3306 是 MySQL 默认端口;db_library是要连接的数据库名,必须和脚本里的库名一致。useSSL=false是为了避免本地连接时 SSL 握手产生额外校验;serverTimezone=Asia/Shanghai解决 MySQL 8 版本的时区报错;characterEncoding=utf8保证写入数据库的中文不乱码。USER 和 PASSWORD 分别改成你本机的 MySQL 账号密码,注意一定不要用默认的 123456 硬编码到正式项目里,但课设阶段为了方便可以接受。

关于驱动类名,MySQL 5.x 配套的驱动类名是com.mysql.jdbc.Driver,MySQL 8.x 是com.mysql.cj.jdbc.Driver。如果你用的是 MySQL 8 但 pom 里还是旧驱动,运行时会直接报ClassNotFoundException。解决方式就是保持驱动版本和数据库版本一致,这个在第 5 章排查里会再展开。

3.4 用一条 SQL 验证数据库连接

写完 DBUtil 后,先别急着启动整个 GUI。写一个最简查询类,确认 JDBC 链路通不通。这是排查定位最快的方法,能直接把问题缩小到「数据库连接」还是「界面代码」:

public class DBTest { public static void main(String[] args) { String sql = "SELECT COUNT(*) FROM t_book"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { if (rs.next()) { System.out.println("图书总数: " + rs.getInt(1)); } } catch (Exception e) { e.printStackTrace(); } } }

这段代码用 try-with-resources 把 Connection、Statement、ResultSet 三个资源都放在 try 括号里,方法结束后 JVM 会自动关闭它们,不需要写 finally 块手动关闭。executeQuery执行查询,rs.getInt(1)取第一列。如果控制台能打印出图书总数,说明连接参数、驱动、库名都没问题,可以大胆进入界面层调试;如果抛异常,堆栈信息会直接告诉你问题出在哪一层。

提示:遇到「Unknown database 'db_library'」就说明脚本没执行成功;遇到「Access denied for user」就是密码不对;遇到「Communications link failure」就检查 MySQL 服务是否启动。把这些常见报错记下来,第 5 章会按现象排查。

4. 核心模块实现:入库、借阅、归还与 JTable 刷新的关键代码

4.1 图书入库:表单校验 + PreparedStatement 防注入

图书入库模块的表单一般包含书名、作者、出版社、价格、库存几个输入框。点「保存」按钮后,第一件事不是执行 SQL,而是校验输入合法性。书名不能为空,价格必须是非负数,库存必须是整数。这些校验可以放在按钮回调里,也可以写一个 validate 方法。完成校验后,再把 Book 对象传给 DAO 层。

DAO 层的代码逻辑并不复杂,关键是怎么把数据写进数据库。最稳妥的方式是用 PreparedStatement:

public boolean addBook(Book book) { String sql = "INSERT INTO t_book(book_name, author, publisher, price, stock) VALUES(?,?,?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, book.getBookName()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getPublisher()); ps.setBigDecimal(4, book.getPrice()); ps.setInt(5, book.getStock()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

这里用了五个?占位符,分别对应 INSERT 语句里的五个字段。执行前通过 setString、setBigDecimal、setInt 把 Java 对象的属性绑定到占位符上。这样写有两个好处:第一,占位符机制自动处理特殊字符,书名里带单引号、双引号也不会破坏 SQL 语法;第二,PreparedStatement 有预编译机制,同一 SQL 结构在多次执行时效率更高。很多课设喜欢用 Statement 加字符串拼接:

Statement stmt = conn.createStatement(); stmt.execute("INSERT INTO t_book VALUES(" + bookName + ")");

这种写法在输入内容包含单引号时必炸,而且存在 SQL 注入风险。如果你在代码里看到这类写法,建议当场改成 PreparedStatement,答辩时老师大概率会盯着这个点问。

4.2 借阅与归还:事务是防止数据错乱的最后防线

借阅图书不是一条 SQL 能完成的。它至少涉及两步:在 t_borrow 表插入一条借阅记录,同时把 t_book 表的 stock 减一。如果第一步成功、第二步失败,那就会出现数据错乱。A 借了书,库存却没减少,下一次还能继续借,这显然不对。

所以要引入事务。事务的本质是让一组操作要么全部成功,要么全部回滚。借阅方法的完整写法是这样:

public boolean borrowBook(int bookId, int userId) { String checkSql = "SELECT stock FROM t_book WHERE id = ? FOR UPDATE"; String insertSql = "INSERT INTO t_borrow(book_id, user_id, status) VALUES(?,?,0)"; String updateSql = "UPDATE t_book SET stock = stock - 1 WHERE id = ? AND stock > 0"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 查询当前库存,FOR UPDATE 在事务里锁定这行数据 try (PreparedStatement ps = conn.prepareStatement(checkSql)) { ps.setInt(1, bookId); ResultSet rs = ps.executeQuery(); if (rs.next() && rs.getInt("stock") <= 0) { conn.rollback(); return false; } } try (PreparedStatement ps1 = conn.prepareStatement(insertSql); PreparedStatement ps2 = conn.prepareStatement(updateSql)) { ps1.setInt(1, bookId); ps1.setInt(2, userId); ps1.executeUpdate(); ps2.setInt(1, bookId); ps2.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ignored) { } e.printStackTrace(); return false; } finally { try { if (conn != null) conn.setAutoCommit(true); } catch (SQLException ignored) { } } }

这段代码有三个关键点。第一个是conn.setAutoCommit(false),它让连接不自动提交每一条 SQL,而是等到conn.commit()才统一提交;第二个是SELECT ... FOR UPDATE,它在事务里锁住图书行,防止两个人同时借同一本库存只剩 1 的书;第三个是 catch 块里调rollback(),一旦 insert 或 update 失败,把之前已经执行成功的操作全部撤销。

归还的逻辑正好是反过来的:更新 t_borrow 表的 status 为 1,并且写入 return_time,再把 t_book 表的 stock 加一。注意这里也要放在同一个事务里,否则会出现「还书记录写进去了,库存没加回来」的尴尬情况。还有一个小细节:归还时要确认 status 是 0,如果一本书已经还了又被还一次,status 就会从 1 变成 1,虽然没报错,但逻辑上说不通。

4.3 查询与 JTable:列表为什么卡在旧数据上

查询模块是整个系统里最常用的功能。图书列表用 JTable 展示时,核心操作是:拿到查询结果集,逐行往 TableModel 里塞数据,然后让表格刷新。常见的错误是查询完只往 model 里 addRow,却没有清空旧数据,结果越点查询越多,页面出现重复行。

正确的刷新逻辑是先清空再填充:

private void loadBookTable(String keyword) { String sql = "SELECT id, book_name, author, publisher, price, stock FROM t_book"; boolean hasKeyword = keyword != null && !keyword.trim().isEmpty(); if (hasKeyword) { sql += " WHERE book_name LIKE ?"; } sql += " ORDER BY id"; DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); // 清空旧数据 try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (hasKeyword) { ps.setString(1, "%" + keyword + "%"); } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Object[] row = { rs.getInt("id"), rs.getString("book_name"), rs.getString("author"), rs.getString("publisher"), rs.getBigDecimal("price"), rs.getInt("stock") }; model.addRow(row); } } } catch (SQLException e) { e.printStackTrace(); } }

model.setRowCount(0)是这段代码的灵魂,它把表格模型里已有的行全部清空,确保每次加载都是最新的数据。LIKE ?配合%通配符实现模糊搜索,用户输入「数据」两个字,就能查出「数据结构」「数据库原理」等包含关键词的书名。注意这里仍然用 PreparedStatement 的占位符,而不是把 keyword 直接拼进 SQL,就是为了防注入。

JTable 的渲染有个常见误区:直接把 ResultSet 数据塞进 JTable 的构造方法,而不是通过 TableModel。那样确实能显示一次数据,但后续刷新困难,而且类型绑定不灵活。课设中建议始终用 DefaultTableModel 管理数据,刷新时只需要 setRowCount(0) 再 addRow,操作简单又不容易出毛病。

5. 避坑与排查:连不上数据库、中文乱码、按钮没反应的 5 个翻车点

5.1 ClassNotFoundException: com.mysql.cj.jdbc.Driver

现象:启动程序后控制台抛java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,程序直接退出。

原因:这个异常代表 JVM 在 classpath 里找不到 MySQL 驱动类。最常见的情况是 pom.xml 里压根没写 MySQL 依赖,或者写了但 IDEA 还没刷新完 Maven 依赖。

解决:先打开 pom.xml,确认有没有 mysql-connector-java 依赖。没有就补上;有的话,在 IDEA 右侧 Maven 面板点一下刷新按钮,让依赖重新解析。如果项目不是 Maven 工程,就需要手动把驱动 jar 包放到 build path 里。我一般还会检查一个细节:驱动类名是不是对应了正确的 MySQL 版本,MySQL 5.x 用com.mysql.jdbc.Driver,MySQL 8.x 用com.mysql.cj.jdbc.Driver,用反了也会报这个异常。

5.2 数据库里中文全是问号

现象:执行图书入库后,打开数据库表,看到的中文字段变成一堆??,或者查询到的是乱码。

原因:字符集链路上的三环没对上。第一,数据库表字符集可能不是 utf8mb4;第二,JDBC 连接 URL 里没加characterEncoding=utf8;第三,SQL 脚本文件本身的编码和数据库字符集不一致,导致数据写入前就已经乱掉。

解决:从三个维度统一编码。建表时显式指定DEFAULT CHARSET=utf8mb4;JDBC URL 加上characterEncoding=utf8;IDE 里把 java 文件和 sql 文件设为 UTF-8 编码。检查顺序是先看连接 URL,再看建表语句,最后确认数据库表的实际字符集。用SHOW CREATE TABLE t_book;能直接看到表当前的字符集定义。

5.3 点击按钮没反应,控制台也没任何输出

现象:界面正常弹出,按钮也可以点击,但点了以后什么事情都没发生,不报错也不跳转。

原因:这类问题通常不在数据库层,而是事件监听器没有正确挂载,或者监听器内部异常被吞掉。很多同学写了btn.addActionListener(this),但当前类没有实现 ActionListener 接口,也没有重写 actionPerformed,那按钮自然形同虚设。另一种情况是 actionPerformed 里执行了数据库操作,但 catch 块只写e.printStackTrace(),而且被 IDE 折叠起来了,你根本看不到堆栈信息。

解决:先在按钮回调的第一行加一个System.out.println("clicked"),确认事件有没有到达处理器。如果输出了「clicked」,说明监听器没问题,问题在后续业务代码;如果没输出,检查监听器挂载和实现类。还有一个实用的技巧:在 actionPerformed 的 catch 块里用throw new RuntimeException(e)替代e.printStackTrace(),让异常直接打到控制台,这样才能看到真实原因。

5.4 JTable 一片空白,数据查得到但表格不显示

现象:数据库里有数据,DAO 层查询也没报错,但 JTable 界面上就是什么都不显示,或者只显示上一次的数据,新增的记录看不到。

原因:两个典型原因。第一,查询方法执行了数据库查询,但没有把 ResultSet 中的数据 addRow 到 TableModel;第二,刷新时没有调用setRowCount(0),旧数据还在表里,查出来的新数据被追加在后面,看起来像是没更新。

解决:检查 loadBookTable 方法里有没有model.addRow(...),没有就补上。刷新逻辑统一用「先 setRowCount(0) 再 addRow」的模式。还有一个容易被忽略的点:DefaultTableModel 的构造方法如果指定了列名和列数,addRow 时数组的长度必须跟列数一致,否则会抛出IllegalArgumentException: Array length must equal column count。

5.5 插入借阅记录报外键约束失败

现象:在借阅界面选择图书和用户后,点击「借阅」,控制台报Cannot add or update a child row: a foreign key constraint fails。

原因:t_borrow 表依据外键关联到 t_book 和 t_user,当插入记录时,book_id 或 user_id 指向的记录在对应主表中不存在,数据库就会拒绝写入。最常见的情况是:图书或用户已经删除,但界面的下拉框还缓存着旧数据。

解决:借阅前先刷新图书下拉框和用户下拉框,确保选中的 id 在主表里真实存在。如果手工测试时删过数据,重新打开系统再测试。另一种情况是脚本里两张主表数据没有初始化,t_user 里只有管理员账号,普通用户还没录入,借阅时选到不存在的用户自然报错。先往 t_user 表插入几条测试用户,再跑借阅流程就正常了。

6. 进阶与验证:借阅数据一致性检查与可执行 jar 的输出技巧

课设做到「能跑」只是及格线,真正拉开差距的是「能讲清楚」。演示的时候,老师不会只盯着界面看,他更关心你点一下「借阅」之后,数据库里发生了什么。我习惯用一个验证 SQL 来演示借阅流程的完整性:

SELECT b.book_name, u.username, br.borrow_time, br.return_time, br.status FROM t_borrow br JOIN t_book b ON br.book_id = b.id JOIN t_user u ON br.user_id = u.id ORDER BY br.borrow_time DESC;

这条 SQL 把三张表用 JOIN 连起来,能直接看到「谁借了哪本书、什么时候借的、还了没有」。演示时,你借一本书,再执行这条 SQL,如果新记录出现且 status 是 0,接着归还,status 变成 1,数据库层面的完整闭环就被你证明了。这个验证习惯比你在台上背代码要有说服力得多。

第二个进阶技巧是打包。课设演示现场往往没有 IDEA,或者投影仪电脑上没装 JDBC 驱动,提前打成可执行 jar 是最稳妥的方案。IDEA 里通过 File → Project Structure → Artifacts → + → JAR → From modules with dependencies,选主类,构建后就能生成一个包含所有依赖的完整 jar。双击运行的前提是机器装了 JDK,如果没有,再考虑用 exe4j 包一层原生可执行文件。

最后一个习惯是清理代码。课设源码里经常残留测试类、无用的 main 方法、多余的系统输出。交作业之前,我会把测试类删掉,把 System.out.println 改成日志或直接去掉,再将硬编码的数据库密码提取到配置文件。这些小动作不影响功能,却能让代码读起来像一个「产品」而不是「草稿」。

我以前交课设时,界面能弹出来就以为稳了,结果老师打开数据库往里一查,发现借阅表里全是脏数据,当场翻车。从那以后,我每次做这类桌面系统,都会强制走一遍「操作 → 查库 → 核对状态」的闭环,把每一步背后到底多了哪条记录当成验收标准。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 23:41:35

t3code:T3 Stack驱动的CLI+Electron一体化开发工具生态

1. “t3code”不是工具名&#xff0c;而是开发者社区里一个正在成型的CLI生态代号最近两周&#xff0c;我在几个前端技术群和Electron开发者论坛里反复看到“t3code”这个词——它既没出现在npm registry的包列表里&#xff0c;也没在GitHub上搜到对应仓库。但有趣的是&#xf…

作者头像 李华
网站建设 2026/10/26 23:40:32

t3code:轻量级跨端构建系统,一套Web代码三端部署

1. 项目概述&#xff1a;t3code 是什么&#xff0c;它解决的到底是什么问题&#xff1f;t3code 这个名字乍一看像某个开源工具、CLI 命令行套件&#xff0c;或是某款跨平台开发框架的代号——但它既不是 npm 上广为人知的包&#xff0c;也不是 GitHub trending 榜单里的明星项目…

作者头像 李华
网站建设 2026/10/26 23:39:46

Claude Code Mods 实战:用 TypeScript 扩展终端智能体

1. 从终端里长出来的“外挂”&#xff1a;Claude Code Mods 到底在解决什么问题第一次听到 Claude Code Mods 这个词&#xff0c;很多人会下意识把它当成某种插件市场里的扩展包。其实不是。它更像是一套“给 Claude Code 这个终端里的编程助手加装工具、改界面、换交互方式”的…

作者头像 李华
网站建设 2026/10/26 23:39:44

数字身份资产管理:Token与MCP全生命周期安全实践

1. 数字身份资产管理的核心痛点在AI驱动的现代开发团队中&#xff0c;Token、MCP&#xff08;Message Control Protocol&#xff09;和各种访问规则构成了数字身份资产的核心要素。这些要素本质上属于组织资产而非个人资产&#xff0c;但在实际操作中却经常出现"人走资产丢…

作者头像 李华
网站建设 2026/10/26 23:39:04

Python+Django构建动物领养平台:毕业设计实战指南

1. 项目概述这个基于Python的动物领养平台是我去年完成的本科毕业设计项目&#xff0c;核心目标是搭建一个连接流浪动物救助站与潜在领养者的在线平台。整个系统采用PythonDjango框架开发&#xff0c;前后端分离设计&#xff0c;包含完整的用户管理、动物信息展示、领养申请流程…

作者头像 李华
网站建设 2026/10/26 23:38:17

Qt+Snap7实现S7-1200 PLC上位机DB块批量读写与断线重连

简介&#xff1a;面向Qt桌面开发与工业自动化方向的学习者&#xff0c;这份压缩包给出一个可参考的西门子PLC通信示例&#xff0c;重点演示在Qt工程中接入Snap7库、连接S7系列PLC并完成数据读写的基本做法&#xff0c;适合具备一定C与Qt基础、希望把上位机界面与PLC通信打通的开…

作者头像 李华