ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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 包本身必须显式添加到项目 classpathIDE 中需手动配置 Build Path不能靠环境变量自动加载。2.2 MySQL 初始化字符集、用户权限、初始库表一步到位图书管理系统对中文支持是刚需必须在创建数据库时指定utf8mb4字符集而非utf8MySQL 的utf8实际只支持 3 字节 UTF-8无法存储 emoji 和部分生僻汉字。执行以下 SQLCREATE 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 bookapplocalhost IDENTIFIED BY Book2024; GRANT SELECT, INSERT, UPDATE, DELETE ON book_management.* TO bookapplocalhost; 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 同理新建 Project → 选择 “Java” → SDK 选 JDK 17右键项目 →Open Module Settings→Modules→Dependencies→→JARs or directories→ 选中下载好的mysql-connector-java-8.0.33.jar在src/main/resources下新建db.properties文件内容如下jdbc.urljdbc:mysql://localhost:3306/book_management?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernamebookapp jdbc.passwordBook2024注意serverTimezoneAsia/Shanghai必须显式设置否则publish_date字段读写会出现 14 小时偏差allowPublicKeyRetrievaltrue是 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 ListBook findAll() throws SQLException { ListBook 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.Datetry-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 { ListBook 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 中禁止出现characterEncodingutf8注意JDBC URL 中显式加characterEncodingutf8会强制使用 MySQL 的utf83 字节反而导致插入生僻字时报错Incorrect string value。正确做法是仅靠utf8mb4字符集 Collation驱动自动适配。5.2 连接池缺失导致的“Too many connections”错误教学项目常忽略连接复用每操作一次就new Connection()MySQL 默认最大连接数 15110 个用户同时操作即爆。解决方案不是调高max_connections而是用 HikariCP!-- pom.xml -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependencypublic class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/book_management?useSSLfalseserverTimezoneAsia/Shanghai); config.setUsername(bookapp); config.setPassword(Book2024); 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()执行后表格无变化按顺序排查数据源为空在findAll()返回前加System.out.println(Found books.size() books);确认 DAO 是否真查到数据EDT 线程违规loadBooksToTable()是否在非 EDT 线程调用用SwingUtilities.isEventDispatchThread()检查非 EDT 必须包装SwingUtilities.invokeLater(...)TableModel 列数不匹配model.addColumn()与model.addRow()的 Object[] 长度必须一致差 1 位就会静默失败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 测试覆盖率、打包成可执行 jar6.1 用 draw.io 绘制符合评分标准的 ER 图答辩要求“提供系统 ER 图”不能手绘拍照需用工具生成矢量图。核心实体只有books表但需体现主键idPK、外键本系统无外键故不画属性类型标注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 ListBook books new BookDAO().findAll(); // Then assertEquals(1, books.size()); assertEquals(Java编程思想, books.get(0).getTitle()); } }使用 H2 内存数据库h2依赖替代真实 MySQL测试不依赖环境mvn test即可运行答辩时可现场演示测试通过率。6.3 打包成双击运行的 jarMANIFEST.MF 与资源路径陷阱IntelliJ 打包时src/main/resources/db.properties必须在 jar 包根目录否则ClassLoader.getSystemResourceAsStream(db.properties)返回 null。正确配置File → Project Structure → Artifacts → → JAR → From modules with dependencies在Output Layout中确认db.properties出现在root directory下而非lib/或resources/子目录Manifest中设置Main-Class: com.example.BookApp你的主类全路径构建后执行java -jar book-system.jar若报Could not find or load main class说明 Manifest 缺失或路径错误。验证 jar 内部结构jar -tf book-system.jar | grep properties应输出db.properties且无前缀路径。本文还有配套的精品资源点击获取
返回列表