使用SpringBoot + Thymeleaf + MyBatisPlus实现一个简单的书籍管理系统-demo1
一 系统功能设计
采用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 {
@Override
public boolean saveBook(Book book) {
return this.save(book);
}
@Override
public boolean updateBook(Book book) {
return this.updateById(book);
}
@Override
public boolean deleteBook(Long id) {
return this.removeById(id);
}
@Override
public Book getBookById(Long id) {
return this.getById(id);
}
@Override
public List<Book> getAllBooks() {
return this.list();
}
@Override
public Page<Book> getBooksByPage(int pageNum, int pageSize) {
Page<Book> page = new Page<>(pageNum, pageSize);
return this.page(page);
}
@Override
public 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 {
@Override
public boolean saveBook(Book book) {
return this.save(book);
}
@Override
public boolean updateBook(Book book) {
return this.updateById(book);
}
@Override
public boolean deleteBook(Long id) {
return this.removeById(id);
}
@Override
public Book getBookById(Long id) {
return this.getById(id);
}
@Override
public List<Book> getAllBooks() {
return this.list();
}
@Override
public Page<Book> getBooksByPage(int pageNum, int pageSize) {
Page<Book> page = new Page<>(pageNum, pageSize);
return this.page(page);
}
@Override
public 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: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/book_db?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
thymeleaf:
# html缓存,生产环境可开启
cache: false
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: 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: false
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: 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>

浙公网安备 33010602011771号