news 2026/9/15 5:35:30

Java Swing图书管理系统实战:MySQL连接、中文支持与JTable刷新

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Swing图书管理系统实战:MySQL连接、中文支持与JTable刷新

简介:这是一套基于Java Swing与MySQL开发的GUI图书管理系统完整工程,面向高校计算机专业学生课程设计与Java GUI开发初学者,解决双角色(学生/管理员)场景下的图书借阅、入库、读者管理等核心业务需求。资源包共184个文件,含35个带完整注释的Java源码、101个编译后Class文件、18张界面图标(PNG)、15张功能截图(JPG)、3个MySQL建库建表SQL脚本及1份配套课程报告(DOCX),整体压缩后24.47MB,结构清晰,便于按模块理解MVC分层逻辑与Swing事件驱动机制。已有601人学习下载,提供可直接导入Eclipse运行的完整项目(兼容MyEclipse),含登录、借还书、书籍增删改查、读者管理等9个核心视图及6张数据库表,代码规范、注释详实,适合作为Java桌面应用开发的实践范例与课程设计参考。

1. 为什么现在还有人用 Java + Swing + MySQL 做图书管理系统?不是早该淘汰了吗?

这不是怀旧,而是现实约束下的理性选择。高校计算机专业大三、大四学生做课程设计或毕业设计时,常被明确要求“必须使用桌面端技术栈”“不得基于 Web 框架”“需体现数据库交互与 GUI 控件联动能力”——这时 Java + Swing + MySQL 就成了最稳妥、最易交付、最能覆盖考核点的组合。它不追求高并发或云原生,但要求:启动即用、界面可操作、增删改查闭环、数据持久化可靠、代码结构清晰可答辩。Swing 虽无现代化 UI 框架的响应式能力,却胜在 JDK 自带、零依赖、调试直观;MySQL 则因安装成熟、语法标准、Navicat 可视化友好,成为教学场景下最易上手的关系型数据库。本文面向真实开发场景:从零搭建一个可运行、可调试、可扩展的图书管理桌面应用,重点讲清 Swing 组件如何绑定 MySQL 数据、事务怎么控制、中文乱码怎么根治、JTable 怎么动态刷新、以及毕业答辩常被追问的“为什么不用 JavaFX 或 Spring Boot”。

2. 搭建最小可运行环境:JDK 17 + MySQL 8.0 + IDE 配置要点

2.1 JDK 与 MySQL 版本协同逻辑必须理清

Java 17 是当前 LTS 版本,Swing 在 JDK 17 中仍完整保留(未被移除),且支持模块化隔离;MySQL 8.0 提供了更严格的默认认证方式(caching_sha2_password)和更规范的 SQL 模式,但与 JDBC 8.0.33+ 兼容性极佳。关键点在于:JDBC 驱动版本必须与 MySQL 服务端版本严格匹配。若使用 MySQL 8.0.33,必须选用 mysql-connector-java-8.0.33.jar(或更高补丁版),不可混用 5.x 驱动——否则连接时会抛出java.sql.SQLException: Unknown system variable 'query_cache_size'。下载地址应为官方 Maven 仓库(https://repo1.maven.org/maven2/mysql/mysql-connector-java/),而非第三方镜像站,避免驱动包被篡改。

提示:不要用mysql-connector-java-8.0.33.jar的“免安装版”概念。所谓“免安装”仅指无需 Windows 安装程序,但 jar 包本身必须显式添加到项目 classpath,IDE 中需手动配置 Build Path,不能靠环境变量自动加载。

2.2 MySQL 初始化:字符集、用户权限、初始库表一步到位

图书管理系统对中文支持是刚需,必须在创建数据库时指定utf8mb4字符集,而非utf8(MySQL 的utf8实际只支持 3 字节 UTF-8,无法存储 emoji 和部分生僻汉字)。执行以下 SQL:

CREATE DATABASE book_management DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; USE book_management; CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(17) NOT NULL UNIQUE, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), publish_date DATE, price DECIMAL(10,2), stock INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE USER 'bookapp'@'localhost' IDENTIFIED BY 'Book@2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON book_management.* TO 'bookapp'@'localhost'; FLUSH PRIVILEGES;

注意COLLATE utf8mb4_0900_ai_ci是 MySQL 8.0 默认排序规则,比utf8mb4_general_ci更准确支持中文排序;GRANT语句中明确限定主机为'localhost',避免开放远程访问权限——这是教学环境安全底线。

2.3 IntelliJ IDEA / Eclipse 工程配置实操步骤

以 IntelliJ IDEA 2023.3 为例(Eclipse 同理):

  1. 新建 Project → 选择 “Java” → SDK 选 JDK 17;
  2. 右键项目 →Open Module SettingsModulesDependencies+JARs or directories→ 选中下载好的mysql-connector-java-8.0.33.jar
  3. src/main/resources下新建db.properties文件,内容如下:
jdbc.url=jdbc:mysql://localhost:3306/book_management?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=bookapp jdbc.password=Book@2024

注意:serverTimezone=Asia/Shanghai必须显式设置,否则publish_date字段读写会出现 14 小时偏差;allowPublicKeyRetrieval=true是 MySQL 8.0+ 连接必需参数,用于绕过公钥交换验证(教学环境可接受,生产环境需配置 SSL)。

3. Swing 界面与 MySQL 数据层的双向绑定实现

3.1 设计分层架构:为什么不能把 SQL 写死在 ActionListener 里?

常见错误是直接在按钮点击事件中拼接 SQL 字符串并执行,导致代码不可测试、事务难控制、SQL 注入风险高。正确做法是划分三层:

  • View 层:JFrame + JPanel + JButton + JTextField + JTable,只负责展示和触发事件;
  • Controller 层:BookController 类,接收 View 事件,调用 Service 方法,更新 View 状态;
  • Model 层:BookService + BookDAO + Book 实体类,封装业务逻辑与数据库操作。

这种分层让JTable的数据刷新、新增校验、删除确认等逻辑可独立单元测试,也便于答辩时解释“MVC 思想落地”。

3.2 BookDAO 的核心实现:PreparedStatement 防注入 + 事务控制

public class BookDAO { private static final String INSERT_SQL = "INSERT INTO books (isbn, title, author, publisher, publish_date, price, stock) VALUES (?, ?, ?, ?, ?, ?, ?)"; private static final String SELECT_ALL_SQL = "SELECT id, isbn, title, author, publisher, publish_date, price, stock FROM books ORDER BY created_at DESC"; public void insert(Book book) throws SQLException { try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(INSERT_SQL, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, book.getIsbn()); ps.setString(2, book.getTitle()); ps.setString(3, book.getAuthor()); ps.setString(4, book.getPublisher()); ps.setDate(5, new java.sql.Date(book.getPublishDate().getTime())); ps.setBigDecimal(6, book.getPrice()); ps.setInt(7, book.getStock()); int affectedRows = ps.executeUpdate(); if (affectedRows == 0) { throw new SQLException("Insert failed, no rows affected"); } try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { book.setId(rs.getInt(1)); } } } } public List<Book> findAll() throws SQLException { List<Book> books = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(SELECT_ALL_SQL)) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setIsbn(rs.getString("isbn")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setPublishDate(rs.getDate("publish_date")); book.setPrice(rs.getBigDecimal("price")); book.setStock(rs.getInt("stock")); books.add(book); } } } return books; } }
关键参数说明:
  • Statement.RETURN_GENERATED_KEYS:让PreparedStatement返回自增主键,避免二次查询;
  • ps.setDate(5, new java.sql.Date(...))java.util.Date不能直接传给setDate(),必须转为java.sql.Date
  • try-with-resources:自动关闭 Connection/PreparedStatement/ResultSet,防止连接泄漏;
  • ps.setString()替代ps.executeUpdate("INSERT INTO...'" + title + "'"):彻底杜绝 SQL 注入。

3.3 JTable 动态刷新机制:DefaultTableModel 的正确用法

Swing 中 JTable 与数据源解耦,必须通过DefaultTableModel中转。错误做法是每次查询后table.setModel(new DefaultTableModel(...))——这会导致列宽重置、排序状态丢失。正确做法是复用模型并清空后填充:

private void loadBooksToTable() { try { List<Book> books = bookService.findAll(); DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); // 清空现有行,保留列定义 for (Book book : books) { model.addRow(new Object[]{ book.getId(), book.getIsbn(), book.getTitle(), book.getAuthor(), book.getPublisher(), book.getPublishDate(), book.getPrice(), book.getStock() }); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, "加载图书失败:" + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } }

注意:model.setRowCount(0)是关键,它保留JTable的列标题、列宽、渲染器等 UI 状态,仅清除数据行。若用new DefaultTableModel(...)重建,则所有列宽需重新table.getColumnModel().getColumn(i).setPreferredWidth(...)设置,答辩时极易被问“为什么每次刷新界面都变窄”。

4. 图书管理核心功能落地:增删改查 + 中文输入全流程验证

4.1 新增图书:ISBN 格式校验与重复拦截

ISBN-13 是当前国际标准,格式为 13 位数字,可能含连字符(如978-7-04-050694-6),但数据库字段应存纯数字。校验逻辑需在 Controller 层完成:

private boolean isValidIsbn(String isbn) { if (isbn == null) return false; String digitsOnly = isbn.replaceAll("\\D", ""); // 移除非数字字符 return digitsOnly.length() == 13 && digitsOnly.chars().allMatch(Character::isDigit); } private void handleAddBook() { String isbn = isbnField.getText().trim(); if (!isValidIsbn(isbn)) { JOptionPane.showMessageDialog(this, "ISBN 格式错误,请输入 13 位数字(可含连字符)", "输入错误", JOptionPane.WARNING_MESSAGE); return; } try { if (bookService.existsByIsbn(isbn)) { JOptionPane.showMessageDialog(this, "该 ISBN 已存在,请勿重复录入", "重复提示", JOptionPane.INFORMATION_MESSAGE); return; } Book book = new Book(); book.setIsbn(isbn.replaceAll("\\D", "")); book.setTitle(titleField.getText().trim()); book.setAuthor(authorField.getText().trim()); book.setPublisher(publisherField.getText().trim()); book.setPublishDate(Date.valueOf(publishDateField.getText().trim())); // 输入格式:2023-01-01 book.setPrice(new BigDecimal(priceField.getText().trim())); book.setStock(Integer.parseInt(stockField.getText().trim())); bookService.add(book); JOptionPane.showMessageDialog(this, "添加成功!"); loadBooksToTable(); // 刷新表格 } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, "价格或库存必须为数字", "输入错误", JOptionPane.WARNING_MESSAGE); } catch (SQLException e) { JOptionPane.showMessageDialog(this, "数据库操作失败:" + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } }
参数说明:
  • isbn.replaceAll("\\D", ""):正则\\D匹配所有非数字字符,确保入库为纯数字;
  • Date.valueOf("2023-01-01")java.time.LocalDate不被 JDBC 4.2 直接支持,此处用java.sql.Date.valueOf()转换;
  • bookService.existsByIsbn():DAO 层需实现SELECT COUNT(*) FROM books WHERE isbn = ?,避免先查后插的竞态条件。

4.2 删除图书:带事务回滚的级联确认

删除操作必须二次确认,且需捕获异常回滚事务。Swing 中不能阻塞 EDT(事件调度线程),因此确认对话框必须同步弹出:

private void handleDeleteBook() { int selectedRow = table.getSelectedRow(); if (selectedRow == -1) { JOptionPane.showMessageDialog(this, "请先选择要删除的图书", "提示", JOptionPane.INFORMATION_MESSAGE); return; } Long id = (Long) table.getValueAt(selectedRow, 0); // 假设第一列为 id int confirm = JOptionPane.showConfirmDialog( this, "确定要删除 ID 为 " + id + " 的图书吗?此操作不可撤销。", "确认删除", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE ); if (confirm != JOptionPane.YES_OPTION) return; try { bookService.delete(id); JOptionPane.showMessageDialog(this, "删除成功!"); loadBooksToTable(); } catch (SQLException e) { JOptionPane.showMessageDialog(this, "删除失败:" + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } }

注意:JOptionPane.showConfirmDialog()是模态对话框,会阻塞当前线程直到用户点击,这是 Swing 单线程模型下的正确交互方式。切勿用SwingWorker异步弹窗——会导致 UI 线程错乱。

4.3 修改图书:编辑态与保存态的状态管理

JTable 默认不可编辑,需重写isCellEditable()并监听单元格编辑结束事件:

table.getModel().addTableModelListener(e -> { if (e.getType() == TableModelEvent.UPDATE) { int row = e.getFirstRow(); int column = e.getColumn(); if (column >= 0 && column <= 6) { // 仅允许编辑 title/author/publisher/price/stock try { Book book = getBookAtRow(row); // 根据 row 从 model 获取原始 Book 对象 String newValue = (String) table.getValueAt(row, column); // 根据 column 更新 book 对应字段... bookService.update(book); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, "保存失败:" + ex.getMessage()); loadBooksToTable(); // 回滚显示 } } } });

实际项目中建议采用“双击进入编辑模式”的交互:点击某行后,弹出JDialog表单修改,而非直接编辑表格——更符合图书管理业务逻辑,也避免用户误操作。

5. 排查高频故障:中文乱码、连接超时、JTable 不刷新的根因与修复

5.1 中文乱码三重校验清单

乱码问题 90% 出现在字符集未统一,需逐层检查:

层级检查项正确值验证命令
MySQL 服务端character_set_serverutf8mb4SHOW VARIABLES LIKE 'character_set_server';
数据库 & 表DEFAULT CHARSETutf8mb4SHOW CREATE DATABASE book_management;
SHOW CREATE TABLE books;
JDBC URLcharacterEncoding参数不加此项(由utf8mb4自动推导)URL 中禁止出现&characterEncoding=utf8

注意:JDBC URL 中显式加characterEncoding=utf8会强制使用 MySQL 的utf8(3 字节),反而导致插入生僻字时报错Incorrect string value。正确做法是仅靠utf8mb4字符集 +Collation驱动自动适配。

5.2 连接池缺失导致的“Too many connections”错误

教学项目常忽略连接复用,每操作一次就new Connection(),MySQL 默认最大连接数 151,10 个用户同时操作即爆。解决方案不是调高max_connections,而是用 HikariCP:

<!-- pom.xml --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
public class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/book_management?useSSL=false&serverTimezone=Asia/Shanghai"); config.setUsername("bookapp"); config.setPassword("Book@2024"); config.setDriverClassName("com.mysql.cj.jdbc.Driver"); config.setMaximumPoolSize(10); // 教学环境 10 足够 config.setMinimumIdle(2); config.setConnectionTimeout(30000); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

5.3 JTable 不刷新的 4 个隐藏原因与验证方法

loadBooksToTable()执行后表格无变化,按顺序排查:

  1. 数据源为空:在findAll()返回前加System.out.println("Found " + books.size() + " books");,确认 DAO 是否真查到数据;
  2. EDT 线程违规loadBooksToTable()是否在非 EDT 线程调用?用SwingUtilities.isEventDispatchThread()检查,非 EDT 必须包装SwingUtilities.invokeLater(...)
  3. TableModel 列数不匹配model.addColumn()model.addRow()的 Object[] 长度必须一致,差 1 位就会静默失败;
  4. JTable 被重复 add():检查getContentPane().add(table)是否执行多次,导致新 table 覆盖旧 table,旧 table 仍持有老数据。

验证方法:在loadBooksToTable()结尾加System.out.println("Table row count: " + table.getRowCount());,若输出0但 DAO 返回非空列表,问题必在model.setRowCount(0)后未addRow(),或addRow()的 Object[] 有 null 值触发异常被捕获。

6. 毕业答辩加分技巧:ER 图绘制、JUnit 测试覆盖率、打包成可执行 jar

6.1 用 draw.io 绘制符合评分标准的 ER 图

答辩要求“提供系统 ER 图”,不能手绘拍照,需用工具生成矢量图。核心实体只有books表,但需体现:

  • 主键id(PK)、外键(本系统无外键,故不画);
  • 属性类型标注:isbn: VARCHAR(17)price: DECIMAL(10,2)
  • 中文注释:每个字段旁加小号中文说明(如“ISBN 编号”“图书定价”);
  • 关系线:books自身无关联,但可加注释框:“本系统暂未实现借阅记录、读者信息等扩展模块”。

提示:draw.io 导出为 PNG 时勾选 “Transparent background”,插入 Word 文档后无白边;导出 PDF 用于打印,分辨率更高。

6.2 为 BookDAO 编写 JUnit 5 测试用例(覆盖增删查)

@ExtendWith(MockitoExtension.class) class BookDAOTest { @Mock private Connection connection; @Mock private PreparedStatement preparedStatement; @Mock private ResultSet resultSet; @Test void testFindAll_ReturnsBooks() throws SQLException { // Given BookDAO dao = new BookDAO(); // 使用 H2 内存数据库替代 MySQL,避免依赖外部服务 Connection h2Conn = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", ""); h2Conn.createStatement().executeUpdate("CREATE TABLE books(id INT PRIMARY KEY, title VARCHAR(100))"); h2Conn.createStatement().executeUpdate("INSERT INTO books VALUES(1, 'Java编程思想')"); // When List<Book> books = new BookDAO().findAll(); // Then assertEquals(1, books.size()); assertEquals("Java编程思想", books.get(0).getTitle()); } }

使用 H2 内存数据库(h2依赖)替代真实 MySQL,测试不依赖环境,mvn test即可运行,答辩时可现场演示测试通过率。

6.3 打包成双击运行的 jar:MANIFEST.MF 与资源路径陷阱

IntelliJ 打包时,src/main/resources/db.properties必须在 jar 包根目录,否则ClassLoader.getSystemResourceAsStream("db.properties")返回 null。正确配置:

  1. File → Project Structure → Artifacts → + → JAR → From modules with dependencies
  2. Output Layout中,确认db.properties出现在root directory下(而非lib/resources/子目录);
  3. Manifest中设置Main-Class: com.example.BookApp(你的主类全路径);
  4. 构建后执行java -jar book-system.jar,若报Could not find or load main class,说明 Manifest 缺失或路径错误。

验证 jar 内部结构:jar -tf book-system.jar | grep properties应输出db.properties,且无前缀路径。

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

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

ThinkPHP香水外贸响应式网站模板的部署、改造与PHP 8兼容实战指南

简介&#xff1a;面向香水外贸企业的ThinkPHP响应式英文网站模板&#xff0c;适合需要英文产品展示、企业信息展示及多设备访问体验的外贸团队。模板基于ThinkPHP框架构建&#xff0c;代码结构清晰、易于扩展&#xff0c;可快速部署上线&#xff0c;也方便后续根据产品线进行二…

作者头像 李华
网站建设 2026/9/15 5:33:18

C#酒店管理系统实战:数据库建模、事务控制与WinForms数据绑定

简介&#xff1a;本资源是一份面向高校计算机专业本科生的C#课程设计实践项目&#xff0c;聚焦酒店客房管理业务场景&#xff0c;帮助学习者掌握Windows Forms桌面应用开发、SQL Server数据库交互及完整软件工程流程。资源包含70个文件&#xff0c;总大小454KB&#xff0c;涵盖…

作者头像 李华
网站建设 2026/9/15 5:33:02

基于SpringBoot的市场摊位招投标系统毕业设计项目源码

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/15 5:32:33

数据库实时同步与异步同步:机制、原理与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 5:30:56

基于YOLOv9的验证码识别实战:从数据标注到模型部署

简介&#xff1a;面向验证码识别与计算机视觉开发者&#xff0c;这是一套基于Python和YOLOv9的验证码识别设计源码&#xff0c;聚焦网站安全、自动登录、自动化测试等真实场景&#xff0c;可用于快速理解并搭建从数据处理、模型训练到端到端预测的完整流程。资源包共2000个文件…

作者头像 李华
网站建设 2026/9/15 5:30:21

云安全管理新范式:从最小权限到配置核查的落地指南

每次行业大会的消息一出&#xff0c;总有人问我“值不值得跑一趟”。说实话&#xff0c;如果只能挑一个会去&#xff0c;我一般会优先看看CDIE这类偏“数字化转型应用”的场子——因为这里聊的不是PPT里的概念&#xff0c;而是企业真金白银踩出来的落地路径。今年收到新钛云服的…

作者头像 李华