Java 17 + MySQL 8.0 图书借阅系统:从控制台到 Swing GUI 的 3 层架构演进实战

📅 2026/7/8 19:44:25
Java 17 + MySQL 8.0 图书借阅系统:从控制台到 Swing GUI 的 3 层架构演进实战
Java 17 MySQL 8.0 图书借阅系统从控制台到 Swing GUI 的 3 层架构演进实战1. 项目背景与架构演进必要性在软件开发领域架构设计往往决定了系统的可维护性和扩展性。图书借阅系统作为经典的数据库应用案例从简单的控制台程序演进到图形界面GUI的三层架构系统不仅反映了技术栈的升级更体现了软件工程思想的成熟。传统控制台版本虽然能快速实现核心功能但存在几个明显短板用户交互体验差纯文本界面难以提供直观操作代码耦合度高业务逻辑与数据访问混杂扩展困难新增功能需修改大量现有代码维护成本高任何改动都可能引发连锁反应采用三层架构表示层-Presentation、业务逻辑层-Business、数据访问层-Data Access能有效解决这些问题。以下是架构演进的核心优势对比特性控制台版本三层架构Swing版本代码复用率30%70%功能扩展周期3-5人日/功能1-2人日/功能数据库切换成本需重写80%代码仅修改数据访问层单元测试覆盖率通常20%可达到60%长期维护成本高低2. 技术栈选型与开发环境2.1 核心组件版本选择Java 17选择LTS版本特性支持与稳定性兼备Record类型简化DTO定义TextBlock优化SQL书写Switch表达式增强业务逻辑表达MySQL 8.0关键特性利用窗口函数实现复杂统计CTE(Common Table Expressions)优化查询更好的JSON支持2.2 开发工具链# 推荐环境配置 - IDEIntelliJ IDEA Ultimate含GUI设计器 - 构建工具Maven 3.8 - 数据库工具DBeaver/Datagrip - 版本控制Git GitLens2.3 关键依赖配置!-- pom.xml 核心依赖 -- dependencies !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency !-- Swing扩展 -- dependency groupIdcom.formdev/groupId artifactIdflatlaf/artifactId version2.4/version /dependency /dependencies3. 三层架构具体实现3.1 数据访问层(DAO)设计采用接口隔离原则定义核心数据操作契约public interface BookRepository { // 使用Java16的Record定义DTO record BookDTO(String isbn, String title, String author) {} ListBookDTO findByCondition(BookCondition condition); OptionalBookDTO findByIsbn(String isbn); int save(BookDTO book); int update(BookDTO book); } // 实现示例 public class BookDAO implements BookRepository { private final DataSource dataSource; public BookDAO(DataSource dataSource) { this.dataSource dataSource; } Override public ListBookDTO findByCondition(BookCondition cond) { String sql SELECT isbn, title, author FROM books WHERE available true AND (? IS NULL OR title LIKE CONCAT(%,?,%)) ; // 使用try-with-resources确保资源释放 try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, cond.keyword()); stmt.setString(2, cond.keyword()); ResultSet rs stmt.executeQuery(); ListBookDTO result new ArrayList(); while (rs.next()) { result.add(new BookDTO( rs.getString(isbn), rs.getString(title), rs.getString(author) )); } return result; } catch (SQLException e) { throw new DataAccessException(e); } } }注意DAO层应始终捕获SQLException并转换为业务异常避免技术细节泄露到业务层3.2 业务逻辑层设计业务层需要处理核心领域逻辑例如借书规则验证public class BookService { private final BookRepository bookRepo; private final LoanRepository loanRepo; public LoanResult loanBook(String userId, String isbn) { // 验证图书库存 BookDTO book bookRepo.findByIsbn(isbn) .orElseThrow(() - new BookNotFoundException(isbn)); if (!book.available()) { throw new BookNotAvailableException(isbn); } // 检查用户借阅限额 int currentLoans loanRepo.countActiveLoans(userId); if (currentLoans 5) { // 业务规则 throw new LoanLimitExceededException(userId); } // 创建借阅记录 LoanRecord loan new LoanRecord( UUID.randomUUID().toString(), userId, isbn, LocalDate.now(), LocalDate.now().plusDays(30) // 30天借期 ); return loanRepo.save(loan); } }3.3 表示层实现Swing界面开发的关键技巧使用CardLayout实现多面板切换// 主窗口初始化 public class MainFrame extends JFrame { private final CardLayout cardLayout new CardLayout(); private final JPanel cardPanel new JPanel(cardLayout); public MainFrame() { // 添加不同视图面板 cardPanel.add(new LoginPanel(), LOGIN); cardPanel.add(new MainMenuPanel(), MENU); cardPanel.add(new BookSearchPanel(), SEARCH); // 默认显示登录 cardLayout.show(cardPanel, LOGIN); add(cardPanel); } public void navigateTo(String panelName) { cardLayout.show(cardPanel, panelName); } }表格数据绑定最佳实践// 使用AbstractTableModel实现数据绑定 class BookTableModel extends AbstractTableModel { private final ListBookDTO books; private final String[] COLUMNS {ISBN, 书名, 作者}; public BookTableModel(ListBookDTO books) { this.books Objects.requireNonNull(books); } Override public int getRowCount() { return books.size(); } Override public Object getValueAt(int row, int col) { BookDTO book books.get(row); return switch (col) { case 0 - book.isbn(); case 1 - book.title(); case 2 - book.author(); default - throw new IllegalArgumentException(); }; } // 更新数据方法 public void updateData(ListBookDTO newData) { this.books.clear(); this.books.addAll(newData); fireTableDataChanged(); // 触发UI更新 } }4. 关键功能模块实现对比4.1 用户登录流程对比控制台版本// 控制台登录示例 public class ConsoleLogin { public void login() { Scanner scanner new Scanner(System.in); System.out.print(用户名: ); String username scanner.nextLine(); System.out.print(密码: ); String password scanner.nextLine(); User user userService.authenticate(username, password); if (user null) { System.out.println(登录失败); return; } // 进入主流程... } }Swing版本// Swing登录面板 public class LoginPanel extends JPanel { private final JTextField usernameField new JTextField(20); private final JPasswordField passwordField new JPasswordField(20); private final JButton loginButton new JButton(登录); public LoginPanel() { setLayout(new GridBagLayout()); // 构建UI... loginButton.addActionListener(e - { String username usernameField.getText(); String password new String(passwordField.getPassword()); try { User user userService.authenticate(username, password); EventBus.publish(new LoginSuccessEvent(user)); } catch (AuthenticationException ex) { JOptionPane.showMessageDialog(this, 登录失败: ex.getMessage()); } }); } }4.2 图书查询功能对比三层架构优势体现维度控制台版本Swing三层架构版本查询条件处理硬编码在main方法中独立的SearchCriteria对象分页支持无通过Pageable接口实现异常处理控制台打印错误信息统一异常处理转弹窗提示数据绑定手动拼接输出字符串TableModel自动绑定单元测试难以测试可单独测试Service层5. 数据库设计与优化5.1 核心表结构CREATE TABLE books ( id BIGINT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(17) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, publish_date DATE, available BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE, password_hash VARCHAR(64) NOT NULL, role ENUM(ADMIN, LIBRARIAN, MEMBER) NOT NULL, active BOOLEAN DEFAULT TRUE ); CREATE TABLE loans ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, book_id BIGINT NOT NULL, loan_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (book_id) REFERENCES books(id), INDEX idx_loan_user (user_id), INDEX idx_loan_book (book_id) );5.2 查询性能优化实践问题场景需要同时显示图书信息和当前借阅状态低效方案-- N1查询问题 SELECT * FROM books WHERE title LIKE %Java%; -- 对每本书执行: SELECT * FROM loans WHERE book_id ? AND return_date IS NULL;优化方案-- 使用LEFT JOIN一次获取 SELECT b.*, l.id AS loan_id, l.user_id, l.loan_date, l.due_date FROM books b LEFT JOIN loans l ON b.id l.book_id AND l.return_date IS NULL WHERE b.title LIKE %Java%;Java17记录类映射结果public record BookWithLoanStatus( BookDTO book, OptionalLoanDTO activeLoan ) {} // 在Repository中 public ListBookWithLoanStatus findBooksWithLoanStatus(String keyword) { String sql SELECT b.*, l.id AS loan_id, l.user_id, l.loan_date, l.due_date FROM books b LEFT JOIN loans l ON b.id l.book_id AND l.return_date IS NULL WHERE b.title LIKE CONCAT(%,?,%) ; // 执行查询并映射结果... }6. 迁移路线图与实践建议6.1 从控制台到GUI的迁移步骤数据库重构添加必要的状态字段如available建立完整的外键关系添加索引优化查询分层架构搭建创建明确的包结构com.yourapp ├── dao ├── service ├── model └── ui功能迁移优先级先移植数据访问层保持单元测试覆盖率70%再实现核心业务逻辑最后开发Swing界面持续集成配置!-- 保证构建质量 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version2.22.2/version configuration includes include**/*Test.java/include /includes /configuration /plugin6.2 常见陷阱与解决方案陷阱1Swing线程安全问题解决方案始终在EDT中更新UISwingUtilities.invokeLater(() - { tableModel.updateData(newBooks); });陷阱2数据库连接泄漏解决方案使用try-with-resourcestry (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { // 操作数据库 }陷阱3业务逻辑渗透到UI层解决方案采用严格分层架构禁止UI层直接访问DAO7. 扩展思考与进阶方向7.1 架构演进可能性引入依赖注入使用Micronaut或Spring Core简化对象管理Singleton public class BookService { Inject private BookRepository bookRepo; }增加REST API层为未来移动应用做准备Path(/api/books) public class BookResource { GET public ListBookDTO getAll() { return bookService.findAll(); } }引入事件驱动架构使用观察者模式解耦组件// 借书成功事件 public class LoanCreatedEvent { private final LoanDTO loan; // 构造方法等... } // 监听器示例 public class NotificationService implements EventListenerLoanCreatedEvent { Override public void onEvent(LoanCreatedEvent event) { // 发送到期提醒等 } }7.2 性能优化进阶连接池配置// HikariCP配置示例 HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/library); config.setUsername(user); config.setPassword(password); config.setMaximumPoolSize(10); return new HikariDataSource(config);缓存策略实施public class CachedBookRepository implements BookRepository { private final BookRepository delegate; private final CacheString, BookDTO cache; Override public OptionalBookDTO findByIsbn(String isbn) { BookDTO cached cache.getIfPresent(isbn); if (cached ! null) { return Optional.of(cached); } OptionalBookDTO result delegate.findByIsbn(isbn); result.ifPresent(book - cache.put(isbn, book)); return result; } }批处理优化// 批量插入示例 public int[] batchInsert(ListBookDTO books) { String sql INSERT INTO books (isbn, title) VALUES (?,?); try (Connection conn dataSource.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { for (BookDTO book : books) { stmt.setString(1, book.isbn()); stmt.setString(2, book.title()); stmt.addBatch(); } return stmt.executeBatch(); } }