使用SpringBoot + Thymeleaf + MyBatisPlus实现一个简单的书籍管理系统

news/2025/10/16 20:45:39/文章来源:https://www.cnblogs.com/smalldong/p/19146590

一 系统功能设计

采用SpringBoot + Thymeleaf + MyBatisPlus技术栈实现一个简单的书籍管理系统,包含以下功能:

  • 书籍列表展示
  • 书籍添加
  • 书籍编辑
  • 书籍删除
  • 书籍查询(按条件筛选)

二 数据库设计

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '书名',`author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '作者',`isbn` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ISBN编号',`publisher` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '出版社',`publish_date` date DEFAULT NULL COMMENT '出版日期',`price` decimal(10, 2) DEFAULT NULL COMMENT '价格',`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分类',`stock` int(11) DEFAULT 0 COMMENT '库存数量',`create_time` datetime(0) DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime(0) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `uk_isbn`(`isbn`) USING BTREE,INDEX `idx_author`(`author`) USING BTREE,INDEX `idx_category`(`category`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '书籍表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES (1, '软件体系结构与设计实用教程(第二版)', '刘其成', '9787113301859', '中国铁道出版社', '2023-10-01', 43.00, '计算机类', 10, '2025-03-31 16:54:49', '2025-03-31 16:54:53');SET FOREIGN_KEY_CHECKS = 1;

三 后端接口设计

创建Maven项目,项目名Book-Management

src/main/java
├── com.yqd
│   ├── config          // 配置类
│   ├── controller      // 控制器
│   ├── entity          // 实体类
│   ├── mapper          // MyBatis Mapper接口
│   ├── service         // 服务层
│   │   ├── impl        // 服务实现
│   ├── util            // 工具类
│   └── BookManagerApplication.java // 启动类
src/main/resources
├── static              // 静态资源
├── templates           // Thymeleaf模板
└── application.yml     // 配置文件

3.1 在IDEA中配置MAVEN

3.2 设置系统的包

3.3 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.2</version></parent><groupId>com.yqd</groupId><artifactId>Book-Management</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--        web开发的场景启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.7</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><!--    SpringBoot应用打包插件--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

3.4 BookManagerApplication

package com.yqd;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan(value = {"com.yqd.mapper"})
public class BookManagerApplication {public static void main(String[] args) {SpringApplication.run(BookManagerApplication.class,args);}
}

3.5 Book

package com.yqd.entity;import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;import java.math.BigDecimal;
import java.util.Date;@Data
@TableName("book")
public class Book {@TableId(type = IdType.AUTO)private Long id;private String title;private String author;private String isbn;private String publisher;@TableField("publish_date")@DateTimeFormat(pattern = "yyyy-MM-dd")private Date publishDate;private BigDecimal price;private String category;private Integer stock;@TableField(fill = FieldFill.INSERT)private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;
}

3.6 BookMapper

package com.yqd.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqd.entity.Book;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BookMapper extends BaseMapper<Book> {
}

3.7 BookService

package com.yqd.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.Book;
import com.yqd.mapper.BookMapper;
import com.yqd.service.BookService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {@Overridepublic boolean saveBook(Book book) {return this.save(book);}@Overridepublic boolean updateBook(Book book) {return this.updateById(book);}@Overridepublic boolean deleteBook(Long id) {return this.removeById(id);}@Overridepublic Book getBookById(Long id) {return this.getById(id);}@Overridepublic List<Book> getAllBooks() {return this.list();}@Overridepublic Page<Book> getBooksByPage(int pageNum, int pageSize) {Page<Book> page = new Page<>(pageNum, pageSize);return this.page(page);}@Overridepublic List<Book> searchBooks(String keyword) {QueryWrapper<Book> queryWrapper = new QueryWrapper<>();if (!StringUtils.isEmpty(keyword)) {queryWrapper.like("title", keyword).or().like("author", keyword).or().like("isbn", keyword).or().like("publisher", keyword);}return this.list(queryWrapper);}
}

3.8 BookServiceImpl

package com.yqd.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.Book;
import com.yqd.mapper.BookMapper;
import com.yqd.service.BookService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.List;@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {@Overridepublic boolean saveBook(Book book) {return this.save(book);}@Overridepublic boolean updateBook(Book book) {return this.updateById(book);}@Overridepublic boolean deleteBook(Long id) {return this.removeById(id);}@Overridepublic Book getBookById(Long id) {return this.getById(id);}@Overridepublic List<Book> getAllBooks() {return this.list();}@Overridepublic Page<Book> getBooksByPage(int pageNum, int pageSize) {Page<Book> page = new Page<>(pageNum, pageSize);return this.page(page);}@Overridepublic List<Book> searchBooks(String keyword) {QueryWrapper<Book> queryWrapper = new QueryWrapper<>();if (!StringUtils.isEmpty(keyword)) {queryWrapper.like("title", keyword).or().like("author", keyword).or().like("isbn", keyword).or().like("publisher", keyword);}return this.list(queryWrapper);}
}

3.9 BookController

package com.yqd.controller;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yqd.entity.Book;
import com.yqd.service.BookService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;@Controller
@RequestMapping("/book")
public class BookController {private final BookService bookService;public BookController(BookService bookService) {this.bookService = bookService;}@GetMapping("/list")public String listBooks(@RequestParam(value = "page", defaultValue = "1") int page,@RequestParam(value = "size", defaultValue = "10") int size,@RequestParam(value = "keyword", required = false) String keyword,Model model) {if (keyword != null && !keyword.isEmpty()) {model.addAttribute("books", bookService.searchBooks(keyword));} else {Page<Book> bookPage = bookService.getBooksByPage(page, size);model.addAttribute("books", bookPage.getRecords());model.addAttribute("page", bookPage);}model.addAttribute("keyword", keyword);return "book/list";}@GetMapping("/add")public String addBookForm(Model model) {model.addAttribute("book", new Book());return "book/form";}@PostMapping("/save")public String saveBook(@ModelAttribute Book book) {if (book.getId() == null) {bookService.saveBook(book);} else {bookService.updateBook(book);}return "redirect:/book/list";}@GetMapping("/edit/{id}")public String editBookForm(@PathVariable Long id, Model model) {model.addAttribute("book", bookService.getBookById(id));return "book/form";}@GetMapping("/delete/{id}")public String deleteBook(@PathVariable Long id) {bookService.deleteBook(id);return "redirect:/book/list";}
}

3.10 application.yml

server:port: 8080spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/book_db?useSSL=false&serverTimezone=GMT%2B8username: rootpassword: 123456thymeleaf:# html缓存,生产环境可开启cache: falseprefix: classpath:/templates/suffix: .htmlmode: HTML5encoding: UTF-8

四 前端页面设计

4.1 修改pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

4.2 修改application.yml

  thymeleaf:# html缓存,生产环境可开启cache: falseprefix: classpath:/templates/suffix: .htmlmode: HTML5encoding: UTF-8

4.3 在templates/book下新建页面

list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>书籍列表</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4"><h1>书籍管理</h1><div class="row mb-3"><div class="col-md-6"><a th:href="@{/book/add}" class="btn btn-primary">添加书籍</a></div><div class="col-md-6"><form th:action="@{/book/list}" method="get" class="d-flex"><input type="text" name="keyword" th:value="${keyword}" class="form-control" placeholder="搜索..."><button type="submit" class="btn btn-secondary ms-2">搜索</button></form></div></div><table class="table table-striped"><thead><tr><th>ID</th><th>书名</th><th>作者</th><th>ISBN</th><th>价格</th><th>库存</th><th>操作</th></tr></thead><tbody><tr th:each="book : ${books}"><td th:text="${book.id}"></td><td th:text="${book.title}"></td><td th:text="${book.author}"></td><td th:text="${book.isbn}"></td><td th:text="${book.price}"></td><td th:text="${book.stock}"></td><td><a th:href="@{/book/edit/{id}(id=${book.id})}" class="btn btn-sm btn-warning">编辑</a><a th:href="@{/book/delete/{id}(id=${book.id})}" class="btn btn-sm btn-danger"onclick="return confirm('确定要删除吗?')">删除</a></td></tr></tbody></table><div th:if="${page}" class="d-flex justify-content-center"><nav aria-label="Page navigation"><ul class="pagination"><li class="page-item" th:classappend="${page.current == 1} ? 'disabled'"><a class="page-link" th:href="@{/book/list(page=1, size=${page.size})}">首页</a></li><li class="page-item" th:classappend="${page.current == 1} ? 'disabled'"><a class="page-link" th:href="@{/book/list(page=${page.current-1}, size=${page.size})}">上一页</a></li><li class="page-item" th:each="i : ${#numbers.sequence(1, page.pages)}"th:classappend="${i == page.current} ? 'active'"><a class="page-link" th:href="@{/book/list(page=${i}, size=${page.size})}" th:text="${i}"></a></li><li class="page-item" th:classappend="${page.current == page.pages} ? 'disabled'"><a class="page-link" th:href="@{/book/list(page=${page.current+1}, size=${page.size})}">下一页</a></li><li class="page-item" th:classappend="${page.current == page.pages} ? 'disabled'"><a class="page-link" th:href="@{/book/list(page=${page.pages}, size=${page.size})}">尾页</a></li></ul></nav></div></div>
</body>
</html>

form.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title th:text="${book.id} ? '编辑书籍' : '添加书籍'"></title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4"><h1 th:text="${book.id} ? '编辑书籍' : '添加书籍'"></h1><form th:action="@{/book/save}" th:object="${book}" method="post"><input type="hidden" th:field="*{id}"><div class="mb-3"><label for="title" class="form-label">书名</label><input type="text" class="form-control" id="title" th:field="*{title}" required></div><div class="mb-3"><label for="author" class="form-label">作者</label><input type="text" class="form-control" id="author" th:field="*{author}" required></div><div class="mb-3"><label for="isbn" class="form-label">ISBN</label><input type="text" class="form-control" id="isbn" th:field="*{isbn}" required></div><div class="mb-3"><label for="publisher" class="form-label">出版社</label><input type="text" class="form-control" id="publisher" th:field="*{publisher}"></div><div class="mb-3"><label for="publishDate" class="form-label">出版日期</label><input type="date" class="form-control" id="publishDate" th:field="*{publishDate}"></div><div class="mb-3"><label for="price" class="form-label">价格</label><input type="number" step="0.01" class="form-control" id="price" th:field="*{price}"></div><div class="mb-3"><label for="category" class="form-label">分类</label><input type="text" class="form-control" id="category" th:field="*{category}"></div><div class="mb-3"><label for="stock" class="form-label">库存数量</label><input type="number" class="form-control" id="stock" th:field="*{stock}"></div><button type="submit" class="btn btn-primary">保存</button><a th:href="@{/book/list}" class="btn btn-secondary">取消</a></form>
</div>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/938498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创业思路

创业思路ai p图, 人工后期校验, 人工p, 然后把图片给手机壳制作厂, 卖给提供照片的人. diy手机壳. 比如他本人的照片加风景, 比如巴黎铁塔

详细介绍:Java-Spring入门指南(十九)thymeleaf基本概念

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

P2605 [ZJOI2010] 基站选址

题目概述 题目链接:https://www.luogu.com.cn/problem/P2605。 有 \(n\) 个村庄,你需要建立不超过 \(k\) 个基站,每一户人家都有参数 \(d_i,s_i,w_i,c_i\) 分别表示距离第一户人家的距离、在不超过 \(s_i\) 的地方有…

NVIDIA Jetson AGX Xavier刷机教程

NVIDIA Jetson AGX Xavier刷机教程 @目录NVIDIA Jetson AGX Xavier刷机教程1.下载SdkManager2.安装SdkManager3.刷机 1.下载SdkManager 进入SdkManager官网下载SdkManager的deb版本 注意,ubuntu的24.04版本暂时不支持…

洛谷p1462-通往奥格瑞码道路

题目:https://www.luogu.com.cn/problem/P1462 思考过程: 刚拿到这个题,我把消耗的血量和路费当成了负权边去考虑,考虑到我们只需要考虑扣钱的最优,并且在血量扣尽之前达到就好,那我觉得可以用一个数组hp来代表实…

详细介绍:VR 太阳光参数与快速渲染

详细介绍:VR 太阳光参数与快速渲染pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco…

位运算中没用的小技巧

位运算那些没用的小技巧消去最后一位的1 x & (x - 1)交换两个数 a ^= b; b ^= a; a ^= b;检查一个数的奇偶 x & 1求x的绝对值 ~(x >> 31) ? a : ~a + 1取两个数的最大值 x ^ ((x ^ y) & -(x < y…

超越基础:SightAI 智能路由与多模型选择实战 - sight

在第一周的入门指南中,我们已经学会了如何使用 SightAI 的基础 API 调用,实现了一个简单的对话交互。不少开发者会面临新的需求:想降低推理成本时,如何找到性价比更高的模型?追求交互速度时,怎样获取更快的响应?…

kafka连接认证

kafka连接认证明白,我帮你整理一份 Kafka 客户端三阶段检查完整文档,涵盖原理、检查目的、可能异常、排查方法以及你之前脚本逻辑对应的解释。文档形式清晰,方便阅读和运维参考。Kafka 客户端三阶段连接检查完整文档…

[Vulhub靶机]JARBAS靶机渗透

[Vulhub靶机]JARBAS靶机渗透 靶机搭建 下载地址: https://download.vulnhub.com/jarbas/Jarbas.zip 下载后设置nat模式即可与攻击机同一网段 kali:192.168.88.1331.信息收集 主机发现 首先对C段进行扫描,发现131这台主…

CF622D 题解

很好的一道思维题。 容易发现答案下界是 \(0\),手模几组发现好像都能取到,考虑能不能找到构造答案为 \(0\) 的通解。 那么除了 \(i=n\) 以外的所有 \(i\) 都应该有 \(d_i=n-i\),考虑构造这个东西,然后最后 \(n\) 随…

vue学习的总结

vue学习如何判断vue框架: (1)使用插件(最无脑的) (2)判断是否有id为app的div (3)判断站点是否只有一个index.html (4)可以用findVueRoot函数 (5)使用油猴的脚本进行清除路由若能清除也可以判断为vue(油猴…

最小二乘问题详解5:非线性最小二乘求解实例

通过拟合非线性函数的实例,详细讲解了非线性最小二乘问题中Gauss-Newton方法的实现过程,并给出了基于Eigen库的C++代码示例及收敛分析。1. 引言 在上一篇文章《最小二乘问题详解4:非线性最小二乘》中,介绍了非线性…

【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、强大的方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】

【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、强大的方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto…

第五周预习

20231313 张景云《密码系统设计》第五周预习AI对内容的总结 Windows.C.C++.加密解密实战.sm.ys 一、数字签名技术基础背景与意义:互联网信息安全问题突出,如网站篡改、仿冒页面等,数字签名技术作为基于密码学的安全…

2025 非标门/铸铝门/别墅大门厂家推荐榜:聚焦品质与服务的实力之选

在建筑建材领域,门作为空间防护与装饰的核心载体,其品质直接关系到居住安全与生活体验。市面上门业厂商众多,产品性能与服务质量参差不齐。基于技术实力、产品适配性、市场口碑等多维度考量,本文筛选出五家各具优势…

工业数字化未来:IT与OT融合实践

本文探讨数字技术在工业与运营环境中的实际应用,包括远程诊断系统、统一登录平台和跨设备协作解决方案,分析如何通过优化数字员工体验提升生产力与安全性,并介绍多家机构在IT与OT融合过程中的实践经验。工业运营工作…

AI安全新威胁:提示注入与模型中毒攻击深度解析

本文深入探讨AI安全领域两大新兴威胁:提示注入与模型中毒攻击。详细分析直接注入、间接注入、多模态注入等攻击技术,以及数据投毒、后门植入等模型威胁,为企业安全团队提供全面的防御策略和实战案例。提示注入与模型…

神经网络入门研读报告

神经网络入门研读报告:基于数据驱动的机器认知模型 神经网络是一种模拟生物神经系统信息处理机制的机器学习模型,核心功能是通过多层非线性变换,从结构化或非结构化数据中自动学习特征与规律,实现分类、预测等认知…

阅读《记录一类分治方法》笔记

目前只有题一。阅读《记录一类分治方法》笔记 题一 \(n\) 个点的无向图,边有边权。\(q\) 次操作,每次操作是添加/删除一条边、修改一条边的边权、查询最小生成树这四种之一。 前言 其实只看了题一,后面都还没看呢。…