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

一 准备环境

1 数据库环境搭建

CREATE DATABASE IF NOT EXISTS db_book;
USE db_book;

CREATE TABLE book (
                      id INT PRIMARY KEY AUTO_INCREMENT COMMENT '书籍唯一标识',
                      name VARCHAR(255) NOT NULL COMMENT '书籍名称',
                      author VARCHAR(100) NOT NULL COMMENT '作者',
                      press VARCHAR(100) NOT NULL COMMENT '出版社',
                      status VARCHAR(20) NOT NULL COMMENT '书籍状态(例如:在库、借出、已预订等)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='书籍信息表';

2 Java环境搭建

3 Maven环境搭建

二 实验过程

1 实验目的

  1. 掌握 MyBatis-Plus 的基本配置与使用方法
  2. 理解并实践基于 MyBatis-Plus 的 CRUD 操作
  3. 学会使用 MyBatis-Plus 的条件构造器进行复杂查询
  4. 掌握分页查询的实现方式
  5. 理解实体类与数据库表的映射关系
  6. 培养基于 SpringBoot+MyBatis-Plus 进行实际项目开发的能力

2 实验要求

  1. 创建一个图书管理系统,实现图书信息的增删改查功能
  2. 使用 MyBatis-Plus 简化数据访问层代码
  3. 实现按图书名称、作者、类别等条件进行查询
  4. 实现图书信息的分页展示
  5. 对图书信息进行必要的验证(如非空验证)
  6. 代码结构清晰,符合 SpringBoot 开发规范
  7. 编写必要的测试用例验证功能正确性

3 实验过程

image-20251021175822158

1. 创建 Maven 项目

使用 IDEA 创建名为E3-BookManagementSystem的Maven项目,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 https://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.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yqd</groupId>
    <artifactId>E3-BookManagementSystem</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>E3-BookManagementSystem</name>
    <description>E3-BookManagementSystem</description>

    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- MyBatisPlus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
2. 配置文件(application.properties)

src/main/resources下创建配置文件:

spring:
  application:
    name: E3-BookManagementSystem
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_book?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root  # 替换为你的数据库用户名
    password: 123456  # 替换为你的数据库密码

# MyBatisPlus配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml  # mapper.xml存放路径
type-aliases-package: com.yqd.entity  # 实体类包路径
configuration:
  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志(可选)

server:
  port: 8081
3 启动类
package com.yqd;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.yqd.mapper") // 扫描Mapper接口所在包
public class E3BookManagementSystemApplication {

    public static void main(String[] args) {
        SpringApplication.run(E3BookManagementSystemApplication.class, args);
    }

}
4. 实体类(Book.java)
package com.yqd.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    private Integer id;
    private String name;
    private String author;
    private String press;
    private String status;
}
5. Mapper 接口
package com.yqd.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqd.entity.Book;
import org.apache.ibatis.annotations.Mapper;

@Mapper  // 标记为MyBatis的Mapper接口
public interface BookMapper extends BaseMapper<Book> {
    // 无需手动编写CRUD方法,BaseMapper已提供
}
6. 服务层接口(BookService.java)
package com.yqd.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.yqd.entity.Book;

import java.util.List;

public interface BookService extends IService<Book> {
    // 继承IService,获得更多增强CRUD方法

    // 在BookService接口中添加
    List<Book> findByStatus(String status);
}
7. 服务层实现(BookServiceImpl.java)
package com.yqd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yqd.entity.Book;
import com.yqd.mapper.BookMapper;
import com.yqd.service.BookService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.List;

@Service  // 标记为服务层组件
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {
    @Override
    public List<Book> findByStatus(String status) {
        // 使用QueryWrapper构建条件
        QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("status", status);  // 等价于 WHERE status = ?
        return baseMapper.selectList(queryWrapper);
    }
}
8. 控制器(BookController.java)

实现增删改查接口:

package com.yqd.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yqd.entity.Book;
import com.yqd.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 org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/books")
public class BookController {
    @Autowired
    private BookService bookService;

    // 1. 查询所有书籍(列表页)
    @GetMapping
    public String list(Model model) {
        // MyBatis-Plus的list()方法等价于JPA的findAll()
        List<Book> books = bookService.list();
        model.addAttribute("books", books);
        return "book/list";
    }

    // 2. 跳转新增/编辑页
    @GetMapping("/form")
    public String form(@RequestParam(required = false) Integer id, Model model) {
        if (id != null) {
            // MyBatis-Plus的getById()等价于JPA的findById()
            Book book = bookService.getById(id);
            model.addAttribute("book", book != null ? book : new Book());
        } else {
            model.addAttribute("book", new Book());
        }
        return "book/form";
    }

    // 3. 保存书籍(新增/更新)
    @PostMapping("/save")
    public String save(Book book) {
        // MyBatis-Plus的saveOrUpdate()方法自动判断新增/更新(根据ID是否为空)
        bookService.saveOrUpdate(book);
        return "redirect:/books";
    }

    // 4. 异步删除书籍
    @DeleteMapping("/{id}")
    @ResponseBody
    public Map<String, Object> delete(@PathVariable Integer id) {
        Map<String, Object> result = new HashMap<>();
        try {
            // MyBatis-Plus的removeById()等价于JPA的deleteById()
            boolean success = bookService.removeById(id);
            result.put("success", success);
            result.put("msg", success ? "删除成功" : "删除失败:书籍不存在");
        } catch (Exception e) {
            result.put("success", false);
            result.put("msg", "删除失败:" + e.getMessage());
        }
        return result;
    }

    // 5. 异步查询单本书籍
    @GetMapping("/{id}")
    @ResponseBody
    public Book getBookById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    // 6. 分页查询示例(在Controller中)
    @GetMapping("/page")
    @ResponseBody
    public IPage<Book> getPage(@RequestParam(defaultValue = "1") Integer pageNum,
                               @RequestParam(defaultValue = "10") Integer pageSize) {
        Page<Book> page = new Page<>(pageNum, pageSize);
        return bookService.page(page); // 返回分页结果(包含总条数、当前页数据等)
    }
}
9. 前端页面实现(Thymeleaf + jQuery)
9.1 页面目录结构

src/main/resources/templates下创建:

templates/
├── index.html         // 首页
└── book/
    ├── list.html      // 书籍列表页
    └── form.html      // 新增/编辑页
9.2 首页(index.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1 style="text-align: center; margin-top: 100px;">
    <a href="/books">进入书籍管理系统</a>
</h1>
</body>
</html>
9.3 书籍列表页(list.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>书籍列表</title>
    <script th:src="@{js/jquery.min.js}"></script>
    <style>
        table { width: 80%; margin: 20px auto; border-collapse: collapse; }
        th, td { border: 1px solid #ccc; padding: 10px; text-align: center; }
        .btn { padding: 5px 10px; margin: 0 5px; cursor: pointer; }
        .add-btn { margin: 20px 0 0 10%; }
        /* 新增消息样式 */
        .msg {
            padding: 10px;
            margin: 10px 0;
            border-radius: 4px;
            text-align: center;
        }
        .success-msg { background: #d5f5e3; color: #27ae60; }
        .error-msg { background: #fadbd8; color: #e74c3c; }
    </style>
</head>
<body>
<h1 style="text-align: center;">书籍管理系统</h1>
<!-- 在list.html的.container内添加以下代码(h1标签下方) -->
<div th:if="${not #strings.isEmpty(successMsg)}" class="msg success-msg" th:text="${successMsg}"></div>
<div th:if="${not #strings.isEmpty(errorMsg)}" class="msg error-msg" th:text="${errorMsg}"></div>
<button class="btn add-btn" onclick="window.location.href='/books/form'">新增书籍</button>

<table>
    <tr>
        <th>ID</th>
        <th>书名</th>
        <th>作者</th>
        <th>出版社</th>
        <th>状态</th>
        <th>操作</th>
    </tr>
    <tr th:each="book : ${books}">
        <td th:text="${book.id}"></td>
        <td th:text="${book.name}"></td>
        <td th:text="${book.author}"></td>
        <td th:text="${book.press}"></td>
        <td th:text="${book.status}"></td>
        <td>
            <button class="btn edit-btn" th:data-id="${book.id}">编辑</button>
            <button class="btn delete-btn" th:data-id="${book.id}">删除</button>
        </td>
    </tr>
</table>

<script>
    $(function() {
        // 编辑按钮点击事件
        $('.edit-btn').click(function() {
            const id = $(this).data('id');
            window.location.href = `/books/form?id=${id}`; // 跳转到编辑页
        });

        // 删除按钮点击事件
        $('.delete-btn').click(function() {
            const id = $(this).data('id');
            if (confirm(`确定删除ID为${id}的书籍吗?`)) {
                deleteBook(id, $(this).closest('tr'));
            }
        });
    });

    // 异步删除书籍
    function deleteBook(id, $tr) {
        $.ajax({
            url: `/books/${id}`,
            type: 'DELETE',
            success: function(res) {
                if (res.success) {
                    alert(res.msg);
                    $tr.remove(); // 移除当前行
                } else {
                    alert(res.msg);
                }
            },
            error: function() {
                alert('删除失败,请重试');
            }
        });
    }
    <!-- 消息自动消失脚本 -->
    $(function() {
        // 3秒后自动隐藏提示消息
        setTimeout(() => {
            $('.msg').fadeOut(1000);
        }, 3000);
    });
</script>
</body>
</html>
9.4 新增 / 编辑页(form.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${book.id != null ? '编辑书籍' : '新增书籍'}"></title>
    <script th:src="@{js/jquery.min.js}"></script>
    <style>
        .container { width: 500px; margin: 50px auto; }
        h2 { text-align: center; margin-bottom: 30px; }
        .form-group { margin: 15px 0; }
        label { display: inline-block; width: 100px; text-align: right; margin-right: 10px; }
        input, select { width: 300px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
        .msg {
            padding: 10px;
            margin: 10px 0;
            border-radius: 4px;
            text-align: center;
        }
        .success-msg { background: #d5f5e3; color: #27ae60; } /* 成功提示 */
        .error-msg { background: #fadbd8; color: #e74c3c; }   /* 错误提示 */
        .btn-group { margin-top: 20px; margin-left: 110px; }
        .btn { padding: 8px 20px; border: none; border-radius: 4px; cursor: pointer; }
        .save-btn { background: #3498db; color: white; margin-right: 10px; }
        .cancel-btn { background: #95a5a6; color: white; }
        .save-btn:disabled { background: #95a5a6; cursor: not-allowed; }
    </style>
</head>
<body>
<div class="container">
    <h2 th:text="${book.id != null ? '编辑书籍' : '新增书籍'}"></h2>
    <form id="bookForm" th:action="@{/books/save}" method="post">
        <!-- 隐藏域:用于传递ID(编辑时需要) -->
        <input type="hidden" name="id" th:value="${book.id}">

        <div class="form-group">
            <label for="name">书名:</label>
            <input type="text" id="name" name="name" th:value="${book.name}" required>
        </div>

        <div class="form-group">
            <label for="author">作者:</label>
            <input type="text" id="author" name="author" th:value="${book.author}" required>
        </div>

        <div class="form-group">
            <label for="press">出版社:</label>
            <input type="text" id="press" name="press" th:value="${book.press}" required>
        </div>

        <div class="form-group">
            <label for="status">状态:</label>
            <select id="status" name="status" required>
                <option value="在库" th:selected="${book.status == '在库'}">在库</option>
                <option value="借出" th:selected="${book.status == '借出'}">借出</option>
                <option value="已预订" th:selected="${book.status == '已预订'}">已预订</option>
            </select>
        </div>

        <div class="form-group">
            <button type="submit" class="btn save-btn" id="saveBtn">保存</button>
            <button type="button" class="btn" onclick="window.location.href='/books'">取消</button>
        </div>
    </form>
</div>
<script>
    // 防止重复提交:点击保存后禁用按钮
    $(function() {
        $('#bookForm').submit(function() {
            $('#saveBtn').prop('disabled', true).text('保存中...');
            return true; // 允许表单提交
        });

        // 3秒后自动隐藏提示消息
        setTimeout(() => {
            $('.msg').fadeOut(1000);
        }, 3000);
    });
</script>
</body>
</html>

4 运行与测试

1. 启动项目

运行E3BookManagementSystemApplication.javamain方法,控制台显示如下信息表示启动成功:

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2. 访问系统

打开浏览器,输入http://localhost:8081,点击 "进入书籍管理系统" 进入书籍列表页。

3. 功能测试
(1)新增书籍
  • 点击 "新增书籍" 按钮,填写表单(如书名 "Java 编程"、作者 "张三"、出版社 "编程出版社"、状态 "在库")。
  • 点击 "保存",页面跳转至列表页,显示 "新增成功",列表中出现新增的书籍。
(2)编辑书籍
  • 点击某条书籍的 "编辑" 按钮,修改表单内容(如修改状态为 "借出")。
  • 点击 "保存",页面跳转至列表页,显示 "更新成功",列表中数据已更新。
(3)删除书籍
  • 点击某条书籍的 "删除" 按钮,确认后弹出 "删除成功" 提示,列表中该书籍消失。
posted @ 2025-10-21 18:17  碧水云天4  阅读(6)  评论(0)    收藏  举报