Java SE图书管理系统:JDBC分层架构与MySQL实战
简介这是一套基于Java语言、Eclipse开发环境与MySQL数据库实现的图书馆管理系统课程设计源码面向计算机专业本科生及Java初学者用于完成数据库应用开发类课程作业或小型项目实践。资源包共160个文件含40个核心Java源文件如LoginFrame、BookListFrame、UserDaoImpl等、83个编译后class文件、26个配置与说明txt文档以及png界面图、project工程配置等完整呈现MVC分层结构与JDBC数据库操作逻辑压缩包仅1.44MB轻量易导入。已有3137人学习下载适合快速理解GUI界面构建、MySQL表设计图书/读者/借阅三表关联、Swing事件响应及用户权限控制等关键知识点。读者可直接运行调试掌握从登录验证、图书增删改查到借阅管理的全流程开发实践。1. 这不是“又一个 Swing 图书系统”而是一套可调试、可扩展、能跑通 JDBC 连接池的 Java 图书管理最小可行架构你下载的eclipse数据库图书馆管理系统.zip表面看是课程设计常见的 Swing MySQL 组合但真正有价值的是它隐含的一套可落地验证的分层结构从LoginFrame的事件驱动入口到UserDaoImpl和BookDaoImpl中封装的 PreparedStatement 参数化查询再到ManagementDaoImpl里对借阅状态的事务控制——它没用 Spring Boot 自动装配却用最朴素的 JDBC 手动管理了 Connection 生命周期。这意味着你能在 Eclipse 里单步调试addBook()方法亲眼看到conn.prepareStatement(sql)后setString(1, book.getTitle())如何映射到 MySQL 的INSERT INTO books(title, author, isbn)也能在BookListFrame的refreshTable()调用中确认 ResultSet 每一行数据如何被new Book(...)封装成 POJO。它适合两类人一是刚学完 JDBC 却卡在“连不上数据库”或“中文乱码”的大二学生二是想快速搭建 Java SE 级 GUI 管理后台原型的初级开发——因为所有 DAO 类都预留了getConnection()抽象方法你只需替换JDBCUtil里的 URL 和账号就能对接本地 MySQL 8.0 或阿里云 RDS。2. 从 Eclipse 工程结构到 MySQL 表设计还原真实开发链路2.1 解压后必须做的三件事校验包结构、补全依赖、初始化数据库解压eclipse数据库图书馆管理系统.zip后你会看到一个典型的 Java SE 项目目录非 Maven包含src/下的com.library.dao、com.library.frame、com.library.model三个包。但注意lib/目录下缺失 mysql-connector-java.jar——这是导致“找不到主类”或“ClassNotFoundException: com.mysql.cj.jdbc.Driver”的最常见原因。正确做法是# 下载适配 MySQL 8.0 的驱动必须 8.0.28 wget https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.33/mysql-connector-java-8.0.33.jar # 将其复制到项目根目录下的 lib/ 文件夹 cp mysql-connector-java-8.0.33.jar /path/to/your/project/lib/提示Eclipse 中右键项目 → Properties → Java Build Path → Libraries → Add External JARs → 选中mysql-connector-java-8.0.33.jar。若仍报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver检查JDBCUtil.java中的驱动类名是否为com.mysql.cj.jdbc.DriverMySQL 5.x 用com.mysql.jdbc.Driver8.x 必须用cj版本。接着初始化数据库。系统默认使用数据库名library_db执行以下 SQL 创建表结构注意字符集必须为utf8mb4否则中文书名会变问号CREATE DATABASE library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library_db; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role ENUM(admin,user) DEFAULT user, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, author VARCHAR(100), isbn VARCHAR(20) UNIQUE, status ENUM(available,borrowed) DEFAULT available, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE borrow_records ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE );注意books.status字段类型为ENUM而非布尔值。BookDaoImpl.updateStatus()方法中通过UPDATE books SET status ? WHERE id ?更新参数传入borrowed或available字符串而非true/false。这是初学者易错点——误将 ENUM 当作 boolean 处理导致 SQL 执行失败。2.2 MVC 分层逻辑拆解为什么BookFrame不直接操作数据库该系统严格遵循 MVC 模式但未引入框架所有协调由手动编码完成。以添加图书为例流程如下2.2.1 View 层AddBookFrame触发事件AddBookFrame是一个继承JFrame的窗口类其中confirmButton.addActionListener(e - { ... })是入口。关键代码片段// AddBookFrame.java 片段 confirmButton.addActionListener(e - { String title titleField.getText().trim(); String author authorField.getText().trim(); String isbn isbnField.getText().trim(); if (title.isEmpty() || author.isEmpty() || isbn.isEmpty()) { JOptionPane.showMessageDialog(this, 请填写完整信息); return; } // Controller 职责组装数据并调用 Model Book book new Book(); // model 包下的实体类 book.setTitle(title); book.setAuthor(author); book.setIsbn(isbn); // 调用业务逻辑层实际是 DAO 的包装 boolean success new BookService().addBook(book); // BookService 是轻量级 service 类 if (success) { JOptionPane.showMessageDialog(this, 添加成功); this.dispose(); // 关闭窗口 } else { JOptionPane.showMessageDialog(this, 添加失败请检查 ISBN 是否重复); } });2.2.2 Model 层Book实体与BookDaoImpl的 JDBC 实现Book类仅含字段与 getter/setter无业务逻辑。真正的数据操作在BookDaoImpl// BookDaoImpl.java public class BookDaoImpl implements BookDao { Override public boolean addBook(Book book) { String sql INSERT INTO books(title, author, isbn) VALUES(?, ?, ?); try (Connection conn JDBCUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, book.getTitle()); // 参数索引从 1 开始 ps.setString(2, book.getAuthor()); ps.setString(3, book.getIsbn()); int rows ps.executeUpdate(); // 返回影响行数 return rows 0; } catch (SQLException e) { e.printStackTrace(); // 生产环境应记录日志而非 print return false; } } }逻辑说明PreparedStatement预编译 SQL防止 SQL 注入ps.setString(1, ...)中的1对应 SQL 中第一个?顺序不能错executeUpdate()用于 INSERT/UPDATE/DELETE返回受影响行数 0即表示成功。若isbn字段设为UNIQUE重复插入会抛SQLIntegrityConstraintViolationException需在catch块中捕获并提示用户。2.3 Eclipse 调试实战定位“登录失败”的真实原因LoginFrame是系统入口其loginButton的监听器调用UserDaoImpl.login(username, password)。但学生常遇到“用户名密码正确却登录失败”问题往往不在逻辑而在数据库连接或字符编码。调试步骤如下在UserDaoImpl.login()方法首行打断点运行 Debug 模式输入测试账号admin/123456观察Connection conn JDBCUtil.getConnection()是否返回非 null若conn为 null进入JDBCUtil.getConnection()检查Class.forName(com.mysql.cj.jdbc.Driver)是否抛出ClassNotFoundException驱动未加载DriverManager.getConnection(url, user, pwd)的url是否为jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4缺少characterEncodingutf8mb4会导致密码比对失败若conn正常单步执行SELECT * FROM users WHERE username ? AND password ?观察ResultSet.next()是否返回false此时执行SELECT * FROM users WHERE username admin查看数据库中密码字段值——很可能发现密码是明文存储如123456但代码中未做加密比对或数据库里存的是 MD5 值而代码未加密传输值。提示该系统未实现密码加密UserDaoImpl.login()直接比对明文。若你希望升级安全性可在LoginFrame提交前用MessageDigest.getInstance(MD5).digest(password.getBytes(UTF-8))生成哈希再与数据库中存储的哈希值比对。但注意MD5 已不安全生产环境应使用 BCrypt。3. JDBC 连接池与异常处理让系统从“能跑”走向“稳跑”3.1 为什么原版不用连接池以及如何低成本接入 HikariCP当前系统每次 DAO 操作都调用JDBCUtil.getConnection()获取新连接用完即close()。这在单用户演示时无问题但并发稍高如 5 人同时借书就会触发 MySQL 的max_connections限制默认 151。根本原因是未复用连接且未统一管理连接生命周期。HikariCP 是目前性能最优的 JDBC 连接池接入只需三步3.1.1 添加依赖与配置下载hikaricp-5.0.1.jar放入lib/并在JDBCUtil.java中替换连接获取逻辑// JDBCUtil.java 替换前原始 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 替换后HikariCP private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4); config.setUsername(root); config.setPassword(your_password); config.setMaximumPoolSize(20); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接 config.setConnectionTimeout(30000); // 连接超时 30 秒 config.setIdleTimeout(600000); // 空闲连接存活 10 分钟 config.setMaxLifetime(1800000); // 连接最大寿命 30 分钟 dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 从此处获取连接 }参数说明maximumPoolSize20适应小型局域网访问connectionTimeout30000防止网络抖动导致线程阻塞idleTimeout和maxLifetime避免 MySQL 的wait_timeout默认 28800 秒导致连接失效。若 MySQL 配置了wait_timeout600则maxLifetime必须小于该值。3.1.2 DAO 方法改造确保连接正确归还原BookDaoImpl.addBook()使用 try-with-resources 自动关闭Connection但连接池中conn.close()并非销毁连接而是归还到池中。因此必须移除Connection的显式 close只关闭PreparedStatement和ResultSetOverride public boolean addBook(Book book) { String sql INSERT INTO books(title, author, isbn) VALUES(?, ?, ?); try (Connection conn JDBCUtil.getConnection(); // 从池中获取 PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, book.getTitle()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getIsbn()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } // conn 会在 try 结束时自动归还无需 conn.close() }3.2 全局异常处理捕获 SQLException 并分类响应原系统对异常处理较粗放仅printStackTrace()。实际开发中需区分场景给出用户友好提示异常类型可能原因用户提示文案开发者日志建议SQLTimeoutExceptionMySQL 响应超时“系统繁忙请稍后再试”记录 SQL 语句 执行耗时SQLIntegrityConstraintViolationException主键/唯一键冲突“ISBN 已存在请检查后重试”记录冲突字段名与值SQLNonTransientConnectionException数据库服务不可用“数据库连接异常请联系管理员”记录连接 URL 与错误码改造JDBCUtil的getConnection()方法加入连接健康检查public static Connection getConnection() throws SQLException { Connection conn dataSource.getConnection(); // 执行简单查询验证连接有效性 try (Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT 1)) { if (!rs.next()) throw new SQLException(Connection validation failed); } return conn; }4. 图书借阅核心逻辑与边界验证从“能增删改查”到“业务闭环”4.1 借阅流程的事务控制为什么borrowBook()必须原子执行BookListFrame中点击“借阅”按钮实际调用ManagementDaoImpl.borrowBook(userId, bookId)。该方法需同时完成三件事更新books表中该书status borrowed插入一条borrow_records记录检查该用户是否已借阅超过 5 本书业务规则。若用三个独立 SQL 执行可能出现更新了书状态但插入记录失败导致书“被借走却无记录”。解决方案是显式事务控制Override public boolean borrowBook(int userId, int bookId) { String updateSql UPDATE books SET status borrowed WHERE id ? AND status available; String insertSql INSERT INTO borrow_records(user_id, book_id) VALUES(?, ?); try (Connection conn JDBCUtil.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try (PreparedStatement updatePs conn.prepareStatement(updateSql); PreparedStatement insertPs conn.prepareStatement(insertSql)) { // 步骤1尝试更新书状态WHERE status available 确保未被他人借走 updatePs.setInt(1, bookId); int updated updatePs.executeUpdate(); if (updated 0) { throw new SQLException(图书已被借出或不存在); } // 步骤2插入借阅记录 insertPs.setInt(1, userId); insertPs.setInt(2, bookId); insertPs.executeUpdate(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { conn.rollback(); // 任一失败则回滚 throw e; } } catch (SQLException e) { e.printStackTrace(); return false; } }关键细节UPDATE books SET status borrowed WHERE id ? AND status available中的AND status available是乐观锁思想避免并发借阅同一本书conn.setAutoCommit(false)后必须显式commit()或rollback()否则连接会一直占用。4.2 归还逻辑与状态同步returnBook()的幂等性设计ResetUserFrame或BookListFrame中的“归还”操作调用ManagementDaoImpl.returnBook(bookId)。难点在于用户可能多次点击“归还”需保证多次调用结果一致幂等。实现方式是基于状态判断而非单纯 UPDATEOverride public boolean returnBook(int bookId) { String sql UPDATE books SET status available WHERE id ? AND status borrowed; try (Connection conn JDBCUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, bookId); int rows ps.executeUpdate(); return rows 0; // 只有原状态为 borrowed 才更新成功 } catch (SQLException e) { e.printStackTrace(); return false; } }为什么这样设计若bookId100的书已是available状态UPDATE ... WHERE status borrowed不会匹配任何行rows0方法返回false上层 UI 可提示“该书尚未借出”。这比先SELECT status再UPDATE更高效也避免了 SELECT-UPDATE 的竞态条件。4.3 数据一致性验证用 SQL 快速诊断借阅异常当用户反馈“借了书但列表里没显示”时不要盲目重启应用先用 SQL 校验数据状态-- 检查某本书的当前状态和借阅记录 SELECT b.id, b.title, b.status, r.id as record_id, r.borrow_time, r.return_time FROM books b LEFT JOIN borrow_records r ON b.id r.book_id AND r.return_time IS NULL WHERE b.id 123; -- 统计每个用户的在借数量验证是否超限 SELECT u.username, COUNT(r.id) as borrowed_count FROM users u LEFT JOIN borrow_records r ON u.id r.user_id AND r.return_time IS NULL GROUP BY u.id, u.username HAVING borrowed_count 5;技巧r.return_time IS NULL表示未归还的记录HAVING子句在GROUP BY后过滤比WHERE更准确。将此 SQL 保存为diagnose_borrow.sql在 Navicat 或 MySQL Workbench 中一键执行5 秒定位问题根源。5. Eclipse 环境优化与部署技巧让课程设计答辩更从容5.1 解决“Eclipse 找不到或无法加载主类”三大高频场景该错误在导入项目后极常见本质是类路径Classpath配置错误而非代码问题。按优先级排查场景检查项修复操作主类未指定Run As→Run Configurations→Main标签页中Main class是否为空点击Search...输入LoginFrame选中com.library.frame.LoginFrame输出目录错误Project Properties→Java Build Path→Source标签页中Default output folder是否指向bin/确保为YourProjectName/bin且bin/目录下存在com/library/frame/LoginFrame.class模块路径干扰项目含module-info.java或 Eclipse 设置了 Java Platform Module System右键项目 →Properties→Java Build Path→Projects标签页 → 移除无关模块或删除module-info.java注意若项目使用 Java 11Eclipse 默认启用模块系统。但本系统无模块声明必须禁用——在Java Build Path→Libraries→ 选中JRE System Library→Edit→ 选择Execution Environment如JavaSE-11而非Workspace default JRE。5.2 导出可运行 JAR 包包含依赖与启动配置课程设计需提交.jar文件Eclipse 导出时务必勾选“Package required libraries into generated JAR”右键项目 →Export→Java→Runnable JAR fileLaunch configuration选LoginFrame的运行配置Export destination设为dist/library-system.jarLibrary handling选Package required libraries into generated JAR关键点击Finish。生成的 JAR 包可直接双击运行或命令行执行java -jar dist/library-system.jar验证技巧用jar -tf dist/library-system.jar | grep mysql确认mysql-connector-java-8.0.33.jar已嵌入用jar -xf dist/library-system.jar META-INF/MANIFEST.MF查看Main-Class: com.library.frame.LoginFrame是否写入清单文件。5.3 中文显示终极方案从 Eclipse 控制台到 MySQL 全链路 UTF-8中文乱码是课程设计答辩最大扣分点。需四层统一Eclipse 编辑器编码Window→Preferences→General→Workspace→Text file encoding→UTF-8JDBC URL 参数jdbc:mysql://localhost:3306/library_db?characterEncodingutf8mb4serverTimezoneAsia/ShanghaiMySQL 服务端配置my.cnf中[mysqld]段添加collation-server utf8mb4_unicode_ci和init-connect SET NAMES utf8mb4Swing 组件字体在LoginFrame构造函数末尾添加UIManager.put(Label.font, new Font(微软雅黑, Font.PLAIN, 14));避免 Linux 下显示方块。最后验证在LoginFrame的usernameField.setText(张三);运行后输入框显示正常中文且数据库中INSERT INTO users(username) VALUES(张三)成功存储——至此全链路中文支持闭环。本文还有配套的精品资源点击获取