springboot:首次整合ssmp完成图书管理系统

221 阅读14分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

SSMP整合案例思想

SSMP整合案例

制作流程

  • 先开发基础的 CRUD 功能,做一层测一层
  • 调通页面,确认异步提交成功后,制作所有功能。
  • 添加分页功能与查询功能。

个人目的

  • 做出一个 SpringBoot 的 ssm & ssmp 模板。

1. 创建项目选择功能

我们用到 springWeb 功能和 MySQL 功能。因为使用 mybatis-plus 所以不选择 spring 自带的 mybatis。

image-20220422205958542

image-20220422210124601


2. 整合依赖

导入 mp、druid、lombok 的依赖

        <!--导入依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3. 修改引导类名和配置文件

  • 我们修改引导类名
  • 我们修改配置文件格式为 yml
  • 我们设置端口为 80

image-20220422211132412


4. 创建实体类

我们在引导类同层目录下创建一个 pojo 包,里面放我们的实体类。

这里我们使用了一个叫 lombok 的依赖,可以有效提高我们的代码效率。

image-20220422211618633

package com.hyz.pojo;

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

/**
 * @author workplace
 * @date 2022/4/22 21:13
 */
@Data
@RequiredArgsConstructor
@AllArgsConstructor
@TableName(value = "books")
public class Books {
    @TableId(value = "bookID")
    private int bookID;

    @NonNull
    @TableField(value = "bookName")
    private String bookName;

    @NonNull
    @TableField(value = "bookCounts")
    private int bookCounts;

    @NonNull
    @TableField(value = "detail")
    private String detail;
}

这里我的主键设置是自增,所以我写的 lombok 的有参构造函数可以不包括主键。这样子在创建对象的时候可以通过构造函数去创建就不需要每设置一个参数就调用一次对应的 set 方法。如果看不懂注解的意思可以去查查 lombok 依赖的文档来看。


5. druid 连接数据库配置

我们需要配置 druid 连接数据库的配置

# 配置连接数据库
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC
      username: root
      password: root

完成这一步之后,就开始去写 mapper 的东西了。


6. 创建 mapper 接口

我们使用了 mybats-plus 可以大大提高我们的代码效率。它有点类似与 mybatis 的反编译但是更加的方便。

我们只需要在接口继承 baseMapper<泛型> 并将泛型指向 对应的实体类就会生成这个关于这个实体类的 sql 语句。

image-20220423131321067

package com.hyz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hyz.pojo.Books;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author workplace
 * @date 2022/4/22 21:30
 */

@Mapper
public interface BooksMapper extends BaseMapper<Books> {

}

如果你的数据库设置了主键自增,并且想要 mybats-plus 的日志功能,可以配置一下 mybatis-plus

# mybatis-plus
mybatis-plus:
  global-config:
    # id自增
    db-config:
      id-type: auto
  # log 答应
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

如果需要手动添加一些 mybatis-plus 没有的 sql 语句,可以原来 mybatis 的方法。

当完成之后我们的 dao 层就完成了


7. 创建 service 和 serviceImpl

一般我们的 service 层的命名和 dao 层的命名是不同的。service 层的命名是面对功能,而 dao 层的命名是面向数据库操作。

比如我们的登录在 service 中是 login(String userName, String password)

但是在 dao 层的命名则是 selectByUserNameAndPassword(String userName, String password)。

在 service 层进行测试的时候要尽可能把内容打印出来,因为日志能观察到的是数据测的操作,而 service 层中可能还有别的附加操作所以我们需要打印出来。

在对 service 层进行测试的时候,尽量把所有的方法都测试一遍。我们可以偷偷懒将 dao 层里的测试拿到 service 层来稍作修改也能继续用了。

service 必须要进行测试!

service 的返回值我们一般定义为操作状态,就好比是否成功而不是修改了多少条

一般开发的时候写接口的话会在接口名前写个 【I】比如 BookService 接口写作 IBookService 接口

  • 这里我们通过 mp 自动帮我们实现了 service 和 serviceImpl 的内容。

    只需要在 service 接口后继承 IService<T> 泛型指向实体类即可。

    在 serviceImpl 实现 service 接口继承 ServiceImpl<M,T> M指向你的mapper,T指向实体类

    如果自带的功能不能满足,可以通过自己在 service 接口重载和追加功能。如果重载的话可以加上 @Override 注解。


8. 创建 controller 层

注意,所有的增删查改的操作要和对应的 RestFul 请求格式匹配,请求的参数要和方法的参数匹配。删除对deletemapping、增加对 putmapping、修改对 postmapping、搜索对 getmapping。

如果在 postMapping 的时候出现 com.fasterxml.jackson.databind.exc.InvalidDefinitionException这个错误,需要在实体类里创建无参构造函数就可以解决这个问题。原因是我在该实体类中添加了一个为了方便实例化该类用的构造函数,导致JVM不会添加默认的无参构造函数,而jackson的反序列化需要无参构造函数,因此报错。

传实体用 @RequestBody 路径变量用 @PathVariable


9. Controller 层信息一致性处理(前后端数据协议)

设计 Controller 层返回结果的实体类,用于后端与前端进行数据格式统一,也成为 前后端数据协议。

package com.hyz.controller.util;

import lombok.*;

/**
 * @author workplace
 * @date 2022/4/26 14:08
 */
@Data
public class R {
    private Boolean flag;
    private Object data;
    private String msg;

    public R() {
    }

    public R(Boolean flag) {
        this.flag = flag;
    }

    public R(Boolean flag, Object data) {
        this.flag = flag;
        this.data = data;
    }

}

通过 flag 来表示是否查询成功,用 data 来表示查询的数据。然后我们再去修改 Controller 层

可以看 BooksController2 和 BooksController 看出两者区别。

image-20220426142524205

这么做的好处是:

  1. 设计统一的返回值结果返回值类型便于前端开发读取数据
  2. 返回值结果类型可以根据需求自行设定,没有固定格式
  3. 返回值结果模型类用于后端与前端数据格式统一,也成为前后端数据协议

10. 前端设置

在 springboot 的单体工程当中我们的所有前端信息都 src 下的 resources 下的 static 目录中

需要注意删除和查询造作可能出现线程冲突


11. 异常信息处理

业务操作失败的返回的数据格式是不同的。

我们先来人为制造异常

@PostMapping
public R save(@RequestBody Books books) throws IOException {
    if (true) {
        throw new IOException();
    }
    return new R(booksService.save(books));
}

以后想要人为制造异常都可以通过这种方式来实现

结果我们发现返回了一个未见过的 json 格式的对象

{
    "timestamp": "2022-04-26T14:23:26.494+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/books"
}

后台代码 BUG 导致格式的不一致性。

我们需要对业务的异常也固定一致性

  1. 这是属于 Controller 层的错误所以我们在 controller 包下的 utils 包下创建 springmvc 的异常处理器。

    package com.hyz.controller.util;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * @author workplace
     * @date 2022/4/26 22:28
     */
    // 1. 定义它为 controller 层的异常处理器,可以用ControllerAdvice。
    //@ControllerAdvice
    
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
        // 2. 拦截所有的异常信息,可以再 @ExceptionHandler(Exception.class)来指定异常
    
        @ExceptionHandler
        public R doException(Exception exception) {
            // 记录日志
            // 通知运维
            // 通知开发
            exception.printStackTrace();
            // 3. 修改回复前后端约束,返回异常。
            return new R(false, "服务器故障请稍后再试");
        }
    }
    
  2. 然后去修改前后端数据协议

    package com.hyz.controller.util;
    
    import lombok.*;
    
    /**
     * @author workplace
     * @date 2022/4/26 14:08
     */
    @Data
    public class R {
        private Boolean flag;
        private Object data;
        private String msg;
    
        public R() {
        }
    
        public R(Boolean flag) {
            this.flag = flag;
        }
    
        public R(Boolean flag, Object data) {
            this.flag = flag;
            this.data = data;
        }
    
        public R(Boolean flag, String msg) {
            this.flag = flag;
            this.msg = msg;
        }
    }
    

    增加了 msg 功能。

    如果这样只的话,后端提供失败业务信息,前端提供成功业务信息,非常混乱。所以我们应该统一业务信息管理。所以我们可以在 controller 层进行统一管理

    @PostMapping
    public R save(@RequestBody Books books) throws IOException {
        boolean save = booksService.save(books);
        return new R(save, save ? "添加成功" : "添加失败");
    }
    

12. 分页查询问题

通过后台补救性方案去修复分页查询出现的【当前页码值大于总页码值】 的情况

@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
    IPage<Books> page = booksService.getPage(currentPage, pageSize);
    // 如果当前页码值大于总页码值,那么重新执行查询操作,当前页码值等于总页码值
    if (currentPage > page.getPages()) {
        page = booksService.getPage((int) page.getPages(), pageSize);
    }
    return new R(true,page);
}

这个问题有很多补救方案,需要根据现实情况来决定。核心思想就是【当前页码值大于总页码值】的情况下,需要对当前页码进行修改,改到哪里都行,第一页也行,最后一页也行。


13. 按条件查询

这里使用 mp 的分页功能来完成根据选择来显示分页。加入到原有的分页功能,加入了 lambda 判断。

后端可以通过对象接收请求路径的参数。会自动获取映射。

【controller】

@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Books books) {
    IPage<Books> page = booksService.getPage(currentPage, pageSize, books);
    // 如果当前页码值大于总页码值,那么重新执行查询操作,当前页码值等于总页码值
    if (currentPage > page.getPages()) {
        page = booksService.getPage((int) page.getPages(), pageSize, books);
    }
    return new R(true, page);
}

【service】

@Override
public IPage<Books> getPage(int currentPage, int pageSize, Books books) {
    LambdaQueryWrapper<Books> lqw = new LambdaQueryWrapper<>();
    lqw.like(!Strings.isEmpty(books.getBookName()), Books::getBookName, books.getBookName());
    lqw.like(!Strings.isEmpty(books.getDetail()), Books::getDetail, books.getDetail());
    IPage<Books> page = new Page<>(currentPage, pageSize);
    booksMapper.selectPage(page, lqw);
    return page;
}

SSMP 整合案例

需求:做一个前后端分离的书城系统,需要有图书管理系统。有列表、新增、修改、删除、分页、查询这些功能。

1. 配置起步依赖

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!--导入依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.8</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

这里使用了 【druid 数据库连接池】【mybatis-plus】【lombok】这些第三方技术。


2. application.yml 配置

# port
server:
  port: 80

# druid
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ssmbuild?serverTimezone=UTC
      username: root
      password: root

# mybatis-plus
mybatis-plus:
  global-config:
    # id自增
    db-config:
      id-type: auto
  # log 日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • port:配置端口为 80
  • druid:连接数据库
  • mybatis-plus:设置数据库的 id 自增和日志打印

3. Dao

  1. 我们先创建数据库

image-20220427155201656

  1. 根据数据库创建对应的实体类

    package com.hyz.pojo;
    
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.*;
    import org.springframework.lang.Nullable;
    
    /**
     * @author workplace
     * @date 2022/4/22 21:13
     */
    @Data
    @RequiredArgsConstructor
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(value = "books")
    public class Books {
        @TableId(value = "bookID")
        private int bookID;
    
        @NonNull
        @TableField(value = "bookName")
        private String bookName;
    
        @NonNull
        @TableField(value = "bookCounts")
        private int bookCounts;
    
        @NonNull
        @TableField(value = "detail")
        private String detail;
    }
    

    因为我的数据库名没有下划线,所以需要在实体类里通过注解指明 @TableId 主键、@TableName表名、@TableField 字段名

  2. 创建实体类对应的 mapper 接口

    package com.hyz.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.hyz.pojo.Books;
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * @author workplace
     * @date 2022/4/22 21:30
     */
    
    @Mapper
    public interface BooksMapper extends BaseMapper<Books> {
    
    }
    
    • @Mapper:是为了将接口放入 spring 容器当中,统一由 spirng 管理。同时为这个接口生成一个实现类,让别的类进行引用。

    • BaseMapper<Books>:让泛型指向对应的实体类,让 mp 自动生成实体类的 sql 语句。

    • 如果 mapper 包下由太多个 mapper 接口,不想每一个手动将它们放入容器当中,可以在引导类上添加注解@MapperScan

      @MapperScan("包路径")
      

      这样子包下的所有接口都放入了 spring 容器当中了。

  3. 测试代码

    package com.hyz.mapper;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.hyz.pojo.Books;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import javax.annotation.Resource;
    
    /**
     * @author workplace
     * @date 2022/4/22 22:08
     */
    @SpringBootTest
    public class BooksMapperTest {
        @Resource(name = "booksMapper")
        private BooksMapper booksMapper;
    
        @Test
        public void test1() {
            System.out.println(booksMapper.selectById(1));
        }
    
        @Test
        public void test2() {
            int insert = booksMapper.insert(new Books("C++", 10, "从指针到顶针"));
            System.out.println(insert);
        }
    
        @Test
        public void test3() {
            booksMapper.updateById(new Books(7, "C++", 1, "从指针到顶针"));
        }
    
        @Test
        public void test4() {
            booksMapper.deleteById(8);
        }
    
        @Test
        public void test5() {
            IPage page = new Page(1, 5);
            booksMapper.selectPage(page, null);
            System.out.println("最大页码值:" + page.getPages());
            System.out.println("当前页码值:" + page.getCurrent());
            System.out.println("数据总量:" + page.getTotal());
            System.out.println("每页数据总量:" + page.getSize());
            System.out.println("数据:" + page.getRecords());
        }
    
        @Test
        public void test6() {
            // 1. 创建条件查询器
            QueryWrapper<Books> qw = new QueryWrapper<>();
            // 2. 调用 like 方法,选择字段名和模糊查询的条件
            qw.like("bookName", "C");
            // 3. 使用模糊查询
            booksMapper.selectList(qw);
        }
    
        @Test
        public void test7() {
            // 1. 创建 lambda 表达式条件查询器
            LambdaQueryWrapper<Books> lqw = new LambdaQueryWrapper<>();
            String bookName = null;
            // 2. 调用方法
            lqw.like(true, Books::getBookName, "C");
            booksMapper.selectList(lqw);
        }
    
    }
    

创建了实体类、创建了一个继承 BaseMapper 的接口,我们只需要测试我们的代码是否能够真正的运行就完成 Dao 的所有搭建了。


4. Service

我们这里调用 mp 提供的接口快速开发。因为不习惯 mp 自带的分页功能,所以我们选择自己重写一个分页功能。

  1. 创建 service 接口

    接口继承 IService<Books>,泛型指向实体类。

    package com.hyz.service;
    
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.hyz.pojo.Books;
    
    /**
     * @author workplace
     * @date 2022/4/24 23:43
     */
    public interface IBooksService extends IService<Books> {
    
        IPage<Books> getPage(int currentPage,int pageSize);
    
        IPage<Books> getPage(int pages, int pageSize, Books books);
    }
    
  2. 创建 service 实体类

    实体类继承ServiceImpl<BooksMapper, Books>实现IBooksService接口。继承指向 mapper 接口和实体类。

    package com.hyz.service.Impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.hyz.mapper.BooksMapper;
    import com.hyz.pojo.Books;
    import com.hyz.service.IBooksService;
    import org.apache.logging.log4j.util.Strings;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * @author workplace
     * @date 2022/4/24 23:46
     */
    @Service
    public class BookServiceImpl extends ServiceImpl<BooksMapper, Books> implements IBooksService {
    
        @Autowired
        private BooksMapper booksMapper;
    
        @Override
        public IPage<Books> getPage(int currentPage, int pageSize) {
            IPage<Books> page = new Page<>(currentPage, pageSize);
            booksMapper.selectPage(page, null);
            return page;
        }
    
        @Override
        public IPage<Books> getPage(int currentPage, int pageSize, Books books) {
            LambdaQueryWrapper<Books> lqw = new LambdaQueryWrapper<>();
            lqw.like(!Strings.isEmpty(books.getBookName()), Books::getBookName, books.getBookName());
            lqw.like(!Strings.isEmpty(books.getDetail()), Books::getDetail, books.getDetail());
            IPage<Books> page = new Page<>(currentPage, pageSize);
            booksMapper.selectPage(page, lqw);
            return page;
        }
    }
    

也就是说 mp 帮我们完成成 dao 层和 service 的大部分工作。接下来我们进行测试

  1. 测试

    package com.hyz.service;
    
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.hyz.pojo.Books;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.List;
    
    /**
     * @author workplace
     * @date 2022/4/24 23:06
     */
    @SpringBootTest
    public class IBooksServiceTest {
        @Autowired
        private IBooksService booksService;
    
        @Test
        public void test1() {
            System.out.println(booksService.getById(1));
        }
    
        @Test
        public void test2() {
    
            if (booksService.save(new Books( "C++", 10, "从指针到顶针"))) {
                System.out.println("save成功了");
            } else {
                System.out.println("save失败了");
            }
        }
    
        @Test
        public void test3() {
            System.out.println(booksService.updateById(new Books(5, "C++", 30, "从指针到顶针")));
        }
    
        @Test
        public void test4() {
            System.out.println(booksService.removeById(5));
        }
    
        @Test
        public void test5() {
            List<Books> all = booksService.list();
            for (Books books : all) {
                System.out.println(books);
            }
        }
        
    
        @Test
        public void test6() {
            IPage<Books> page = new Page<Books>(1, 5);
            booksService.page(page);
            System.out.println("最大页码值:" + page.getPages());
            System.out.println("当前页码值:" + page.getCurrent());
            System.out.println("数据总量:" + page.getTotal());
            System.out.println("每页数据总量:" + page.getSize());
            System.out.println("数据:" + page.getRecords());
        }
    }
    

5. Controller

控制层我们选择基于 RestFul 开发,使用 Postman 测试

  1. 创建前后端数据协议

    这里创建了一个前后端数据协议,这个协议需要前后端达成一致才创建。

    • flag:状态,这次服务是否成功
    • data:数据,传输到前端的数据
    • msg:信息,如果出现错误后端发送错误信息。

    Controller 类的所有方法都将按照这个类的 json 格式发送到前端。

    package com.hyz.controller.util;
    
    import lombok.*;
    
    /**
     * @author workplace
     * @date 2022/4/26 14:08
     */
    @Data
    public class R {
        private Boolean flag;
        private Object data;
        private String msg;
    
        public R() {
        }
    
        public R(Boolean flag) {
            this.flag = flag;
        }
    
        public R(Boolean flag, Object data) {
            this.flag = flag;
            this.data = data;
        }
    
        public R(Boolean flag, String msg) {
            this.flag = flag;
            this.msg = msg;
        }
    }
    

    这么做的好处是:

    1. 设计统一的返回值结果返回值类型便于前端开发读取数据

    2. 返回值结果类型可以根据需求自行设定,没有固定格式

    3. 返回值结果模型类用于后端与前端数据格式统一,也成为前后端数据协议

  2. 创建异常处理器

    首先将类定义为controller 层的异常处理器,然后截取异常信息,并根据前后端数据协议格式将信息返回到前端去。

    我们还能在拦截异常的时候对不同的错误由不同的回复。

    package com.hyz.controller.util;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * @author workplace
     * @date 2022/4/26 22:28
     */
    
    // 1. 定义它为 controller 层的异常处理器,可以用 @ControllerAdvice。
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
        
        // 2. 拦截所有的异常信息,可以再 @ExceptionHandler(Exception.class)来指定异常
        @ExceptionHandler
        public R doException(Exception exception) {
            // 记录日志
            // 通知运维
            // 通知开发
            exception.printStackTrace();
            // 3. 修改回复前后端约束,返回异常。
            return new R(false, "服务器故障请稍后再试");
        }
    }
    
  3. 创建 Controller

    这里使用了 RestFul 风格的写法,将各个功能完成。

    package com.hyz.controller;
    
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.hyz.controller.util.R;
    import com.hyz.pojo.Books;
    import com.hyz.service.IBooksService;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    
    /**
     * @author workplace
     * @date 2022/4/26 10:13
     */
    @RestController
    @RequestMapping("/books")
    public class BooksController {
        @Resource
        private IBooksService booksService;
    
        @GetMapping
        public R getAll() {
            return new R(true, booksService.list());
        }
    
        @PostMapping
        public R save(@RequestBody Books books) throws IOException {
            boolean save = booksService.save(books);
            return new R(save, save ? "添加成功" : "添加失败");
        }
    
        @PutMapping
        public R update(@RequestBody Books books) {
            return new R(booksService.updateById(books));
        }
    
        @DeleteMapping("/{id}")
        public R delete(@PathVariable("id") int id) {
            return new R(booksService.removeById(id));
        }
    
        @GetMapping("/{id}")
        public R getById(@PathVariable("id") int id) {
    
            return new R(true, booksService.getById(id));
        }
    
        @GetMapping("/{currentPage}/{pageSize}")
        public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Books books) {
            IPage<Books> page = booksService.getPage(currentPage, pageSize, books);
            // 如果当前页码值大于总页码值,那么重新执行查询操作,当前页码值等于总页码值
            if (currentPage > page.getPages()) {
                page = booksService.getPage((int) page.getPages(), pageSize, books);
            }
            return new R(true, page);
        }
    }
    

到此,前后端分离的后端所有任务已经全部完成

如果在 postMapping 的时候出现 com.fasterxml.jackson.databind.exc.InvalidDefinitionException这个错误,需要在实体类里创建无参构造函数就可以解决这个问题。原因是我在该实体类中添加了一个为了方便实例化该类用的构造函数,导致JVM不会添加默认的无参构造函数,而jackson的反序列化需要无参构造函数,因此报错。


6. 总结

  1. Dao 层

    • 我们使用了 mybats-plus 可以大大提高我们的代码效率。它有点类似与 mybatis 的反编译但是更加的方便。

    • 我们只需要在接口继承 baseMapper<泛型> 并将泛型指向 对应的实体类就会生成这个关于这个实体类的 sql 语句。

    • 可以通过配置 mp 来让主键自增

    • 如果需要手动添加一些 mybatis-plus 没有的 sql 语句,可以使用原来 mybatis 的方法。

  2. Service 层

    • service 层必须全部做测试!!!

    • 一般我们的 service 层的命名和 dao 层的命名是不同的。service 层的命名是面对功能,而 dao 层的命名是面向数据库操作。

      比如我们的登录在 service 中是 login(String userName, String password)

      但是在 dao 层的命名则是 selectByUserNameAndPassword(String userName, String password)。

    • 在 service 层进行测试的时候要尽可能把内容打印出来,因为日志能观察到的是数据测的操作,而 service 层中可能还有别的附加操作所以我们需要打印出来。

      在对 service 层进行测试的时候,尽量把所有的方法都测试一遍。我们可以偷偷懒将 dao 层里的测试拿到 service 层来稍作修改也能继续用了。

    • 一般开发的时候写接口的话会在接口名前写个 【I】比如 BookService 接口写作 IBookService 接口

    • 这里我们通过 mp 自动帮我们实现了 service 和 serviceImpl 的内容。

      只需要在 service 接口后继承 IService<T> 泛型指向实体类即可。

      在 serviceImpl 实现 service 接口继承 ServiceImpl<M,T> M指向你的mapper,T指向实体类

      如果自带的功能不能满足,可以通过自己在 service 接口重载和追加功能。如果重载的话可以加上 @Override 注解。

  3. Controller 层

    • 所有的增删查改的操作要和对应的 RestFul 请求格式匹配,请求的参数要和方法的参数匹配。删除对deletemapping、增加对 putmapping、修改对 postmapping、搜索对 getmapping。