ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java+MySQL控制台版图书管理系统:从数据库设计到三层架构实战

2026/8/11 3:21:39 拓冰建站 浏览量
Java+MySQL控制台版图书管理系统:从数据库设计到三层架构实战 1. 项目概述为什么从控制台开始如果你正在学习Java或者想找一个能串联起数据库、面向对象和业务逻辑的实战项目那这个“图书管理系统JavaMySQL控制台版”绝对是个黄金起点。别小看“控制台”这三个字它意味着你将剥离掉所有花哨的前端界面直面最核心的数据流转与业务规则。这就像学武术先扎马步学编程先搞懂数据怎么存、怎么取、怎么被规则处理。我见过太多新手一上来就想搞带炫酷界面的Web应用结果被Spring Boot、Vue.js、RESTful API这些框架和概念绕得晕头转向最后连一个完整的“增删改查”流程都没理清。而这个控制台项目恰恰能帮你夯实地基。它的核心价值在于让你用最纯粹的Java SE和JDBC亲手构建一个从用户输入到数据库持久化的完整闭环。你会清晰地看到一段Scanner接收的字符串如何经过业务逻辑的校验与处理最终变成MySQL表中的一条记录反之亦然。整个过程涉及的技术栈非常经典Java基础集合、IO、面向对象、MySQL数据库操作SQL语句、JDBC连接、以及控制台交互设计。通过它你不仅能巩固Java语法更能深刻理解程序与数据库如何“对话”为后续学习任何Java EE或Spring框架打下坚实的逻辑基础。接下来我们就一步步拆解如何从零搭建一个结构清晰、易于维护的控制台版图书管理系统。2. 核心需求与功能模块设计在动手写代码之前我们必须把“图书管理”这件事拆解清楚。一个管理系统本质上是围绕“资源”图书的“状态”进行管理。我们需要明确系统有哪些角色虽然控制台版可能不区分管理员和普通用户但逻辑上要清晰、能执行哪些操作、数据如何定义。2.1 核心业务实体与属性定义首先我们需要抽象出系统中的核心对象。对于图书管理系统最核心的实体无疑是Book(图书)。一本图书至少包含以下属性id (主键)唯一标识通常为整数自增长。这是数据库层面的唯一性保证。isbn (国际标准书号)图书的唯一身份标识字符串类型。在业务上它也应该具备唯一性。name (书名)字符串。author (作者)字符串。publisher (出版社)字符串。price (价格)使用BigDecimal类型绝对不能用double或float以避免浮点数计算精度丢失的经典问题。stock (库存数量)整数。这是实现“借阅”和“归还”业务的关键字段。除了图书我们通常还需要BorrowRecord(借阅记录)实体来跟踪图书的流动情况。其属性包括id (记录ID)bookId (图书ID)外键关联到Book表。borrowerName (借阅人姓名)在简单的控制台系统中我们可以直接用字符串记录借阅人。borrowDate (借阅日期)returnDate (归还日期)初始为null归还后更新。通过是否为null可以判断图书是否已归还。注意在更复杂的系统中还会有User实体。但为了简化首个版本我们将借阅人信息直接放在借阅记录中聚焦核心流程。2.2 功能模块拆解基于上述实体我们可以将系统功能划分为以下几个模块图书信息管理模块新增图书将一本新书的信息录入系统存入数据库。查询图书支持按书名、作者、ISBN等条件进行模糊或精确查询并展示结果。修改图书信息更新某本图书的详细信息如价格、出版社。删除图书从系统中移除一本图书需考虑是否存在未归还的借阅记录即业务约束。图书借阅与归还模块借阅图书输入图书ID或ISBN和借阅人姓名减少图书库存并创建一条借阅记录。归还图书输入图书ID或记录ID增加图书库存并更新对应借阅记录的归还日期。查看借阅记录查询当前未归还的记录或所有历史记录。数据统计与展示模块展示所有图书分页或列表形式展示馆藏。统计信息如图书总数、当前可借阅数量等。这些功能将通过一个控制台菜单驱动用户输入数字选择要执行的操作。整个系统的架构雏形就在脑海里了一个main方法中的循环菜单调用不同的Service方法而Service则通过DAO层与数据库交互。3. 数据库设计与建表实战有了清晰的概念模型接下来就要把它落地到数据库中。我强烈建议使用MySQL Workbench或Navicat这类图形化工具来操作直观且不易出错。3.1 数据库与表结构SQL我们创建一个名为book_management的数据库并在其中创建两张核心表。-- 创建数据库 CREATE DATABASE IF NOT EXISTS book_management DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE book_management; -- 1. 图书表 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, isbn VARCHAR(20) UNIQUE NOT NULL COMMENT 国际标准书号唯一, name VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) NOT NULL COMMENT 作者, publisher VARCHAR(100) COMMENT 出版社, price DECIMAL(10, 2) NOT NULL COMMENT 价格精确到分, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, INDEX idx_name (name), INDEX idx_author (author) ) ENGINEInnoDB COMMENT图书信息表; -- 2. 借阅记录表 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 记录ID, book_id INT NOT NULL COMMENT 图书ID, borrower_name VARCHAR(50) NOT NULL COMMENT 借阅人姓名, borrow_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 借阅日期, return_date TIMESTAMP NULL COMMENT 归还日期, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, FOREIGN KEY (book_id) REFERENCES book(id) ON DELETE RESTRICT, INDEX idx_book_id (book_id), INDEX idx_borrower (borrower_name) ) ENGINEInnoDB COMMENT图书借阅记录表;3.2 设计要点与避坑指南字符集与排序规则使用utf8mb4和utf8mb4_unicode_ci这是MySQL中支持完整Emoji和全球字符的最佳实践能避免未来出现乱码问题。主键与自增id字段使用INT AUTO_INCREMENT作为代理主键简单高效。业务唯一标识如isbn则用UNIQUE约束。精确的金额存储price字段使用DECIMAL(10,2)表示总共10位数字其中2位小数。这是金融、价格类数据存储的铁律。时间戳管理create_time和update_time是审计字段帮你追踪数据生命周期。DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP让数据库自动维护它们非常省心。外键约束borrow_record.book_id关联book.id并使用了ON DELETE RESTRICT。这意味着如果一本书还有未归还的记录return_date为NULL那么这本书就不能被删除。这保证了数据的一致性是业务规则的数据库层面体现。索引创建为name,author,book_id,borrower_name等查询频繁的字段创建了普通索引(INDEX)可以大幅提高查询速度。主键和唯一约束本身就会创建索引。实操心得很多新手会忽略外键约束和时间戳字段。外键能帮你避免产生“幽灵借阅记录”书删了记录还在这种脏数据。而时间戳字段在排查问题比如“这本书什么时候被谁改过价格”时是无价之宝。一开始就加上习惯成自然。4. Java项目结构与核心代码实现现在我们进入编码环节。一个结构清晰的项目是成功的一半。我们采用经典的三层架构表现层UI、业务逻辑层Service、数据访问层DAO虽然控制台项目简单但遵循这个模式能培养良好的编码习惯。4.1 项目结构与Maven依赖使用Maven管理项目pom.xml关键依赖如下dependencies !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version !-- 请使用与你的MySQL服务器匹配的版本 -- /dependency !-- 日志框架方便调试 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version /dependency !-- 单元测试 -- dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version scopetest/scope /dependency /dependencies项目目录结构建议src/main/java/com/yourname/bookmanagement/ ├── Main.java # 程序入口控制台菜单循环 ├── model/ # 实体类 │ ├── Book.java │ └── BorrowRecord.java ├── dao/ # 数据访问层 │ ├── BaseDao.java # 封装通用数据库连接与关闭操作 │ ├── BookDao.java │ └── BorrowRecordDao.java ├── service/ # 业务逻辑层 │ ├── BookService.java │ └── BorrowService.java └── util/ # 工具类 └── DBUtil.java # 数据库连接池工具进阶4.2 核心实体类与数据库连接基类Book.java (模型层)package com.yourname.bookmanagement.model; import java.math.BigDecimal; import java.time.LocalDateTime; public class Book { private Integer id; private String isbn; private String name; private String author; private String publisher; private BigDecimal price; private Integer stock; private LocalDateTime createTime; private LocalDateTime updateTime; // 省略构造函数、Getter/Setter、toString方法 }注意使用BigDecimal表示价格使用LocalDateTime表示时间Java 8。如果与旧版MySQL驱动交互可能需要转换为java.sql.Timestamp。BaseDao.java (DAO层基石)这是整个数据访问层的核心负责管理数据库连接(Connection)、语句(PreparedStatement)和结果集(ResultSet)。资源泄露是Java数据库编程最常见的坑之一必须妥善关闭。package com.yourname.bookmanagement.dao; import java.sql.*; public abstract class BaseDao { // 数据库连接信息应配置在properties文件中这里硬编码仅为演示 private static final String URL jdbc:mysql://localhost:3306/book_management?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD yourpassword; protected Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 通用的关闭方法确保资源被释放 protected void closeResources(Connection conn, PreparedStatement pstmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (pstmt ! null) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } // 一个通用的更新方法用于INSERT, UPDATE, DELETE protected int executeUpdate(String sql, Object... params) { Connection conn null; PreparedStatement pstmt null; try { conn getConnection(); pstmt conn.prepareStatement(sql); // 设置参数 for (int i 0; i params.length; i) { pstmt.setObject(i 1, params[i]); } return pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return -1; // 返回-1表示失败实际项目中应抛出自定义异常 } finally { closeResources(conn, pstmt, null); } } }4.3 数据访问层DAO实现以BookDao为例DAO层是唯一直接和数据库打交道的地方每个方法对应一个具体的数据库操作。BookDao.javapackage com.yourname.bookmanagement.dao; import com.yourname.bookmanagement.model.Book; import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List; public class BookDao extends BaseDao { // 新增图书 public boolean addBook(Book book) { String sql INSERT INTO book(isbn, name, author, publisher, price, stock) VALUES(?, ?, ?, ?, ?, ?); int rows executeUpdate(sql, book.getIsbn(), book.getName(), book.getAuthor(), book.getPublisher(), book.getPrice(), book.getStock()); return rows 0; } // 根据ID查询图书 public Book findById(Integer id) { String sql SELECT * FROM book WHERE id ?; Connection conn null; PreparedStatement pstmt null; ResultSet rs null; Book book null; try { conn getConnection(); pstmt conn.prepareStatement(sql); pstmt.setInt(1, id); rs pstmt.executeQuery(); if (rs.next()) { book extractBookFromResultSet(rs); } } catch (SQLException e) { e.printStackTrace(); } finally { closeResources(conn, pstmt, rs); } return book; } // 根据书名模糊查询 public ListBook findByName(String nameKeyword) { ListBook bookList new ArrayList(); String sql SELECT * FROM book WHERE name LIKE ?; Connection conn null; PreparedStatement pstmt null; ResultSet rs null; try { conn getConnection(); pstmt conn.prepareStatement(sql); pstmt.setString(1, % nameKeyword %); rs pstmt.executeQuery(); while (rs.next()) { bookList.add(extractBookFromResultSet(rs)); } } catch (SQLException e) { e.printStackTrace(); } finally { closeResources(conn, pstmt, rs); } return bookList; } // 更新图书信息例如更新库存 public boolean updateBook(Book book) { String sql UPDATE book SET name?, author?, publisher?, price?, stock? WHERE id?; int rows executeUpdate(sql, book.getName(), book.getAuthor(), book.getPublisher(), book.getPrice(), book.getStock(), book.getId()); return rows 0; } // 根据ID删除图书注意外键约束必须先处理关联的借阅记录 public boolean deleteBook(Integer id) { String sql DELETE FROM book WHERE id ?; int rows executeUpdate(sql, id); return rows 0; } // 从ResultSet中提取Book对象的辅助方法避免代码重复 private Book extractBookFromResultSet(ResultSet rs) throws SQLException { Book book new Book(); book.setId(rs.getInt(id)); book.setIsbn(rs.getString(isbn)); book.setName(rs.getString(name)); book.setAuthor(rs.getString(author)); book.setPublisher(rs.getString(publisher)); book.setPrice(rs.getBigDecimal(price)); book.setStock(rs.getInt(stock)); // 处理时间戳转换这里简化处理 Timestamp createTs rs.getTimestamp(create_time); Timestamp updateTs rs.getTimestamp(update_time); if (createTs ! null) book.setCreateTime(createTs.toLocalDateTime()); if (updateTs ! null) book.setUpdateTime(updateTs.toLocalDateTime()); return book; } }4.4 业务逻辑层Service实现Service层是业务规则的守护者。它调用一个或多个DAO方法组成一个完整的业务操作并处理事务在简单控制台项目中我们暂不涉及复杂事务但要有这个概念。BookService.javapackage com.yourname.bookmanagement.service; import com.yourname.bookmanagement.dao.BookDao; import com.yourname.bookmanagement.model.Book; import java.util.List; public class BookService { private BookDao bookDao new BookDao(); public boolean addNewBook(String isbn, String name, String author, String publisher, String priceStr, String stockStr) { // 1. 参数校验业务规则 if (isbn null || isbn.trim().isEmpty()) { System.out.println(ISBN不能为空); return false; } // 可以添加更多校验如ISBN格式、价格正数等 // 2. 数据转换与封装 Book book new Book(); book.setIsbn(isbn.trim()); book.setName(name.trim()); book.setAuthor(author.trim()); book.setPublisher(publisher.trim()); try { book.setPrice(new java.math.BigDecimal(priceStr)); book.setStock(Integer.parseInt(stockStr)); } catch (NumberFormatException e) { System.out.println(价格或库存格式错误); return false; } // 3. 调用DAO执行持久化 return bookDao.addBook(book); } public ListBook searchBooks(String keyword) { // 简单的搜索逻辑同时按书名和作者模糊查询实际可能更复杂 // 这里为了演示只按书名查。一个更健壮的Service可能会调用一个支持多字段查询的DAO方法。 return bookDao.findByName(keyword); } // 借书业务涉及Book和BorrowRecord两个实体库存检查 public boolean borrowBook(Integer bookId, String borrowerName) { // 1. 检查图书是否存在且库存0 Book book bookDao.findById(bookId); if (book null) { System.out.println(图书不存在); return false; } if (book.getStock() 0) { System.out.println(该书库存不足); return false; } // 2. 减少库存 book.setStock(book.getStock() - 1); boolean updateSuccess bookDao.updateBook(book); // 3. 创建借阅记录需要BorrowRecordDao // BorrowRecordDao recordDao new BorrowRecordDao(); // boolean recordSuccess recordDao.addRecord(...); // return updateSuccess recordSuccess; // 简化版暂只返回更新库存成功与否 return updateSuccess; } // 其他方法还书、修改图书信息等... }核心要点Service层的方法名是“业务语言”如borrowBook它内部可能调用了多个DAO的“数据语言”方法findById,updateBook,addRecord。这里包含了库存检查的业务规则。4.5 控制台表现层与主程序入口这是用户直接交互的部分负责展示菜单、接收输入、调用Service并展示结果。Main.javapackage com.yourname.bookmanagement; import com.yourname.bookmanagement.service.BookService; import java.util.Scanner; public class Main { private static Scanner scanner new Scanner(System.in); private static BookService bookService new BookService(); public static void main(String[] args) { boolean running true; while (running) { showMainMenu(); int choice getIntInput(请选择操作); switch (choice) { case 1: addBookInteractive(); // 交互式新增图书 break; case 2: searchBooksInteractive(); break; case 3: borrowBookInteractive(); break; case 4: // 归还图书 break; case 5: // 查看所有图书 break; case 0: System.out.println(感谢使用再见); running false; break; default: System.out.println(输入无效请重新选择。); } System.out.println(); // 空行分隔 } scanner.close(); } private static void showMainMenu() { System.out.println( 图书管理系统 ); System.out.println(1. 新增图书); System.out.println(2. 查询图书); System.out.println(3. 借阅图书); System.out.println(4. 归还图书); System.out.println(5. 查看所有图书); System.out.println(0. 退出系统); System.out.println(); } private static void addBookInteractive() { System.out.println(--- 新增图书 ---); System.out.print(请输入ISBN: ); String isbn scanner.nextLine(); System.out.print(请输入书名: ); String name scanner.nextLine(); System.out.print(请输入作者: ); String author scanner.nextLine(); System.out.print(请输入出版社: ); String publisher scanner.nextLine(); System.out.print(请输入价格: ); String price scanner.nextLine(); System.out.print(请输入库存: ); String stock scanner.nextLine(); boolean success bookService.addNewBook(isbn, name, author, publisher, price, stock); if (success) { System.out.println(图书添加成功); } else { System.out.println(图书添加失败请检查输入。); } } private static void searchBooksInteractive() { System.out.print(请输入搜索关键词书名: ); String keyword scanner.nextLine(); var books bookService.searchBooks(keyword); if (books.isEmpty()) { System.out.println(未找到相关图书。); } else { System.out.println(找到 books.size() 本图书); for (Book book : books) { System.out.printf(ID: %d, ISBN: %s, 书名: 《%s》, 作者: %s, 价格: %.2f, 库存: %d%n, book.getId(), book.getIsbn(), book.getName(), book.getAuthor(), book.getPrice(), book.getStock()); } } } private static void borrowBookInteractive() { System.out.print(请输入要借阅的图书ID: ); int bookId getIntInput(); scanner.nextLine(); // 消耗换行符 System.out.print(请输入借阅人姓名: ); String borrower scanner.nextLine(); boolean success bookService.borrowBook(bookId, borrower); if (success) { System.out.println(借阅成功); } else { System.out.println(借阅失败); } } // 一个安全的获取整数输入的方法 private static int getIntInput(String prompt) { System.out.print(prompt); while (!scanner.hasNextInt()) { System.out.println(输入错误请输入一个整数。); System.out.print(prompt); scanner.next(); // 清除错误的输入 } int num scanner.nextInt(); scanner.nextLine(); // 非常重要清除输入缓冲区中的换行符避免影响后续nextLine() return num; } }5. 关键技术与进阶优化探讨完成基础版本后我们可以深入思考其中用到的关键技术点以及如何让这个小系统变得更健壮、更专业。5.1 JDBC连接管理与连接池在BaseDao中我们每次操作都DriverManager.getConnection()这是非常低效的。数据库连接的创建和销毁开销很大。生产环境或稍正式的项目中必须使用连接池。我们可以引入如HikariCP、Druid这样的高性能连接池。以HikariCP为例在DBUtil类中初始化一个全局的数据源package com.yourname.bookmanagement.util; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class DBUtil { private static final HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/book_management?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai); config.setUsername(root); config.setPassword(yourpassword); config.setMaximumPoolSize(10); // 连接池最大连接数 config.setMinimumIdle(5); // 最小空闲连接 config.setIdleTimeout(600000); // 空闲连接超时时间 config.setConnectionTimeout(30000); // 获取连接超时时间 dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 从池中获取而非新建 } public static void closeDataSource() { if (dataSource ! null !dataSource.isClosed()) { dataSource.close(); } } }然后在BaseDao中将getConnection()方法改为调用DBUtil.getConnection()。连接池会自动管理连接的创建、复用和关闭性能提升巨大。5.2 异常处理与事务控制目前的代码中异常只是简单打印e.printStackTrace()然后返回false或null。在实际项目中这是不够的。自定义业务异常创建如BookNotFoundException,InsufficientStockException等异常类在Service层抛出在Main层捕获并给用户友好提示。事务控制像“借书”这样的操作涉及更新book表和插入borrow_record表必须保证要么都成功要么都失败。这需要事务。在纯JDBC中可以这样Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 更新图书库存 // 2. 插入借阅记录 conn.commit(); // 提交事务 } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { // 关闭连接前恢复自动提交模式并将连接还给连接池 if (conn ! null) { try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }5.3 输入校验与数据格式化控制台程序的用户体验很大程度上取决于输入校验。我们之前的getIntInput方法是一个好的开始但还不够。更健壮的输入对于价格需要校验是否为非负数字对于日期可以尝试解析特定格式。数据格式化输出在显示图书列表时使用System.out.printf进行对齐让输出更美观。可以考虑将“显示一本书”的逻辑封装成一个方法。防止SQL注入我们全程使用PreparedStatement并通过setObject设置参数这已经从根本上防止了SQL注入。这是必须遵守的安全底线。5.4 日志记录用System.out.println打日志不利于后期维护和问题排查。应该集成SLF4J Logback或Log4j2。import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BookService { private static final Logger LOGGER LoggerFactory.getLogger(BookService.class); public boolean borrowBook(...) { LOGGER.info(用户[{}]尝试借阅图书ID:[{}], borrowerName, bookId); try { // ...业务逻辑 LOGGER.info(借阅成功。); } catch (Exception e) { LOGGER.error(借阅图书时发生异常, e); return false; } } }良好的日志级别DEBUG, INFO, WARN, ERROR和上下文信息是线上调试的利器。6. 常见问题排查与调试技巧在开发过程中你肯定会遇到各种问题。这里记录几个典型场景和解决思路。6.1 数据库连接失败现象Communications link failure或Access denied for user。排查步骤检查MySQL服务确保MySQL服务已启动sudo systemctl status mysql或 查看Windows服务。检查连接参数URL中的IP、端口、数据库名是否正确。serverTimezone参数在MySQL 8.0中经常是必须的。检查用户名密码确认无误。注意MySQL 8.0默认使用caching_sha2_password认证旧版驱动可能不支持可考虑在MySQL中为用户改用mysql_native_password插件。检查网络与防火墙本地连接一般没问题如果是远程数据库检查防火墙是否开放了3306端口。检查驱动版本确保mysql-connector-java的版本与MySQL服务器版本兼容。6.2 中文乱码问题现象控制台或数据库里显示???。解决方案数据库层面建库建表时使用utf8mb4字符集如前文SQL所示。连接层面JDBC URL中必须包含useUnicodetruecharacterEncodingutf8注意这里写utf8JDBC驱动会正确处理为utf8mb4。Java程序层面确保你的Java源文件编码是UTF-8IDE中可设置。控制台输入时某些Windows终端默认编码是GBK可能需要额外处理但现代IDE的终端通常没问题。6.3 时间日期类型处理问题Java 8的LocalDateTime与MySQL的DATETIME/TIMESTAMP转换。方案驱动兼容使用较新的MySQL驱动8.0它直接支持与Java 8时间类型的转换。手动转换如果遇到问题可以在DAO层手动转换// 从ResultSet读 Timestamp timestamp rs.getTimestamp(create_time); LocalDateTime createTime timestamp ! null ? timestamp.toLocalDateTime() : null; // 向PreparedStatement写 pstmt.setTimestamp(5, Timestamp.valueOf(book.getCreateTime()));6.4 业务逻辑错误借了不存在的书现象借阅记录创建成功但图书库存没减少或者反之。原因与解决这通常是事务没处理好导致的。两个数据库操作不在同一个事务里一个成功一个失败。必须按照5.2节所述将相关的数据库操作放在一个事务内。在更复杂的业务中考虑使用java.sql.Savepoint进行更细粒度的事务控制。6.5 性能问题查询慢现象当图书数据量上万后模糊查询变慢。优化索引确保查询条件字段如name,author已建立索引。但注意LIKE %关键词%这种前置通配符的模糊查询索引可能失效。分页查询不要一次SELECT *所有数据。使用LIMIT offset, size进行分页。在DAO中增加findByNameWithPagination方法。SQL优化使用EXPLAIN分析慢查询SQL查看执行计划。这个“图书管理系统JavaMySQL控制台版”项目麻雀虽小五脏俱全。它强迫你思考实体关系、数据流、业务规则和代码结构。当你能够流畅地实现它并且理解上面提到的每一个优化点时你对Java和数据库编程的理解就已经超越了大多数初学者。接下来你可以尝试为它添加图形界面Swing/JavaFX或者将其改造成一个Web应用Spring Boot那时你会发现核心的业务逻辑和数据层代码几乎可以无缝迁移。这才是扎实的进步。