ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot + MySQL + Thymeleaf 构建图书借阅管理系统全流程实战

Spring Boot + MySQL + Thymeleaf 构建图书借阅管理系统全流程实战 最近在准备国赛项目的过程中很多同学反馈面对一个综合性的开发任务时常常感到无从下手不知道如何将学过的零散知识点串联成一个完整的、可运行的系统。本文将围绕一个典型的国赛级项目需求手把手带你从零开始完成一个具备完整前后端交互、数据库操作和基础业务逻辑的Web应用。无论你是正在备赛的学生还是希望提升工程化能力的开发者都能通过本文掌握项目搭建、模块设计、代码实现到部署测试的全流程。1. 项目背景与核心需求分析在各类软件开发竞赛中题目往往不会直接给出具体的技术栈而是描述一个业务场景和功能性需求。我们假设本次“国赛1”项目的核心需求是开发一个简易的在线图书借阅管理系统。这个系统需要满足以下基本功能用户管理区分管理员和普通用户实现注册、登录和权限控制。图书管理管理员可以对图书信息进行增删改查CRUD。借阅管理用户查询图书、发起借阅申请管理员审核借阅申请。数据展示以清晰的界面展示图书列表、借阅记录等信息。这几乎涵盖了Web开发中最核心的模块用户认证、权限管理、数据库设计、前后端数据交互和基本的UI展示。通过实现这个系统你将系统性地实践以下技术栈后端Spring Boot (Java)前端Vue.js / HTML CSS JavaScript (为简化本文后端将提供RESTful API前端使用Thymeleaf模板引擎演示原理相通)数据库MySQL项目管理Maven2. 开发环境与工具准备在开始编码前请确保你的本地开发环境已就绪。一致的环境是团队协作和项目可复现的基础。2.1 基础软件安装JDK版本 8 或 11 (推荐11)。安装后配置JAVA_HOME环境变量。Maven版本 3.6。用于管理项目依赖和构建。MySQL版本 5.7 或 8.0。安装并启动MySQL服务记住root密码。IDEIntelliJ IDEA (推荐) 或 Eclipse。本文示例使用IDEA。浏览器Chrome 或 Firefox用于调试和测试。API测试工具Postman 或 Insomnia用于测试后端接口。2.2 初始化Spring Boot项目我们将使用 Spring Initializr 快速生成项目骨架。访问 Spring Initializr 网站。进行如下配置Project: Maven ProjectLanguage: JavaSpring Boot: 选择一个稳定版本 (如 2.7.x 或 3.x注意JDK版本对应关系)Project Metadata:Group:com.exampleArtifact:library-systemPackaging: JarJava: 对应你的JDK版本Dependencies: 添加以下依赖Spring Web(构建Web应用)Spring Data JPA(数据库ORM)MySQL Driver(数据库连接)Thymeleaf(模板引擎用于服务端渲染简单页面)Lombok(简化Java Bean代码可选但推荐)点击“Generate”下载项目压缩包解压后用IDE打开。你的pom.xml文件核心依赖部分应类似如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies2.3 数据库准备在MySQL中创建数据库和用户。-- 使用MySQL命令行或客户端工具执行 CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER library_user% IDENTIFIED BY YourPassword123!; GRANT ALL PRIVILEGES ON library_db.* TO library_user%; FLUSH PRIVILEGES;注意生产环境务必使用强密码并限制用户主机(‘localhost’)。3. 数据库设计与实体类映射良好的数据库设计是项目的基石。我们根据需求设计三张核心表。3.1 表结构设计用户表 (user)存储系统用户信息。图书表 (book)存储图书信息。借阅记录表 (borrow_record)记录借阅行为关联用户和图书。3.2 创建实体类 (Entity)在src/main/java/com/example/librarysystem/entity包下创建对应的Java实体类。这里使用JPA注解进行对象-关系映射。User.javapackage com.example.librarysystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; import java.util.List; Entity Table(name user) Data public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(unique true, nullable false) private String username; // 用户名 Column(nullable false) private String password; // 密码 (存储加密后的) private String email; private String phone; Enumerated(EnumType.STRING) private Role role; // 角色ADMIN, USER private LocalDateTime createTime; // 一个用户可以有多个借阅记录 OneToMany(mappedBy user, cascade CascadeType.ALL) private ListBorrowRecord borrowRecords; public enum Role { ADMIN, USER } }Book.javapackage com.example.librarysystem.entity; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.util.List; Entity Table(name book) Data public class Book { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false) private String isbn; // 国际标准书号 Column(nullable false) private String title; // 书名 private String author; private String publisher; private BigDecimal price; Column(name total_copies) private Integer totalCopies; // 总册数 Column(name available_copies) private Integer availableCopies; // 可借册数 // 一本书对应多条借阅记录 OneToMany(mappedBy book, cascade CascadeType.ALL) private ListBorrowRecord borrowRecords; }BorrowRecord.javapackage com.example.librarysystem.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDate; Entity Table(name borrow_record) Data public class BorrowRecord { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; ManyToOne JoinColumn(name user_id, nullable false) private User user; // 借阅用户 ManyToOne JoinColumn(name book_id, nullable false) private Book book; // 借阅图书 Column(name borrow_date) private LocalDate borrowDate; // 借阅日期 Column(name due_date) private LocalDate dueDate; // 应还日期 Column(name return_date) private LocalDate returnDate; // 实际归还日期 Enumerated(EnumType.STRING) private Status status; // 状态PENDING, BORROWED, RETURNED, OVERDUE public enum Status { PENDING, // 待审核 BORROWED, // 借出中 RETURNED, // 已归还 OVERDUE // 已逾期 } }关键点解释Entity,Table,Id,Column是JPA基础注解用于定义实体和表字段映射。OneToMany,ManyToOne定义了表之间的关联关系。mappedBy属性指向另一方实体中关联属性的名称。Enumerated(EnumType.STRING)将Java枚举类型以字符串形式存储到数据库更易读。Lombok的Data注解自动生成了getter、setter、toString等方法极大简化了代码。4. 数据访问层与业务逻辑层实现我们遵循经典的三层架构Controller(控制层) - Service(业务层) - Repository(数据访问层)。4.1 创建Repository接口Spring Data JPA只需声明接口即可自动实现基础的CRUD方法。在repository包下创建接口。UserRepository.javapackage com.example.librarysystem.repository; import com.example.librarysystem.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepositoryUser, Long { // 根据用户名查找用户 OptionalUser findByUsername(String username); // 检查用户名是否存在 boolean existsByUsername(String username); }BookRepository.javapackage com.example.librarysystem.repository; import com.example.librarysystem.entity.Book; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface BookRepository extends JpaRepositoryBook, Long { // 根据书名模糊查询 ListBook findByTitleContaining(String title); // 根据作者查询 ListBook findByAuthor(String author); // 复杂查询示例查找可借阅的图书 Query(SELECT b FROM Book b WHERE b.availableCopies 0) ListBook findAvailableBooks(); }BorrowRecordRepository.javapackage com.example.librarysystem.repository; import com.example.librarysystem.entity.BorrowRecord; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface BorrowRecordRepository extends JpaRepositoryBorrowRecord, Long { // 根据用户ID查找其借阅记录 ListBorrowRecord findByUserId(Long userId); // 根据图书ID查找借阅记录 ListBorrowRecord findByBookId(Long bookId); // 根据状态查找借阅记录 ListBorrowRecord findByStatus(BorrowRecord.Status status); }4.2 实现Service业务逻辑业务层处理核心逻辑如借书时的库存检查、状态更新等。在service包下创建接口和实现类。BookService.java (接口)package com.example.librarysystem.service; import com.example.librarysystem.entity.Book; import java.util.List; public interface BookService { Book saveBook(Book book); void deleteBook(Long id); Book updateBook(Long id, Book bookDetails); Book getBookById(Long id); ListBook getAllBooks(); ListBook searchBooks(String keyword); // 借书业务方法 boolean borrowBook(Long bookId, Long userId); // 还书业务方法 boolean returnBook(Long recordId); }BookServiceImpl.java (实现类)package com.example.librarysystem.service.impl; import com.example.librarysystem.entity.Book; import com.example.librarysystem.entity.BorrowRecord; import com.example.librarysystem.entity.User; import com.example.librarysystem.repository.BookRepository; import com.example.librarysystem.repository.BorrowRecordRepository; import com.example.librarysystem.repository.UserRepository; import com.example.librarysystem.service.BookService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; import java.util.Optional; Service RequiredArgsConstructor // Lombok注解为final字段生成构造函数 public class BookServiceImpl implements BookService { private final BookRepository bookRepository; private final UserRepository userRepository; private final BorrowRecordRepository borrowRecordRepository; Override public ListBook getAllBooks() { return bookRepository.findAll(); } Override public Book getBookById(Long id) { return bookRepository.findById(id) .orElseThrow(() - new RuntimeException(Book not found with id: id)); } Override Transactional // 借书操作涉及多表更新需要事务保证一致性 public boolean borrowBook(Long bookId, Long userId) { OptionalBook bookOpt bookRepository.findById(bookId); OptionalUser userOpt userRepository.findById(userId); if (!bookOpt.isPresent() || !userOpt.isPresent()) { return false; // 图书或用户不存在 } Book book bookOpt.get(); if (book.getAvailableCopies() 0) { return false; // 库存不足 } // 1. 减少图书可借数量 book.setAvailableCopies(book.getAvailableCopies() - 1); bookRepository.save(book); // 2. 创建借阅记录 BorrowRecord record new BorrowRecord(); record.setBook(book); record.setUser(userOpt.get()); record.setBorrowDate(LocalDate.now()); record.setDueDate(LocalDate.now().plusWeeks(4)); // 假设借期4周 record.setStatus(BorrowRecord.Status.BORROWED); borrowRecordRepository.save(record); return true; } // 其他方法实现saveBook, deleteBook, updateBook, searchBooks, returnBook... // 篇幅所限此处省略原理类似。 }业务逻辑要点Service标记为Spring管理的业务Bean。Transactional在涉及多个数据库写操作的方法上使用确保操作原子性。例如borrowBook中减库存和创建记录必须同时成功或失败。异常处理简单的RuntimeException用于演示实际项目应定义更细致的业务异常类。库存检查借书前的availableCopies检查是防止超借的关键。5. 控制层与API设计控制层接收HTTP请求调用Service并返回响应。我们设计一套RESTful风格的API。5.1 图书管理相关APIBookController.javapackage com.example.librarysystem.controller; import com.example.librarysystem.entity.Book; import com.example.librarysystem.service.BookService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/books) RequiredArgsConstructor public class BookController { private final BookService bookService; // 获取所有图书 GetMapping public ResponseEntityListBook getAllBooks() { return ResponseEntity.ok(bookService.getAllBooks()); } // 根据ID获取图书 GetMapping(/{id}) public ResponseEntityBook getBookById(PathVariable Long id) { try { Book book bookService.getBookById(id); return ResponseEntity.ok(book); } catch (RuntimeException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); } } // 新增图书 (管理员权限) PostMapping public ResponseEntityBook createBook(RequestBody Book book) { // 实际项目中这里应使用DTO接收参数并对参数进行校验 Book savedBook bookService.saveBook(book); return ResponseEntity.status(HttpStatus.CREATED).body(savedBook); } // 更新图书信息 PutMapping(/{id}) public ResponseEntityBook updateBook(PathVariable Long id, RequestBody Book bookDetails) { Book updatedBook bookService.updateBook(id, bookDetails); return ResponseEntity.ok(updatedBook); } // 删除图书 DeleteMapping(/{id}) public ResponseEntityVoid deleteBook(PathVariable Long id) { bookService.deleteBook(id); return ResponseEntity.noContent().build(); } // 搜索图书 GetMapping(/search) public ResponseEntityListBook searchBooks(RequestParam String keyword) { ListBook books bookService.searchBooks(keyword); return ResponseEntity.ok(books); } // 借书接口 PostMapping(/{bookId}/borrow) public ResponseEntityString borrowBook(PathVariable Long bookId, RequestParam Long userId) { boolean success bookService.borrowBook(bookId, userId); if (success) { return ResponseEntity.ok(借书成功); } else { return ResponseEntity.badRequest().body(借书失败可能图书不存在、库存不足或用户不存在。); } } }API设计说明RestController组合了Controller和ResponseBody直接返回JSON数据。RequestMapping定义类级别的请求路径前缀。GetMapping,PostMapping等定义具体的HTTP方法和路径。PathVariable获取URL路径中的变量。RequestParam获取URL查询参数。RequestBody获取HTTP请求体中的JSON数据并绑定到对象。ResponseEntity用于构建完整的HTTP响应包括状态码、头部和响应体。5.2 用户认证与权限控制简化版完整的安全框架如Spring Security配置较复杂这里提供一个基于Session的简化登录方案。AuthController.javapackage com.example.librarysystem.controller; import com.example.librarysystem.entity.User; import com.example.librarysystem.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpSession; import java.util.Optional; Controller // 注意这里不是RestController因为要返回视图 RequiredArgsConstructor public class AuthController { private final UserRepository userRepository; GetMapping(/login) public String loginPage() { return login; // 对应 src/main/resources/templates/login.html } PostMapping(/login) public String doLogin(RequestParam String username, RequestParam String password, HttpSession session, Model model) { OptionalUser userOpt userRepository.findByUsername(username); // 警告实际项目必须对密码进行加密存储和比对这里仅为演示。 if (userOpt.isPresent() userOpt.get().getPassword().equals(password)) { // 登录成功将用户信息存入Session session.setAttribute(user, userOpt.get()); return redirect:/books; // 重定向到图书列表页 } else { model.addAttribute(error, 用户名或密码错误); return login; } } GetMapping(/logout) public String logout(HttpSession session) { session.invalidate(); // 使Session失效 return redirect:/login; } }6. 前端页面与集成为了快速演示我们使用Thymeleaf模板引擎渲染简单的HTML页面。将HTML文件放在src/main/resources/templates/目录下。books.html (图书列表页)!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title图书列表/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css relstylesheet /head body div classcontainer mt-4 h2图书列表/h2 div th:if${session.user ! null} p欢迎span th:text${session.user.username}/span a href/logout classbtn btn-secondary btn-sm退出/a /p /div table classtable table-striped thead tr thID/th thISBN/th th书名/th th作者/th th出版社/th th价格/th th可借数量/th th操作/th /tr /thead tbody !-- Thymeleaf 遍历图书列表 -- tr th:eachbook : ${books} td th:text${book.id}/td td th:text${book.isbn}/td td th:text${book.title}/td td th:text${book.author}/td td th:text${book.publisher}/td td th:text${book.price}/td td th:text${book.availableCopies}/td td !-- 根据用户角色和库存显示不同按钮 -- a th:href{/api/books/{id}/borrow(userId${session.user.id}, id${book.id})} th:if${session.user ! null and session.user.role.name() USER and book.availableCopies 0} classbtn btn-primary btn-sm借阅/a span th:if${book.availableCopies 0} classtext-muted已借完/span /td /tr /tbody /table /div /body /html需要一个Controller来渲染这个页面Controller RequestMapping(/books) RequiredArgsConstructor public class BookViewController { private final BookService bookService; GetMapping public String listBooks(Model model) { model.addAttribute(books, bookService.getAllBooks()); return books; } }7. 应用配置与运行7.1 数据库连接配置在src/main/resources/application.properties中配置数据库连接和JPA属性。# 数据库连接 spring.datasource.urljdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernamelibrary_user spring.datasource.passwordYourPassword123! spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # JPA配置 spring.jpa.hibernate.ddl-autoupdate # 启动时根据实体更新表结构生产环境用validate或none spring.jpa.show-sqltrue # 控制台打印SQL开发时方便调试 spring.jpa.properties.hibernate.dialectorg.hibernate.dialect.MySQL8Dialect spring.jpa.properties.hibernate.format_sqltrue # 服务器端口 server.port80807.2 启动与测试确保MySQL服务已启动。在IDE中右键点击LibrarySystemApplication主启动类选择Run。看到控制台输出Started LibrarySystemApplication in x.xxx seconds即启动成功。打开浏览器访问http://localhost:8080/books。使用Postman测试API例如GET http://localhost:8080/api/books。8. 常见问题与排查思路在开发过程中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案启动时报java.sql.SQLException: Access denied for user数据库连接失败1. 检查application.properties中的用户名、密码、数据库名是否正确。2. 检查MySQL服务是否运行。3. 检查用户权限是否授予。启动时报Table ‘xxx’ doesn‘t exist表不存在1. 检查spring.jpa.hibernate.ddl-auto是否为update或create。2. 检查实体类Table注解的表名与数据库是否匹配。3. 检查实体类字段名与数据库列名映射是否正确。访问页面报Whitelabel Error Page或 404请求路径错误或Controller未扫描1. 检查浏览器地址栏URL是否正确。2. 检查Controller类是否在启动类同级或子包下确保被Spring扫描到。3. 检查RequestMapping或GetMapping的路径。插入中文数据到数据库后乱码数据库字符集不匹配1. 确认MySQL数据库、表、列的字符集为utf8mb4。2. 在JDBC连接URL中添加参数?useUnicodetruecharacterEncodingutf8。Autowired注入失败报BeanCreationException依赖注入问题1. 检查被注入的类是否被Spring管理是否有Service,Repository,Component等注解。2. 检查是否使用了new关键字手动创建了Bean。事务Transactional不生效代理问题或异常未被抛出1. 确保方法不是private、final或static的。2. 确保异常被抛出默认只回滚RuntimeException和Error。3. 确保调用方是通过Spring代理调用的该方法同类内调用不经过代理。9. 项目优化与扩展建议完成基础功能后可以从以下方向深化项目这正是在竞赛中脱颖而出的关键。9.1 安全性增强密码加密绝对不要明文存储密码。使用BCryptPasswordEncoder进行哈希加密存储和比对。完善权限控制集成Spring Security实现基于角色(ROLE_ADMIN,ROLE_USER)的URL和方法级权限控制。防止SQL注入坚持使用JPA或MyBatis的参数化查询避免手动拼接SQL字符串。XSS防护在Thymeleaf中默认已对HTML内容进行转义。如果直接输出到JavaScript需额外处理。9.2 代码结构与可维护性使用DTO在Controller层和Service层之间使用Data Transfer Object传输数据避免直接暴露实体类更安全、灵活。统一响应封装设计一个通用的Result或Response类来包装所有API的返回结果包含状态码、消息和数据。全局异常处理使用ControllerAdvice和ExceptionHandler捕获并统一处理各类异常返回友好的错误信息。接口文档使用Swagger/OpenAPI自动生成API文档便于前后端协作。9.3 功能扩展借阅逾期计算与提醒在BorrowRecord中增加逾期天数计算逻辑并可通过定时任务发送邮件或站内信提醒。图书预约功能当图书被借完时允许用户预约图书归还后按预约顺序通知。数据统计与报表为管理员提供数据看板如图书借阅排行榜、用户活跃度、月度借阅趋势等。文件上传允许管理员上传图书封面图片。前端分离将Thymeleaf替换为Vue.js或React前端项目通过Axios调用后端REST API实现前后端完全分离。9.4 部署与运维多环境配置使用Spring Profiles管理开发、测试、生产环境的配置如数据库地址、日志级别。日志记录配置Logback或Log4j2将日志按级别输出到文件便于问题追踪。数据库连接池默认的HikariCP性能很好但可以学习其配置项如最大连接数、超时时间以优化生产环境。容器化部署学习编写Dockerfile将应用打包成Docker镜像使用Docker Compose编排应用和MySQL数据库。通过以上步骤你不仅完成了一个可运行的“图书借阅管理系统”原型更实践了从设计到部署的完整软件开发流程。在竞赛或实际项目中评委和用户更关注的是你如何思考问题、设计解决方案以及代码的健壮性和可扩展性。建议你在理解本文每个模块的基础上选择一两个优化方向进行深入研究并实现这会让你的项目更加出彩。
返回列表