
1. 这篇文章真正要解决的问题如果你是一名在校学生或者正在寻找一个能快速上手、功能完整的Java Web项目来丰富简历那么“图书管理系统”这个名字你一定不陌生。它几乎是每个计算机专业学生课程设计或毕业设计的“标配”。然而问题也恰恰出在这里网上充斥着大量雷同、代码质量堪忧、甚至无法运行的“图书管理系统”源码。你下载下来要么环境配置报错要么逻辑混乱要么功能残缺最终不仅没学到东西反而浪费了大量时间在无意义的排错上。这篇文章要解决的就是帮你绕开这些“坑”。我们不会给你一个又一个的“源码压缩包”然后让你自己去猜怎么用。相反我们会基于一个经过验证的、结构清晰的图书管理系统项目从零开始带你完成从环境搭建、数据库设计、核心功能实现到最终部署上线的完整流程。更重要的是我们会剖析这个经典项目背后的设计思想和工程实践让你明白为什么代码要这么写数据库要这么设计而不仅仅是“复制粘贴”。读完本文你将获得一个可直接运行、功能完整的图书管理系统项目包含用户管理、图书管理、借阅/归还、查询统计等核心模块。清晰的MVC三层架构理解知道Controller、Service、Dao每一层该做什么如何协作。从需求到实现的完整开发思维学会如何将一个业务需求如“借书”拆解为数据库操作、业务逻辑和页面交互。一套可复用的项目搭建与排错经验无论是SSMSpringSpringMVCMyBatis还是Spring Boot项目你都能触类旁通。2. 基础概念与核心原理在动手之前我们需要统一几个关键概念这能帮助你在后续编码时清楚地知道每一行代码的意义。2.1 什么是图书管理系统本质上它是一个针对图书馆或图书借阅场景的信息管理系统MIS。核心目标是管理两类实体图书和用户以及它们之间的交互行为借阅和归还。所有功能都围绕“增删改查”CRUD展开但比简单的学生管理系统更复杂因为它涉及状态如图书是否在馆和业务规则如借阅期限、超期罚款。2.2 为什么选择Java Web技术栈对于此类管理系统Java Web是经久不衰的选择。其优势在于生态成熟Spring框架提供了强大的依赖注入、事务管理和Web MVC支持。ORM友好MyBatis或JPA能极大简化数据库操作。结构清晰强制性的分层架构MVC有助于培养良好的编码习惯适合教学和入门。企业级应用广泛学会这套技术栈对你理解后端开发有直接帮助。2.3 核心架构MVC模式这是本项目的骨架务必理解Model模型代表数据和业务规则。在本项目中主要包括实体类Entity/POJO如Book、User、BorrowRecord对应数据库表。数据访问层DAO/Mapper使用MyBatis负责与数据库直接交互执行SQL。业务逻辑层Service包含复杂的业务规则如“借书前检查用户是否超期”、“还书时计算罚款”。它调用DAO层并被Controller层调用。View视图用户看到的界面。我们使用JSP或Thymeleaf模板来渲染HTML页面。Controller控制器接收用户的HTTP请求如点击“借阅”按钮调用相应的Service方法处理业务然后选择合适的视图返回给用户。流程类比可以把图书馆管理员Controller看作前台读者提出借书请求。管理员查阅借阅规则手册Service层逻辑然后去书库DAO层查找图书并办理手续最后把结果成功或失败告知读者View。3. 环境准备与前置条件工欲善其事必先利其器。请确保你的开发环境包含以下组件。版本号是推荐配置轻微差异通常不影响。操作系统Windows 10/11, macOS, Linux 均可。Java开发工具包JDK版本 8 或 11LTS长期支持版。推荐 OpenJDK 11。# 验证安装 java -version集成开发环境IDEIntelliJ IDEA Ultimate推荐或 Eclipse。IDEA对Spring Boot支持更好。项目管理与构建工具Apache Maven。负责管理项目依赖Jar包。# 验证安装 mvn -v数据库MySQL 5.7 或 8.0。我们将用它存储所有数据。版本控制可选但强烈推荐Git。用于代码版本管理。4. 核心流程拆解一个完整的图书管理系统开发流程可以分解为以下六个关键步骤。我们将按此顺序推进需求分析与数据库设计明确系统要做什么并转化为数据库表结构。项目骨架搭建使用Spring Boot Initializr创建项目配置依赖。实体类与数据层开发创建Java实体类并使用MyBatis编写操作数据库的Mapper。业务逻辑层开发实现借书、还书、查询等核心业务规则。控制层与页面开发接收请求调用业务返回页面。功能测试与优化运行系统测试所有功能并考虑添加登录、分页等增强功能。5. 数据库设计与SQL脚本这是系统的基石。设计不当会导致后期代码复杂、性能低下。我们设计核心三张表5.1 表结构设计用户表 (user)存储读者信息。图书表 (book)存储图书信息。借阅记录表 (borrow_record)核心表记录谁在何时借了哪本书以及归还情况。5.2 SQL建表脚本在你的MySQL中创建一个名为library_db的数据库然后执行以下SQL-- 创建数据库 CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE library_db; -- 用户表 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 用户名登录用, password varchar(255) NOT NULL COMMENT 密码存储密文, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 电话, user_type tinyint(1) NOT NULL DEFAULT 1 COMMENT 用户类型0-管理员1-普通读者, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态0-禁用1-正常, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 插入初始管理员和测试用户 (密码均为加密后的123456) INSERT INTO user (username, password, real_name, user_type) VALUES (admin, $2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iK6WM8kF/z8tZR.9qjqgOPkjOqF2, 系统管理员, 0), (reader1, $2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iK6WM8kF/z8tZR.9qjqgOPkjOqF2, 张三, 1); -- 图书表 CREATE TABLE book ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 图书ID, isbn varchar(20) NOT NULL COMMENT 国际标准书号, name varchar(200) NOT NULL COMMENT 图书名称, author varchar(100) DEFAULT NULL COMMENT 作者, publisher varchar(100) DEFAULT NULL COMMENT 出版社, publish_date date DEFAULT NULL COMMENT 出版日期, price decimal(10,2) DEFAULT NULL COMMENT 价格, total_count int(11) NOT NULL DEFAULT 0 COMMENT 总数量, available_count int(11) NOT NULL DEFAULT 0 COMMENT 可借数量, location varchar(100) DEFAULT NULL COMMENT 馆藏位置, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态0-下架1-在架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, PRIMARY KEY (id), UNIQUE KEY uk_isbn (isbn), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; -- 插入测试图书数据 INSERT INTO book (isbn, name, author, publisher, price, total_count, available_count) VALUES (978-7-111-59927-5, Spring Boot实战, Craig Walls, 机械工业出版社, 89.00, 5, 5), (978-7-121-42116-8, MySQL必知必会, Ben Forta, 电子工业出版社, 49.00, 3, 3), (978-7-115-52315-7, Java核心技术 卷I, Cay S. Horstmann, 人民邮电出版社, 119.00, 2, 2); -- 借阅记录表 (核心业务表) CREATE TABLE borrow_record ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 记录ID, user_id int(11) NOT NULL COMMENT 借阅用户ID, book_id int(11) NOT NULL COMMENT 借阅图书ID, borrow_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 借出时间, expected_return_time datetime NOT NULL COMMENT 应还时间, actual_return_time datetime DEFAULT NULL COMMENT 实际归还时间, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态1-借阅中2-已归还3-超期归还, overdue_fine decimal(10,2) DEFAULT 0.00 COMMENT 超期罚款金额, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_book_id (book_id), KEY idx_status (status), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE CASCADE, CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT借阅记录表;设计要点解析外键约束borrow_record表通过user_id和book_id关联用户和图书并设置了外键约束保证了数据的一致性不能借不存在的书。状态字段book.available_count是关键。借书时减1还书时加1。通过它可以直接判断图书是否可借避免频繁联表查询borrow_record。密码存储user.password字段我们直接插入了BCrypt加密后的密文明文是‘123456’这是生产环境的基本要求绝对不要明文存储密码。索引为经常用于查询的字段如book.name,borrow_record.user_id添加了索引以提升查询性能。6. 项目骨架搭建与配置我们将使用Spring Boot来快速构建项目它集成了Spring MVC、MyBatis等常用框架。6.1 创建Spring Boot项目使用IntelliJ IDEA的Spring Initializr创建项目Project: MavenLanguage: JavaSpring Boot: 2.7.x 或 3.x (本文以2.7.18为例兼容性更广)Project Metadata:Group:com.exampleArtifact:library-managementPackaging: JarJava: 11Dependencies: 添加以下依赖Spring Web(用于构建Web应用)MyBatis Framework(集成MyBatis)MySQL Driver(连接MySQL数据库)Lombok(简化实体类代码可选但推荐)6.2 关键配置文件application.yml创建或修改src/main/resources/application.yml文件配置数据库连接和MyBatis# 应用配置 server: port: 8080 servlet: context-path: /library spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # 请修改为你本地的数据库地址、端口、库名、用户名和密码 url: jdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password_here # JPA/Hibernate 相关即使不用JPA也建议关闭DDL自动更新避免破坏现有表结构 jpa: hibernate: ddl-auto: none show-sql: true # MyBatis 配置 mybatis: # mapper.xml 文件的位置 mapper-locations: classpath:mapper/*.xml # 实体类所在的包用于别名 type-aliases-package: com.example.librarymanagement.entity configuration: # 开启驼峰命名自动映射数据库字段 user_name 映射到Java属性 userName map-underscore-to-camel-case: true # 打印SQL日志调试时非常有用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志级别方便调试 logging: level: com.example.librarymanagement.mapper: debug7. 实体类与数据层开发7.1 创建实体类根据数据库表在src/main/java/com/example/librarymanagement/entity/包下创建对应的Java类。User.java:package com.example.librarymanagement.entity; import lombok.Data; import java.util.Date; Data public class User { private Integer id; private String username; private String password; private String realName; private String phone; private Integer userType; // 0-管理员1-读者 private Integer status; // 0-禁用1-正常 private Date createTime; }Book.java和BorrowRecord.java类似使用Data注解自动生成getter/setter等方法。7.2 创建Mapper接口在src/main/java/com/example/librarymanagement/mapper/包下创建数据访问接口。BookMapper.java:package com.example.librarymanagement.mapper; import com.example.librarymanagement.entity.Book; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; Mapper // 关键注解让Spring Boot能扫描到这个接口并创建代理对象 public interface BookMapper { // 插入一本新书 int insert(Book book); // 根据ID删除 int deleteById(Integer id); // 更新图书信息 int updateById(Book book); // 根据ID查询 Book selectById(Integer id); // 根据ISBN查询 Book selectByIsbn(String isbn); // 条件查询图书列表动态SQL会在XML中编写 ListBook selectByCondition(Param(name) String name, Param(author) String author, Param(status) Integer status); // 更新图书可借数量借书或还书时调用 int updateAvailableCount(Param(id) Integer id, Param(delta) Integer delta); }7.3 编写Mapper XML文件在src/main/resources/mapper/目录下创建BookMapper.xml实现接口中定义的SQL。?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.librarymanagement.mapper.BookMapper resultMap idBaseResultMap typeBook id columnid propertyid/ result columnisbn propertyisbn/ result columnname propertyname/ result columnauthor propertyauthor/ result columnpublisher propertypublisher/ result columnpublish_date propertypublishDate/ result columnprice propertyprice/ result columntotal_count propertytotalCount/ result columnavailable_count propertyavailableCount/ result columnlocation propertylocation/ result columnstatus propertystatus/ result columncreate_time propertycreateTime/ /resultMap sql idBase_Column_List id, isbn, name, author, publisher, publish_date, price, total_count, available_count, location, status, create_time /sql insert idinsert parameterTypeBook useGeneratedKeystrue keyPropertyid INSERT INTO book (isbn, name, author, publisher, publish_date, price, total_count, available_count, location, status) VALUES (#{isbn}, #{name}, #{author}, #{publisher}, #{publishDate}, #{price}, #{totalCount}, #{availableCount}, #{location}, #{status}) /insert select idselectByCondition resultMapBaseResultMap SELECT include refidBase_Column_List/ FROM book WHERE 11 if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testauthor ! null and author ! AND author LIKE CONCAT(%, #{author}, %) /if if teststatus ! null AND status #{status} /if ORDER BY create_time DESC /select update idupdateAvailableCount UPDATE book SET available_count available_count #{delta} WHERE id #{id} AND available_count #{delta} 0 /update !-- 注意这个SQL包含了业务逻辑校验确保可借数量不会变成负数 -- /mapperUserMapper.xml和BorrowRecordMapper.xml需要类似地创建。8. 业务逻辑层开发Service层是业务规则的核心。我们在src/main/java/com/example/librarymanagement/service/包下创建服务接口和实现类。BookService.java(接口):package com.example.librarymanagement.service; import com.example.librarymanagement.entity.Book; import java.util.List; public interface BookService { // 新增或更新图书 boolean saveOrUpdate(Book book); // 删除图书 boolean deleteById(Integer id); // 根据ID查询 Book getById(Integer id); // 条件查询 ListBook getListByCondition(String name, String author, Integer status); // 借书核心业务 boolean borrowBook(Integer bookId, Integer userId); // 还书核心业务 boolean returnBook(Integer recordId); }BookServiceImpl.java(实现类):package com.example.librarymanagement.service.impl; import com.example.librarymanagement.entity.Book; import com.example.librarymanagement.entity.BorrowRecord; import com.example.librarymanagement.entity.User; import com.example.librarymanagement.mapper.BookMapper; import com.example.librarymanagement.mapper.BorrowRecordMapper; import com.example.librarymanagement.mapper.UserMapper; import com.example.librarymanagement.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; Service // 标记为Spring的Service组件 public class BookServiceImpl implements BookService { Autowired private BookMapper bookMapper; Autowired private UserMapper userMapper; Autowired private BorrowRecordMapper borrowRecordMapper; // 借书业务逻辑 Override Transactional(rollbackFor Exception.class) // 声明式事务任何异常都回滚 public boolean borrowBook(Integer bookId, Integer userId) { // 1. 校验图书是否存在且可借 Book book bookMapper.selectById(bookId); if (book null || book.getStatus() 0) { throw new RuntimeException(图书不存在或已下架); } if (book.getAvailableCount() 0) { throw new RuntimeException(该图书已全部借出); } // 2. 校验用户是否存在且状态正常 User user userMapper.selectById(userId); if (user null || user.getStatus() 0) { throw new RuntimeException(用户不存在或已被禁用); } // 3. (可选) 校验用户是否有超期未还书籍此处省略 // 4. 创建借阅记录 BorrowRecord record new BorrowRecord(); record.setUserId(userId); record.setBookId(bookId); record.setBorrowTime(new Date()); // 假设借阅期为30天 Date expectedReturn new Date(System.currentTimeMillis() 30L * 24 * 60 * 60 * 1000); record.setExpectedReturnTime(expectedReturn); record.setStatus(1); // 借阅中 borrowRecordMapper.insert(record); // 5. 更新图书可借数量减1 int updateCount bookMapper.updateAvailableCount(bookId, -1); if (updateCount ! 1) { // 如果更新失败通常是因为并发导致available_count为0事务会回滚 throw new RuntimeException(更新图书库存失败可能已被其他用户借走); } return true; } Override Transactional public boolean returnBook(Integer recordId) { // 1. 查询借阅记录 BorrowRecord record borrowRecordMapper.selectById(recordId); if (record null || record.getStatus() ! 1) { throw new RuntimeException(借阅记录不存在或不是借阅中状态); } // 2. 更新记录状态和实际归还时间 record.setActualReturnTime(new Date()); // 判断是否超期 int newStatus 2; // 已归还 if (record.getActualReturnTime().after(record.getExpectedReturnTime())) { newStatus 3; // 超期归还 // 计算罚款示例每天0.1元 long overdueDays (record.getActualReturnTime().getTime() - record.getExpectedReturnTime().getTime()) / (1000 * 60 * 60 * 24); record.setOverdueFine(BigDecimal.valueOf(overdueDays * 0.1)); } record.setStatus(newStatus); borrowRecordMapper.updateById(record); // 3. 更新图书可借数量加1 bookMapper.updateAvailableCount(record.getBookId(), 1); return true; } // 其他方法的实现... Override public ListBook getListByCondition(String name, String author, Integer status) { return bookMapper.selectByCondition(name, author, status); } }业务层关键点Transactional借书和还书操作涉及更新多张表borrow_record和book必须放在一个事务中保证要么全部成功要么全部失败避免数据不一致。业务校验在执行业务前先校验数据状态图书可借、用户有效这是保证系统健壮性的关键。异常处理校验失败或操作失败时抛出明确的运行时异常事务会自动回滚并由Controller层捕获后返回给前端友好提示。9. 控制层与页面开发Controller层负责接收HTTP请求调用Service并返回视图或数据。我们使用Spring MVC的Controller注解。BookController.java:package com.example.librarymanagement.controller; import com.example.librarymanagement.entity.Book; import com.example.librarymanagement.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.util.List; Controller RequestMapping(/book) // 所有请求路径以 /book 开头 public class BookController { Autowired private BookService bookService; // 获取图书列表页 GetMapping(/list) public String listBooks(RequestParam(required false) String name, RequestParam(required false) String author, Model model) { ListBook books bookService.getListByCondition(name, author, 1); // 默认查在架图书 model.addAttribute(books, books); model.addAttribute(name, name); model.addAttribute(author, author); return book/list; // 对应 src/main/resources/templates/book/list.html } // 跳转到新增图书页面 GetMapping(/add) public String toAddPage() { return book/add; } // 处理新增图书提交 PostMapping(/save) public String saveBook(Book book) { bookService.saveOrUpdate(book); return redirect:/book/list; // 保存后重定向到列表页 } // 处理借书请求 (API接口通常由前端Ajax调用) PostMapping(/borrow) ResponseBody // 返回JSON数据而不是视图 public ApiResponse borrowBook(RequestParam Integer bookId, RequestParam Integer userId) { try { boolean success bookService.borrowBook(bookId, userId); if (success) { return ApiResponse.success(借书成功); } else { return ApiResponse.error(借书失败); } } catch (RuntimeException e) { return ApiResponse.error(e.getMessage()); // 返回业务异常信息 } } // 统一的API响应类 public static class ApiResponse { private int code; private String msg; private Object data; // 构造方法、getter/setter、success/error静态方法省略... } }前端页面 (Thymeleaf模板示例)在src/main/resources/templates/book/下创建list.html:!DOCTYPE html html langzh xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title图书列表/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css /head body div classcontainer mt-4 h2图书列表/h2 form classrow g-3 mb-3 th:action{/book/list} methodget div classcol-auto input typetext classform-control namename th:value${name} placeholder图书名称 /div div classcol-auto input typetext classform-control nameauthor th:value${author} placeholder作者 /div div classcol-auto button typesubmit classbtn btn-primary查询/button a href/book/add classbtn btn-success新增图书/a /div /form table classtable table-striped table-hover thead tr thID/th thISBN/th th书名/th th作者/th th出版社/th th可借数量/th th操作/th /tr /thead tbody tr th:eachbook : ${books} td th:text${book.id}1/td td th:text${book.isbn}978-7-111-59927-5/td td th:text${book.name}Spring Boot实战/td td th:text${book.author}Craig Walls/td td th:text${book.publisher}机械工业出版社/td td th:text${book.availableCount}5/td td a th:href{/book/edit/{id}(id${book.id})} classbtn btn-sm btn-warning编辑/a !-- 借书按钮假设当前登录用户ID为1 -- button classbtn btn-sm btn-primary onclickborrowBook([[${book.id}]])借阅/button /td /tr /tbody /table /div script srchttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/js/bootstrap.bundle.min.js/script script function borrowBook(bookId) { if (confirm(确定要借阅这本书吗)) { fetch(/book/borrow?bookId bookId userId1, { // userId应从session中获取 method: POST, }) .then(response response.json()) .then(data { alert(data.msg); if (data.code 200) { location.reload(); // 借书成功刷新页面更新可借数量 } }) .catch(error console.error(Error:, error)); } } /script /body /html10. 运行结果与效果验证10.1 启动项目在IDEA中找到主启动类LibraryManagementApplication通常位于src/main/java/com/example/librarymanagement/右键运行Run。看到控制台输出类似以下日志说明启动成功Tomcat started on port(s): 8080 (http) Started LibraryManagementApplication in 5.123 seconds10.2 功能验证访问首页打开浏览器访问http://localhost:8080/library/book/list。你应该能看到之前插入的测试图书列表。查询功能在搜索框输入“Spring”或作者名点击查询列表应能正确过滤。借书功能点击某本图书的“借阅”按钮弹出确认框点击确定。如果成功页面会刷新并且该书的“可借数量”会减1。同时检查数据库borrow_record表应该会新增一条状态为“1”借阅中的记录。还书功能需要创建一个还书页面或接口传入recordId调用BookService.returnBook方法。成功后borrow_record表对应记录状态更新book表的available_count加1。11. 常见问题与排查思路问题现象可能原因排查方式解决方案启动时报java.sql.SQLException: Access denied for user...数据库连接配置错误用户名、密码、地址、端口1. 检查application.yml中的spring.datasource配置。2. 用命令行或工具如Navicat测试是否能连上MySQL。修正配置确保数据库服务已启动用户有权限访问指定数据库。访问页面报Whitelabel Error Page或 4041. 请求路径错误。2. Controller未扫描到。3. 模板文件位置不对。1. 检查浏览器地址栏URL是否与RequestMapping匹配。2. 检查启动类是否在Controller的父包下。3. 检查模板文件是否在resources/templates正确子目录。1. 修正请求路径。2. 确保启动类有SpringBootApplication。3. 确保Controller方法返回的视图字符串与模板路径一致。页面能打开但数据显示为null或空白1. Model中未添加属性。2. Thymeleaf表达式写错。3. 数据查询为空。1. 在Controller方法中打断点检查model.addAttribute是否执行。2. 检查HTML中th:text等属性名是否正确。3. 查看控制台MyBatis SQL日志看查询是否执行并返回数据。1. 确保属性名和模板中引用名一致。2. 检查Mapper的SQL和接口方法。3. 检查数据库是否有数据。借书时提示“更新图书库存失败”并发问题多个用户同时借同一本最后一本书。查看BookMapper.updateAvailableCount的SQL它通过WHERE条件保证了available_count不会为负。第一个成功的请求会将其减到0后续请求的delta为-1条件available_count (-1) 0不成立更新行数为0。这是正确的业务处理。前端应提示用户“图书已被借走”。更复杂的场景可以考虑使用分布式锁。事务不生效部分更新成功部分失败1. 方法不是public。2. 异常被捕获未抛出。3. 使用了try-catch但未在catch中抛出异常。1. 检查Transactional注解的方法是否为public。2. 检查方法内部是否捕获了异常并处理了未重新抛出。1. 确保事务方法是public。2. 在需要回滚的异常处要么不捕获要么捕获后抛出RuntimeException或使用Transactional(rollbackForException.class)。12. 最佳实践与工程建议当你跑通基础功能后可以考虑以下优化让项目更接近企业级应用统一异常处理创建一个GlobalExceptionHandler类使用ControllerAdvice注解集中处理所有Controller层抛出的异常返回统一的错误JSON格式避免在每个Controller方法里写try-catch。参数校验在Controller的入参对象上使用javax.validation注解如NotBlank,Min并在方法参数前加Valid注解实现自动参数校验。登录与权限控制使用Spring Security或Shiro框架。用户登录后将用户信息存入Session或生成JWT Token。在Controller方法上添加PreAuthorize等注解实现基于角色的访问控制如只有管理员才能新增/删除图书。分页查询使用MyBatis分页插件如PageHelper或Spring Data JPA的分页对象避免一次性查询大量数据。修改Service和Mapper支持传入页码和大小。日志记录使用SLF4J Logback在关键业务节点如借书、还书记录操作日志便于审计和问题追踪。单元测试为Service层编写JUnit单元测试模拟各种正常和异常情况确保核心业务逻辑的正确性。前端与后端分离将上面的Thymeleaf模板替换为纯HTMLJSVue/React后端Controller只提供RESTful API使用RestController前后端通过JSON交互。这是现代Web开发的主流模式。数据库连接池Spring Boot默认使用HikariCP性能很好。在生产环境中务必在application.yml中配置合适的连接池参数如最大连接数、超时时间。13. 总结与后续学习方向通过这个完整的图书管理系统项目你不仅得到了一个可运行的代码更重要的是走通了一个标准Java Web应用从设计到实现的完整流程。你理解了如何将业务需求转化为数据库表如何用MyBatis操作数据如何在Service层封装复杂的业务规则以及如何通过Controller和View与用户交互。本文的核心价值在于提供了一个清晰、可落地的“骨架”。你可以在此基础上继续深化深入Spring Boot学习自动配置原理、Starter机制、多环境配置application-dev.yml,application-prod.yml。深入MyBatis学习动态SQL的更多标签choose,foreach、一对一/一对多关联查询、二级缓存。引入缓存对于频繁查询且不常变的数据如图书分类可以考虑引入Redis作为缓存减轻数据库压力。引入消息队列对于借书成功后的通知如发送邮件或站内信可以引入RabbitMQ或Kafka进行异步解耦。容器化部署学习Docker将你的Spring Boot应用和MySQL数据库打包成镜像使用Docker Compose一键部署。这个项目是你Java Web开发之路一个坚实的起点。建议你不要止步于复制代码而是尝试去修改它、扩展它比如增加图书分类、借阅排行榜、数据导出功能在解决真实问题的过程中你的理解才会更加深刻。