简介:这是一份基于 Java+MySQL 实现的 GUI 图书管理系统课程设计资源,覆盖管理员登录、图书管理、用户检索、借阅归还与图书查找等核心模块,适合 Java 初学者、数据库课程设计学生及需要参考完整项目源码的开发者。压缩包共 187 个文件,主要包含 48 个 Java 源码、64 个编译后的 class 文件、58 个 GUI 运行截图 png 图片,以及 4 个 SQL 建库脚本、多份 docx/doc 说明文档和 md 笔记,整体大小 16.25MB,结构清晰便于按源码、脚本、文档分类查阅。已有 999 人学习下载。资源内含 Swing 界面设计与 JDBC 操作 MySQL 的完整实现,可学习面向对象分层、增删改查 SQL 编写、借阅状态跟踪与管理员权限验证等关键知识点;配套的数据库脚本和运行截图能帮助快速搭建环境并理解业务流程,非常适合课程设计答辩或项目二次开发参考。
1. 期末课设周里,为什么这个“老掉牙”的 Java+MySQL+GUI 组合还值得做
期末课设周,十个 Java 课程设计案例源码里至少五个是图书管理系统,这组合看起来确实老。但把 Java+MySQL+GUI 这三个词真正串起来——从 JDBC 连接串到 Swing 事件模型,再到借还书的事务边界——你对 Java 数据库编程的基础理解,会比背一百道 java 基础面试题都扎实。这个系统解决的事很具体:图书信息录入、库存管理、借书还书登记、逾期状态查看。适合三类人:正在做课程设计的学生、想用最小成本补 JDBC 和 Swing 基础的初学者、以及面试前想快速把一个 CRUD 项目整理成能讲清设计亮点的开发者。它不是什么高深架构,但它是理解 Java 后端怎么跟数据库打交道的最短路径。
2. 先把架子搭对:技术选型、工程结构与图书表设计
2.1 选型:为什么是 Swing 而不是 JavaFX,为什么不用 MyBatis
Swing 看起来是老古董,但它在 JDK 里原生存在,只要有 JDK 就能编译运行,这对课设环境非常友好。JavaFX 从 JDK 11 开始不再随 JDK 一起发布,需要单独下载 SDK,很多机房和线上环境装起来麻烦,而且版本和 JDK 的匹配经常出玄学问题。你想想,演示前一天发现 JavaFX 环境起不来,那种翻车没人想经历。
持久层这边,常见做法是裸 JDBC + PreparedStatement,而不是 MyBatis。图书管理系统就是标准 CRUD,数据量几百条,MyBatis 的 XML 配置和 resultMap 映射对新手是一个黑匣子,一旦查出来的字段对不上,报错信息又长又难懂。裸 JDBC 虽然样板代码多,但每一步都在你掌控里,出问题能用打印 SQL 的方式直接定位。等你这套跑通了,再去学 MyBatis 会发现无非是把这些样板代码换成了框架标签而已。
数据库选 MySQL 是共识:免费安装、教程多、机房环境默认就有。有一个细节在 MySQL 安装教程里经常被忽略,那就是字符集一定要在安装时选成 utf8mb4,而不是默认的 latin1,否则后面中文乱码会让你排查到怀疑人生。GUI 框架也一样,别用 IDEA 的图形界面拖拽插件去生成窗体,那个生成的代码嵌套监听器,报错信息根本读不懂;手写 Swing 虽然慢一点,但每一行都知道在干什么。
2.2 表设计:三张表还是五张表,外键要不要加
图书管理系统核心就三个实体:用户、图书、借阅记录。很多课设会加还书表、罚款表、类别表,但对一个图书管理系统来说,三张表是底线,五张表是加分项。我一般建议先做三张表,跑通了再拆类别表。表设计直接决定后面 GUI 的代码量,表拆得太细,JTable 关联查询会让新手崩溃;表太粗,比如把借书和还书记录混在一张表里,状态字段又会写得一团糟。
外键这个事要单独说。课设题目一般要求外键约束,所以我建议加,但要知道代价:加了外键之后,删除图书或用户时,必须先删干净引用它的借阅记录,否则 MySQL 会报外键约束错误。这个约束在课设演示时反而是好事,至少不会出现孤儿数据。另一个容易被扣分的是库存字段,图书表里必须有总库存和可借库存两个字段,可借库存默认值为 0,这是 mysql 设置默认值为 0 的典型场景,建表时直接写在 DEFAULT 里,比每次插入都手动赋值靠谱。
下面是建表脚本,注意看字段类型和注释:
CREATE DATABASE IF NOT EXISTS library_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE library_system; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 'MD5 摘要,不存明文', real_name VARCHAR(50) NOT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT '0-管理员 1-读者', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), isbn VARCHAR(20), total_stock INT NOT NULL DEFAULT 0, available_stock INT NOT NULL DEFAULT 0 COMMENT '可借库存,减到 0 表示不可借', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_book_name (book_name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, due_time DATETIME NOT NULL, return_time DATETIME, status TINYINT NOT NULL DEFAULT 0 COMMENT '0-在借 1-已还', CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES t_book(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这个脚本里有三处值得讲。第一,所有表都用 InnoDB 引擎,因为 InnoDB 支持事务和外键,MyISAM 虽然查询快但不支持事务,借书扣库存这种操作没有事务就会出大问题。第二,password 字段只存 MD5 摘要,哪怕课设也要有这个习惯,否则评审问一句“密码明文存的吗”就答不上来。第三,t_book 的 book_name 加了普通索引,后面 GUI 做模糊搜索时,LIKE 查询能走索引,数据量大了也不会卡。
2.3 工程骨架:Maven 目录与启动入口
工程结构用 Maven 管理依赖,这是现在 Javaweb 项目完整案例的标准姿势。不要用裸导入 jar 包的方式,太容易漏依赖。Maven 结构的好处是 pom.xml 一写,MySQL 驱动自动下载,不用自己去官网找 jar 包。注意 mysql-connector-java 的坐标在 Maven 中央仓库里是真实存在的,但版本号要跟你本机 MySQL 版本匹配,MySQL 8 就用 8.0.x 的驱动,别用 5.x 的旧驱动去连 MySQL 8。
<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.example</groupId> <artifactId>library-system</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> </dependencies> </project>这段 pom 里最关键的是 mysql-connector-j 的 scope 设为 runtime,意思是编译时不依赖它,运行时才需要。这样写的好处是,代码里用 Class.forName 加载驱动时不会出现编译期报错,而打包时驱动又会被带进去。如果你发现依赖下载不下来,先检查 Maven 镜像源是不是默认的中央仓库被墙了,换成阿里云镜像一般就能解决。目录结构上,按 com.example.library 分包:dao 包放数据库访问、ui 包放 Swing 界面、util 包放 JDBC 工具类和 MD5 工具类、entity 包放 User 和 Book 实体。启动入口就一个 Main.java,里面 new 出登录窗口。
3. 从零搭数据库:建库脚本、JDBC 连接与连接池参数
3.1 建库建表:用 Workbench 导入脚本的三个常见问题
建库脚本写好了,下一步是把它跑起来。两种方式:命令行 mysql -u root -p 进入后 source 执行,或者用 MySQL Workbench 直接粘贴执行。mysql workbench 使用教程里最常用到的就是这个场景:打开 SQL 编辑器,把上面的脚本整个粘进去,点执行按钮。但这一步有三个常见坑,先给你排掉。
第一个坑是执行顺序。如果脚本里有外键,必须先建被引用的表(t_user、t_book),再建引用表(t_borrow_record),顺序反了会报外键关联失败。上面的脚本已经把顺序排好,直接整段执行没问题。第二个坑是重复执行。第二次跑同一段脚本,CREATE TABLE 不带 IF NOT EXISTS 会报表已存在,所以我脚本里每个 CREATE TABLE 都带了 IF NOT EXISTS,这是课设里最容易忽视的细节。第三个坑是字符集不一致。Workbench 默认连接字符集可能是 utf8,但表是 utf8mb4,查询结果里如果有 emoji 字符会乱码,连接时在 Workbench 的 Connection 配置里把 Character Set 改成 utf8mb4 就行。
表建完之后,先用一句 SQL 验证一下,顺便插入一条测试数据:
INSERT INTO t_user (username, password, real_name, role) VALUES ('admin', MD5('123456'), '系统管理员', 0); INSERT INTO t_book (book_name, author, publisher, isbn, total_stock, available_stock) VALUES ('Java核心技术 卷I', '凯.S.霍斯特曼', '机械工业出版社', '9787111612711', 10, 10); SELECT * FROM t_user; SELECT * FROM t_book;这里的 MD5('123456') 是 MySQL 内置函数,直接生成密码摘要,这样后面 Java 端登录时也是把用户输入的密码做 MD5 再比对,两端算法一致。插入图书时 total_stock 和 available_stock 都写成 10,表示总库存 10 本、可借 10 本。验证查询用 SELECT * 就行,课设阶段不用讲究字段列表。
3.2 JDBC 连接工具类:Class.forName 还需要吗,URL 参数怎么配
建好库和表,接下来是 Java 端连数据库。JDBC 连接这一层,我建议写一个独立的工具类,把所有连接相关的配置集中管理。这个工具的写法在 JDK 8 和 JDK 11 下有个小差异:JDK 8 里 Class.forName 加载驱动是必须的,JDK 6 之后虽然 DriverManager 会自动加载 classpath 下的驱动 jar,但显式写 Class.forName 能保证 classpath 扫描失败的场景也能加载。JDK 11 和更高版本里,SPI 机制已经非常成熟,这行代码可写可不写,但写了不会报错,建议保留,因为这行代码在很多 java 基础面试题里都会被问到。
package com.example.library.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class JdbcUtil { private static final String URL = "jdbc:mysql://127.0.0.1:3306/library_system" + "?useSSL=false" + "&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai" + "&allowPublicKeyRetrieval=true"; 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 ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码有四个参数必须解释清楚。useSSL=false 是关掉 SSL 握手,MySQL 8 默认开 SSL,不关的话连接时会多一次握手,性能差且容易报 SSL 相关的错。characterEncoding=utf8 是告诉驱动用 UTF-8 编码传输,不写这个,中文插入数据库就是一堆问号。serverTimezone=Asia/Shanghai 解决 MySQL 8 的时区问题,不写这个会报 CST 时区无法识别的错误。allowPublicKeyRetrieval=true 配合 MySQL 8 默认的 caching_sha2_password 认证插件,不写的话首次连接可能报公钥检索失败。这四个参数是 MySQL 8 环境下 JDBC 连接的标配,一个都不能少。
3.3 连接池还是裸连接:参数怎么设不翻车
课设规模下直接用 DriverManager 完全够,但如果想让项目有点深度,把连接池引进来是一个很好的加分项。连接池的作用是复用连接,避免每次查数据库都新建一条 TCP 连接。图书管理系统的并发量不高,但面试官问起连接池,你要能说清楚参数。常见做法是用 HikariCP,它是 Spring Boot 默认连接池,轻量且性能好。
| 参数 | 建议值 | 说明 |
|---|---|---|
| maximumPoolSize | 10 | 池中最大连接数,Swing 单机应用 10 足够 |
| minimumIdle | 2 | 最小空闲连接,低于这个值会补新连接 |
| connectionTimeout | 30000 | 获取连接超时时间(毫秒),默认 30 秒 |
| idleTimeout | 600000 | 空闲连接存活时间(毫秒),超过且大于 minimumIdle 则回收 |
| maxLifetime | 1800000 | 连接最大生命周期(毫秒),防止数据库侧断开复用连接 |
参数看起来多,但对图书管理系统只需要关注 maximumPoolSize 和 connectionTimeout。池太小,连续快速借书还书时会排队;池太大,空闲连接占内存。10 和 30000 是一组比较稳的搭配。注意 maxLifetime 一定要设,MySQL 侧有 wait_timeout 默认 8 小时,连接池里的连接长时间不用会被 MySQL 断开,HikariCP 如果没有 maxLifetime,拿到被断开的连接会报错。
引入 HikariCP 只需在 pom.xml 加一个依赖,然后把 JdbcUtil 里的 getConnection 改成从 HikariDataSource 获取即可。但这步是可选的,如果你觉得课设时间紧,直接保留 DriverManager 版本,在第 6 章我再讲怎么在答辩时把连接池的事说成“预留的优化点”。
4. 把 GUI 跑起来:登录、图书列表与借还书的核心代码
4.1 登录窗口:密码校验和防注入的底线写法
登录是 GUI 的入口,也是第一个跟数据库打交道的窗口。这里的坑主要在两点:一是密码传输用 JPasswordField 的 getPassword() 而不是 getText(),前者返回 char[],用完之后可以手工置空,降低内存被读取的风险;二是查询语句必须用 PreparedStatement 拼参数,不能把用户输入直接拼进 SQL 字符串,那是 SQL 注入的经典入口。课设里用 Statement 拼串被评审问住的情况非常多。
JButton loginBtn = new JButton("登录"); loginBtn.addActionListener(e -> { String username = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(loginFrame, "用户名和密码不能为空"); return; } String sql = "SELECT id, real_name, role FROM t_user WHERE username = ? AND password = ?"; try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, Md5Util.md5(password)); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { int id = rs.getInt("id"); String realName = rs.getString("real_name"); int role = rs.getInt("role"); loginFrame.dispose(); new MainFrame(id, realName, role).setVisible(true); } else { JOptionPane.showMessageDialog(loginFrame, "用户名或密码错误"); } } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(loginFrame, "数据库连接失败,请检查 MySQL 服务"); } });这段代码的关键在 PreparedStatement。占位符 ? 由驱动转义后再发给 MySQL,用户输入哪怕包含引号和分号,也只会被当作普通字符串,从根本上杜绝了拼接注入。Md5Util.md5() 是工具类方法,把密码字符串转成 32 位十六进制摘要,跟数据库里存的值比对。注意捕获 SQLException 后一定弹窗提示,不能让用户点了登录按钮什么都没发生,这是 Swing 程序体验感的底线。
4.2 图书列表:JTable 绑定 DefaultTableModel 的刷新机制
登录进去之后的主界面,核心是一个 JTable,用来展示图书列表。这里最容易踩的坑是刷新问题:查到的新数据不会自动出现在表格里,必须手动操作数据模型。JTable 本身只负责画格子,数据放在 DefaultTableModel 里,所以刷新的正确姿势是清空 model 里的旧数据,再逐行 addRow 新数据。
private DefaultTableModel model = new DefaultTableModel( new String[]{"编号", "书名", "作者", "出版社", "ISBN", "总库存", "可借"}, 0); private JTable table = new JTable(model); private void loadBooks(String keyword) { model.setRowCount(0); String sql = "SELECT id, book_name, author, publisher, isbn, total_stock, available_stock " + "FROM t_book"; if (keyword != null && !keyword.isEmpty()) { sql += " WHERE book_name LIKE ? OR author LIKE ?"; } try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (keyword != null && !keyword.isEmpty()) { String likePattern = "%" + keyword + "%"; ps.setString(1, likePattern); ps.setString(2, likePattern); } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { model.addRow(new Object[]{ rs.getInt("id"), rs.getString("book_name"), rs.getString("author"), rs.getString("publisher"), rs.getString("isbn"), rs.getInt("total_stock"), rs.getInt("available_stock") }); } } } catch (SQLException ex) { ex.printStackTrace(); } }这里 setRowCount(0) 是清空表格的关键,不调用它,每次查询结果都会追加在旧数据后面,列表越查越长。模糊搜索用 LIKE 但值仍然通过占位符绑定,防止有人输入 % 或 _ 通配符搞出奇怪的匹配。另一个细节是每次查询都新建 Connection 和 PreparedStatement,用 try-with-resources 自动关闭,这套写法是 java 基础里资源关闭的标准答案,答辩时被问“连接什么时候关闭”就答这里。
4.3 借书还书:事务边界与条件更新,rollback 是后悔药
借书是整个系统里唯一需要多表联动的操作:先扣图书库存,再插入一条借阅记录。这两步任何一步失败都不能留下脏数据,所以必须放在事务里。这里要强调一个关键点:扣库存不能先 SELECT 查可用数再 UPDATE,因为两个操作之间数据可能被别的用户改了。正确写法是直接用 UPDATE 语句加上 available_stock > 0 条件,让数据库自己判断,这一步是原子操作。
public boolean borrowBook(int userId, int bookId) { Connection conn = null; try { conn = JdbcUtil.getConnection(); conn.setAutoCommit(false); String updateSql = "UPDATE t_book SET available_stock = available_stock - 1 " + "WHERE id = ? AND available_stock > 0"; try (PreparedStatement ps = conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); if (ps.executeUpdate() == 0) { conn.rollback(); return false; } } String insertSql = "INSERT INTO t_borrow_record (user_id, book_id, due_time) " + "VALUES (?, ?, ?)"; try (PreparedStatement ps = conn.prepareStatement(insertSql)) { ps.setInt(1, userId); ps.setInt(2, bookId); ps.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now().plusDays(30))); ps.executeUpdate(); } 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(); } } } }这套代码有四个关键点。第一,setAutoCommit(false) 之后,后面所有 SQL 都在同一个事务里,只有 commit() 才真正生效。第二,UPDATE 的 WHERE 条件带 available_stock > 0,如果库存已经是 0,这次更新影响 0 行,马上 rollback 返回 false,这就是后悔药——比先查再改安全得多。第三,借阅记录的 due_time 用 LocalDateTime.now().plusDays(30) 表示默认借期 30 天。第四,finally 里把 autoCommit 改回 true 再关闭连接,是为了复用连接时不会把事务状态带出去。
还书操作比借书简单,只要事务里做两件事:把库存加回来,把借阅记录状态改成已还并写 return_time。同样需要事务。这段代码跟 borrowBook 结构一样,把 UPDATE t_book SET available_stock = available_stock + 1 和 UPDATE t_borrow_record SET status = 1, return_time = NOW() 包在里面即可,不再完整贴出,你自己照着 borrowBook 的框架写一遍。
5. 避坑排查:乱码、Socket、事务与界面卡死的五个现场
5.1 中文乱码:插入的数据变成问号,SELECT 出来也是问号
现象是 GUI 里输入中文书名,保存到数据库里变成 ???,用 Workbench 看数据也是一堆问号。原因有三个,按概率排序:第一是建库时字符集用了 latin1,第二是 JDBC 连接 URL 里少了 characterEncoding=utf8,第三是 IDE 控制台本身编码是 GBK。解决方法是先确认库表字符集:SHOW CREATE TABLE t_book,如果看到 CHARSET=latin1,就用 ALTER TABLE 改成 utf8mb4;然后再确认连接 URL 带 characterEncoding=utf8。这两步做完基本解决,如果还乱码,把 IDE 的 File Encoding 和控制台编码都改成 UTF-8。这是 JDBC 开发里血泪经验排第一的问题。
5.2 ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'
现象是命令行连 MySQL 时报这个错,或者在 Java 代码里连接 localhost 失败。原因是 MySQL 服务没启动,或者 JDBC URL 里用了 localhost 导致驱动试图走 Unix socket。解决分两步:先确认服务状态,Windows 下在服务管理器里看 MySQL 是否启动,Linux 下用 systemctl status mysql;再把 JDBC 连接默认值里的 localhost 改成 127.0.0.1,这样驱动会走 TCP 协议而不是 socket,问题立刻消失。这个报错在 MySQL 安装教程里经常被提到,原因是很多人装完 MySQL 忘记启动服务就直接跑 Java。
5.3 借书成功但库存变负数,或者订单记录存在但库存没扣
现象是循环借同一本书,可借库存变成 -1,或者借阅记录里有一条数据但库存没变化。原因是忘开事务,或者事务只包了其中一条 SQL;另一种常见原因是 UPDATE 语句没带 available_stock > 0 条件,导致并发时把库存扣成负数。解决方法是把扣库存和插记录放进同一个事务,setAutoCommit(false)、commit、rollback 缺一不可,并且 UPDATE 的 WHERE 里加 available_stock > 0。验证方法是连续借同一本书直到库存为 0,再借一次应该返回“库存不足”而不是让库存变 -1。
5.4 点登录按钮后整个窗口卡死,转圈转十几秒才恢复
现象是点击登录后窗口立即无响应,鼠标变成转圈状态,过一会才弹出主界面。原因是数据库查询是耗时操作,但你把它直接写在了按钮的 ActionListener 里,而 ActionListener 是在 Swing 的事件分发线程(EDT)上执行的,耗时操作会把整个界面的重绘和事件处理都堵住。解决方法是把数据库查询放到另一个线程里执行,然后用 SwingUtilities.invokeLater 把更新界面的代码切回 EDT。课设量小的话,直接用 new Thread(() -> loadBooks(keyword)).start() 就能解决;面试被问深一点,要说出 SwingWorker 才是标准方案。
5.5 SQLNonTransientConnectionException: SSL connection error 或 Public Key Retrieval is not allowed
现象是 Java 程序第一次连 MySQL 8 就报 SSL 连接错误,或者报公钥检索不允许。原因是 MySQL 8 默认开启 SSL,而且默认认证插件是 caching_sha2_password,驱动版本不对或 URL 里没配 useSSL 和 allowPublicKeyRetrieval 就会报这两个错。解决方法是连接 URL 里加 useSSL=false 和 allowPublicKeyRetrieval=true,同时把 mysql-connector-j 版本升到 8.0.x。还有一个隐藏坑是驱动类名:MySQL 8 的驱动类名是 com.mysql.cj.jdbc.Driver,老写法 com.mysql.jdbc.Driver 在新版本驱动里已经被移除了,写错会直接报 ClassNotFoundException。
6. 从课设到能演示:数据校验、演示数据与演示话术
6.1 输入校验和防注入的最后一道防线
GUI 程序最容易在输入框上翻车。读者借书输入一个不存在的用户 ID,或者 ISBN 里带引号,程序直接崩了,这在演示现场非常难看。我建议在每个输入框的监听器里加一层基础校验:整数必须用 Integer.parseInt 且在 try-catch 里,字符串长度用 length() 判断上限,ISBN 格式用正则简单过滤。所有 SQL 统一走 PreparedStatement,这条红线从登录贯穿到还书,不拼任何字符串。
6.2 演示数据:让评审一眼看懂业务流程
写一段初始化数据的 SQL,提前插入 3 个测试用户(一个管理员、两个读者)和 10 本经典书,覆盖不同出版社和库存状态。重点演一本书库存只有 1 本,借出去之后再借第二次,界面弹出“库存不足”的提示,这个场景最能证明事务和库存逻辑是真实有效的。演示前先跑一遍业务,确保当天不会翻车。
6.3 演示话术与验证方法:把表和数据状态讲成故事
我习惯的演示顺序是:先打开 Workbench 展示三张表和初始数据,再进系统登录管理员账号,搜索一本书,借出后立刻切到 Workbench 看这本书的 available_stock 减了 1,借阅记录表多了一行。这个“数据库实时变化”的对比一出来,比讲十页架构图都管用。最后还书再切过去看库存加回来,整套闭环就成立了。这个习惯我从第一次课设用到现在,每回演示完评审都不怎么追问细节了。希望帮到你。
本文还有配套的精品资源,点击获取