简介这是一套面向Java初学者与课程设计者的图书管理系统完整项目源码基于IntelliJ IDEA开发适合用于毕业设计、课程作业或Java Web入门实战。项目围绕用户、书籍、借阅三大模块展开涵盖注册登录、权限控制、图书增删改查及借还流程并采用MVC设计模式与RESTful接口思路组织代码帮助读者理解从数据库设计到前后端交互的完整链路。压缩包共68个文件约7.09MB包含16个java源文件与16个class编译文件、5个html与2个jsp页面、5个xml配置、4个jar依赖以及png图片、js脚本、css样式和docx设计模式类图等结构清晰便于对照学习。目前已有1642人学习下载读者可借此掌握IDEA环境配置、Servlet与Service分层、数据库操作及项目部署等关键技能快速积累一个可运行的Java Web实践案例。1. 从一次课程设计翻车说起图书管理系统到底该怎么做很多人第一次接触 Java 课程设计选的就是图书管理系统。原因很实在需求看得懂、业务不复杂、能塞进面向对象编程、集合、IO、JDBC 这些知识点。但真正动手才发现翻车点根本不在业务逻辑而在环境。IDEA 社区版下载完新建项目选错 JDKMaven 依赖拉不下来数据库驱动版本对不上控制台报ClassNotFoundException一上午就没了。这篇笔记就围绕「基于 IDEA 的图书管理系统」这条主线把环境搭建、分层设计、数据库落地、功能实现和排错串成一条能照着走的路径。适合正在做课程设计的学生、刚转 Java 的初学者以及想用一个小项目把 Java 基础串起来的开发工程师。读完你至少能拿到一个可运行、可扩展、能写进简历的版本而不是一个只能跑一次的 demo。2. 环境与选型IDEA 里跑通图书管理系统的前置条件2.1 JDK、IDEA 与构建工具怎么配才不返工图书管理系统本质是一个 Java SE 或 Java Web 项目。如果只做控制台版JDK 17 加 IDEA 社区版就够如果要做带界面的 Web 版还需要 Tomcat 和 Maven。我一般建议课程设计直接上 JDK 17 Maven MySQL 8原因是这套组合资料最多遇到问题最容易搜到答案。IDEA 社区版没有 Spring Boot 初始化和数据库工具但写纯 Java 项目完全够用不需要去折腾激活码那类东西社区版下载安装后把 JDK 配好就能开工。安装完 JDK 后在命令行验证java -version javac -version两条命令输出的版本号必须一致。很多人只配了JAVA_HOME却忘了把%JAVA_HOME%\bin加进Path结果java能跑、javac找不到IDEA 里编译直接失败。IDEA 中还要再确认一次File - Project Structure - Project把 Project SDK 指到你安装的 JDKLanguage level 选 17。这一步不做后面新建类时连record和var都用不了。Maven 的作用是管依赖。图书管理系统至少需要 MySQL 驱动和连接池。在pom.xml里写清楚dependencies !-- MySQL 驱动版本要和本地 MySQL 大版本匹配 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version /dependency !-- HikariCP 连接池比手写 DriverManager 更稳 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.1.0/version /dependency /dependencies参数说明mysql-connector-j是 MySQL 8 之后的官方驱动坐标老教程里的mysql-connector-java在 8.0.31 之后已经改名照抄旧坐标会拉不到包。HikariCP 的版本要跟 JDK 匹配JDK 17 用 5.xJDK 8 用 4.x。写完pom.xml后点 IDEA 右侧 Maven 面板的刷新按钮依赖变蓝才算成功。如果一直转圈检查 Maven 镜像是否配了国内源settings.xml里加阿里云镜像能省很多时间。2.2 分层结构为什么图书管理系统一定要分 dao、service、ui新手最容易犯的错是把所有代码写进一个Main类菜单、输入、SQL 全混在一起。功能少的时候能跑一旦要加「按作者模糊查询」或者「借书时校验库存」改一处崩三处。图书管理系统虽然小但它是练分层最好的载体。常见做法是分四层实体层entity、数据访问层dao、业务层service、界面层ui。实体层放Book、User、BorrowRecorddao 层只负责增删改查 SQLservice 层做业务校验比如借书前判断库存是否大于零ui 层只处理菜单和输入输出。这样分的好处是控制台版和 Web 版可以共用 entity、dao、service只换 ui 层。后面想把控制台换成 Swing 或者 Servlet业务代码一行不用动。目录结构建议这样建src/main/java/com/example/library ├── entity │ ├── Book.java │ ├── User.java │ └── BorrowRecord.java ├── dao │ ├── BookDao.java │ └── UserDao.java ├── service │ └── BookService.java ├── util │ └── DBUtil.java └── ui └── MainMenu.javautil包里放数据库连接工具类全局一个连接池避免每次查询都新建连接。这一步是很多课程设计忽略的地方但恰恰是「java 怎么保证数据一致性」这类面试题会追问的点连接池配合事务才能保证借书时「扣库存」和「写借阅记录」要么都成功要么都回滚。3. 数据库与实体映射图书管理系统落地的核心一步3.1 建表语句与三个关键字段设计图书管理系统的数据库不复杂但字段设计有讲究。至少三张表book、user、borrow_record。建表时把主键、唯一索引、外键想清楚后面写 SQL 会顺很多。CREATE TABLE book ( id BIGINT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), stock INT NOT NULL DEFAULT 0, total INT NOT NULL DEFAULT 0 ); CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT reader ); CREATE TABLE borrow_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, book_id BIGINT NOT NULL, user_id BIGINT NOT NULL, borrow_date DATE NOT NULL, return_date DATE, status TINYINT NOT NULL DEFAULT 0, FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (user_id) REFERENCES user(id) );参数说明isbn加唯一索引防止同一本书重复录入stock表示可借数量total表示馆藏总量还书时 stock 加一但不能超过 totalstatus用 0 表示未还、1 表示已还比直接删记录更利于统计。外键约束在课程设计里建议加上能帮你提前发现脏数据。如果用的是 MySQL 8注意默认字符集选utf8mb4否则书名里的特殊字符会乱码。3.2 用 JDBC 写一个可复用的 DBUtil连接数据库的代码只写一次放在DBUtil里。用 HikariCP 管理连接比DriverManager每次新建连接快得多也更接近真实项目。public class DBUtil { private static final HikariDataSource DATA_SOURCE; static { HikariConfig config new HikariConfig(); // 时区必须写否则 MySQL 8 会报 serverTimezone 错误 config.setJdbcUrl(jdbc:mysql://localhost:3306/library?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8); config.setUsername(root); config.setPassword(your_password); config.setMaximumPoolSize(10); config.setMinimumIdle(2); DATA_SOURCE new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return DATA_SOURCE.getConnection(); } }逻辑说明静态代码块在类加载时执行一次连接池只初始化一次。maximumPoolSize设 10 对课程设计足够设太大反而占资源。serverTimezone不写MySQL 8 驱动会直接抛异常这是新手最常见的翻车点之一。characterEncodingutf8保证中文书名不乱码。拿到连接后所有 dao 方法都用 try-with-resources 关闭PreparedStatement和ResultSet连接由连接池回收不要手动 close。3.3 实体类与 ResultSet 的手动映射实体类用普通 JavaBean 就行字段名和表字段对应但用驼峰命名。比如表里的borrow_date对应 Java 的borrowDate。映射时手动写别急着上 ORM 框架课程设计阶段手写一遍才能理解 JDBC 的流程。public class Book { private Long id; private String isbn; private String title; private String author; private Integer stock; private Integer total; // 省略 getter/setter }dao 层查询示例public ListBook findAll() throws SQLException { String sql SELECT id, isbn, title, author, stock, total FROM book; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getLong(id)); b.setIsbn(rs.getString(isbn)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setStock(rs.getInt(stock)); b.setTotal(rs.getInt(total)); list.add(b); } } return list; }参数说明用PreparedStatement而不是Statement能防 SQL 注入这是面试常问的点。rs.getLong(id)里的列名要和 SQL 里的别名一致写错会抛ColumnNotFoundException。如果字段允许为空用rs.getInt返回 0 而不是 null需要区分时改用rs.getObject。4. 功能实现借书、还书与查询的完整链路4.1 借书业务为什么要放在 service 层做事务借书不是一条 SQL 能搞定的。它要同时做三件事检查库存、扣减库存、插入借阅记录。如果扣完库存插入记录时失败库存就白白少了。所以必须放在一个事务里。public boolean borrowBook(Long bookId, Long userId) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 扣库存stock 0 才更新避免超借 String updateSql UPDATE book SET stock stock - 1 WHERE id ? AND stock 0; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setLong(1, bookId); if (ps.executeUpdate() 0) { conn.rollback(); return false; // 库存不足 } } // 2. 插入借阅记录 String insertSql INSERT INTO borrow_record(book_id, user_id, borrow_date, status) VALUES(?, ?, CURDATE(), 0); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setLong(1, bookId); ps.setLong(2, userId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }逻辑说明setAutoCommit(false)开启事务两步都成功才 commit任何一步失败就 rollback。UPDATE ... WHERE stock 0这个条件很关键它把「检查」和「扣减」合并成一条原子操作避免并发下超借。参数说明CURDATE()是 MySQL 函数取当前日期status0表示未还。finally 里恢复自动提交再关闭连接否则连接回到池里还带着事务状态下一个使用者会莫名其妙。4.2 还书与库存回补的边界处理还书逻辑比借书多一个判断这条借阅记录是不是已经还过了。如果重复还书库存会被多加。public boolean returnBook(Long recordId) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 把未还记录改成已还status0 才更新 String updateRecord UPDATE borrow_record SET status 1, return_date CURDATE() WHERE id ? AND status 0; try (PreparedStatement ps conn.prepareStatement(updateRecord)) { ps.setLong(1, recordId); if (ps.executeUpdate() 0) { conn.rollback(); return false; // 记录不存在或已还 } } // 2. 回补库存但不能超过 total String updateStock UPDATE book b JOIN borrow_record r ON b.id r.book_id SET b.stock b.stock 1 WHERE r.id ? AND b.stock b.total; try (PreparedStatement ps conn.prepareStatement(updateStock)) { ps.setLong(1, recordId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }参数说明status 0作为更新条件保证同一条记录只能还一次。b.stock b.total防止库存超过馆藏总量。这里用 JOIN 更新一条 SQL 同时关联两张表比先查再改更安全。如果业务要求记录还书操作员可以在 borrow_record 里加operator_id字段。4.3 模糊查询与分页别让全表扫描拖垮界面图书列表一多一次性SELECT *会卡。加一个按书名模糊查询加分页的接口。public ListBook searchByTitle(String keyword, int page, int size) throws SQLException { String sql SELECT id, isbn, title, author, stock, total FROM book WHERE title LIKE ? LIMIT ? OFFSET ?; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, size); ps.setInt(3, (page - 1) * size); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getLong(id)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setStock(rs.getInt(stock)); list.add(b); } } } return list; }参数说明LIMIT ? OFFSET ?是 MySQL 分页写法page 从 1 开始offset 等于(page-1)*size。LIKE的%拼在 Java 里不要拼进 SQL 字符串否则又回到 SQL 注入的老路。如果数据量上万LIKE %keyword%用不上索引可以考虑全文索引但课程设计阶段没必要。5. 避坑与排查IDEA 里图书管理系统最常见的 5 个翻车点5.1 现象控制台报 ClassNotFoundException: com.mysql.cj.jdbc.Driver原因驱动依赖没进 classpath或者pom.xml里坐标写成了旧的mysql-connector-javaMaven 没拉下来。解决确认pom.xml用的是com.mysql:mysql-connector-j然后在 IDEA 右侧 Maven 面板点刷新等依赖下载完。如果还不行File - Project Structure - Modules - Dependencies里看有没有这个 jar没有就手动加。另外JDBC URL 里驱动类名在 MySQL 8 是com.mysql.cj.jdbc.Driver老教程写的com.mysql.jdbc.Driver已经废弃。5.2 现象中文书名存进数据库变成问号原因数据库、表、连接三处字符集不一致。解决建库时用CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4JDBC URL 加characterEncodingutf8IDEA 的File Encodings里把 Global 和 Project 都设成 UTF-8。三处都对了才不会乱码。如果已经存了乱码数据改字符集救不回来只能重新录入。5.3 现象IDEA 不显示 target 目录但磁盘上确实存在原因IDEA 把 target 标记为排除目录或者 Maven 没有执行编译。解决先执行mvn clean compile然后在项目树右上角齿轮里勾选Show Excluded Files。如果 target 是空的说明编译没成功看 Build 窗口的报错。这个现象本身不影响运行但会让人误以为代码没编译。5.4 现象借书时库存扣成负数原因并发下两个线程同时读到 stock1都判断通过各自扣一次。解决把判断和扣减合并成一条UPDATE book SET stock stock - 1 WHERE id ? AND stock 0根据executeUpdate()返回值判断是否成功。这是最省事的乐观锁写法不需要额外加锁。如果业务更复杂可以在 book 表加version字段做版本控制。5.5 现象IDEA 代码格式化失效快捷键没反应原因快捷键被输入法或其他软件占用或者格式化配置被改乱。解决File - Settings - Keymap搜Reformat Code看绑定的快捷键是不是被覆盖重新绑一个不冲突的。如果是配置文件问题Settings - Editor - Code Style - Java里恢复默认。这个坑不影响功能但写代码时很影响心情早点解决。6. 进阶技巧把图书管理系统改造成能写进简历的项目课程设计做完能跑只是及格线。想让这个项目在面试里拿得出手得加一点真实项目里才有的东西。我一般会从三个方向改加登录鉴权、加操作日志、加单元测试。登录鉴权不用上 Spring Security自己写一个简单的 Session 或 Token 就够。用户登录后把userId和role存进一个全局的CurrentUser对象借书时从里面取而不是让界面层传参。这样 service 层的方法签名更干净也符合「行级权限」的思路普通读者只能查自己的借阅记录管理员能查全部。实现时在 dao 查询里加WHERE user_id ?参数从CurrentUser取。操作日志用 AOP 或者手动在 service 里记都行。建一张operation_log表字段包括操作人、操作类型、目标 id、时间。每次借书、还书、新增图书后插一条。面试时被问到「怎么排查线上问题」你可以说通过操作日志定位到具体是谁在什么时候改了数据这比空谈理论有说服力。单元测试用 JUnit 5重点测 service 层的边界库存为 0 时借书返回 false、重复还书返回 false、还书后库存不超过 total。测试数据库用 H2 内存库不依赖本地 MySQL跑起来快。示例Test void borrowBook_shouldFail_whenStockIsZero() throws SQLException { // 准备一本库存为 0 的书 BookService service new BookService(); boolean result service.borrowBook(999L, 1L); assertFalse(result); }参数说明测试方法名用「被测方法_预期结果_条件」的格式读起来清楚。断言用assertFalse、assertEquals不要只打印结果。跑测试时如果报连接不上数据库检查测试用的配置文件是不是指向了 H2 而不是 MySQL。最后说一个我自己的习惯每加一个功能先在纸上画一遍数据流从界面输入到 SQL 执行再到返回确认每一步的入参和出参。图书管理系统看着简单但借书这条链路涉及库存、记录、用户三个实体画清楚再写比写完再 debug 省时间。这个项目值不值得做如果你刚学完 Java 基础想找一个能把面向对象、集合、JDBC、事务串起来的载体它非常合适如果你已经工作两年可以把它当成练分层和测试的靶子改造成带鉴权和日志的版本。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?