一、前期准备

1.1 写在前面

以前一直是半吊子后端,遇到什么简单的需求直接用Python原生http.server配合GPT速起一个,总感觉说会也不会。这一年前前后后把主流编程语言学了一遍,试了试Django、Flask、CppCMS后还是又选择了Java(早晚得凉)的SpringBoot来做后端。一是教程足够多足够好,国内相当流行,学起来的阻碍相对要小很多,而且身边的好朋友也有会SpringBoot的,以后万一合作起来也方便。毕竟技术迭代这么快,没必要每一个都学的那么认真对吧😗。二是博客的后端用的就是SpringBoot来搭建的,虽然毛病很多,够大够卡,但是作为郭某的懵懂启蒙,还是有一些感情在里面的。于是趁着期末考完试这两天短暂的空隙,向着全栈的目标再迈一步吧!

1.2 内容简介

网课推荐:【SpringBoot+Vue全栈开发】

本篇博客中,会分为以下几部分进行介绍:

  1. Java EE企业级框架:SpringBoot+MyBatisPlus【重点】本文

  2. Web前端核心框架 :Vue+ElementUI【捎带复习一下】下一篇

    主要是学学别人怎么用好apipost这个工具哈哈哈。

  3. 公共云部署:前后端项目集成打包与部署【次重点】下下篇

    以前几乎部署的都是静态应用,所以这次顺便看看带后端的部署流程。

1.3 环境配置

Java

JDK本篇中用的是17的版本

  1. 新建“JAVA_HOME”环境变量,变量值为JDK的安装目录

    image-20240627222653353
  2. 将“JAVA_HOME”环境变量添加到Path中

    image-20240627222712625

编译器

IDEA(大清付费版)版本为2023,写Java还是别VScode了😅

Maven

Maven使用的是3.9.8版本

Maven 是一个项目管理工具,可以对 Java 项目进行自动化的构建和依赖管。

image-20240627223118481

他的作用可以分为三类:

项目构建:提供标准的,跨平台的自动化构建项目的方式

依赖管理:方便快捷的管理项目依赖的资源(jar包),避免资源间的版本冲突等问题

统一开发结构:提供标准的,统一的项目开发结构

image-20240627223810588

运行 Maven 的时候,Maven 所需要的任何构件都是直接从本地仓库获取的。 如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库。

image-20240627223857818

下载Maven后,我们需要进行下面三个步骤:

  1. 修改maven安装包中的conf/settings.xml文件,指定本地仓库位置。

    要不然下的依赖都默认存到C盘啦

      <!-- localRepository
       | The path to the local repository maven will use to store artifacts.
       |
       | Default: ${user.home}/.m2/repository
      <localRepository>/path/to/local/repo</localRepository>
      -->
      <localRepository>E:\apache-maven-3.9.8\maven-repository</localRepository>
    
  2. 换成国内镜像。

        <mirror>
          <id>aliyunmaven</id>
          <mirrorOf>*</mirrorOf>
          <name>阿里云公共仓库</name>
          <url>https://maven.aliyun.com/repository/public</url>
        </mirror>
    
  3. IDEA虽然内置了Maven,但是不好用,我们需要去设置里把Maven换成我们自己下的这个。

    image-20240627224244767

二、快速上手

2.1 SpringBoot介绍

  • 定义:Spring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,旨在简化 Spring 应用的初始搭建和开发过程。
  • 起点:Spring Boot 是所有基于 Spring 开发项目的起点。
  • 目标:尽可能地简化应用开发的门槛,使开发、测试、部署变得更加简单。

SpringBoot 特点

  • 约定优于配置:只需要很少的配置或使用默认的配置。
  • 内嵌服务器:能够使用内嵌的 Tomcat、Jetty 服务器,不需要部署 war 文件。
  • 启动器(Starters):提供定制化的启动器,简化 Maven 配置,开箱即用。
  • 纯 Java 配置:没有代码生成,也不需要 XML 配置。
  • 生产级服务监控:提供安全监控、应用监控、健康检测等

2.2 快速创建

利用 IDEA 提供的 Spring Initializr 创建 SpringBoot 应用

填写项目信息:

  • Group:一般输入公司域名。
  • Artifact:项目名称。

第一个 Helloworld 程序

  1. 创建子目录 controller

  2. 在目录 controller中创建HelloController.java文件:

    package com.example.springboot1.controller;
    
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "Hello, World!";
        }
    }
    
  3. 启动项目,在浏览器中输入 http://localhost:8080/hello

2.3 开发环境热部署

  • 问题:项目开发调试过程中频繁修改后台类文件,需要重新编译和重启,影响开发效率。
  • 解决方案:Spring Boot 提供 spring-boot-devtools 组件,实现热部署,无需手动重启应用。

热部署原理

  • 监听变动devtools 会监听 classpath 下的文件变动,触发 Restart 类加载器重新加载类,从而实现类文件和属性文件的热部署。
  • 排除配置:可以通过 spring.devtools.restart.exclude 属性指定一些文件或目录的修改无需重启应用。

配置 spring-boot-devtools

  1. pom.xml中添加dev-tools依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
  2. application.properties 中配置 devtools

    spring.application.name=springboot1
    
    # 热部署生效
    spring.devtools.restart.enabled=true
    # 设置重启目录
    spring.devtools.restart.additional-paths=src/main/java
    # 设置classpath目录下的WEB-INF文件夹内容修改不重启
    spring.devtools.restart.exclude=static/**
    
    server.port=8080
    

IntelliJ IDEA 自动编译配置

  1. 打开 Settings 页面,在左边菜单栏依次找到 Build, Execution, Deployment → Compiler,勾选 Build project automatically
  2. Ctrl+Shift+Alt+/ 快捷键调出 Maintenance 页面,单击 Registry,勾选 compiler.automake.allow.when.app.running 复选框。

通过以上配置,当在 IntelliJ IDEA 中修改代码时,项目会自动重启。

三、Controller

🎉接口测试工具推荐:Apipost(国内)或Postman(国外),Apipost用了好几个项目了,感觉还行。

image-20240627230645084

3.1 开发组件

  • 整合框架:Spring Boot 将传统 Web 开发的 MVC、JSON、Tomcat 等框架整合,提供了 spring-boot-starter-web 组件,简化了 Web 应用配置。
  • 项目配置:创建 Spring Boot 项目并勾选 Spring Web 选项后,会自动将 spring-boot-starter-web 组件加入到项目中。
  • 组件说明
    • Web:基础的 Web 组件
    • WebMVC:Web 开发的基础框架
    • JSON:JSON 数据解析组件
    • Tomcat:自带的容器依赖

控制器注解

  • @Controller:用于返回页面和数据。
  • @RestController:用于返回纯数据,默认返回 JSON 格式的数据(3.Web开发基础)。

@Controller 用法

  • 返回页面和数据,在前端页面中可以通过 ${name} 参数获取后台返回的数据并显示。
  • 通常与 Thymeleaf 模板引擎结合使用。

@RestController 用法

  • 默认情况下,将返回的对象数据转换为 JSON 格式。

3.2 路由映射

@RequestMapping 注解

  • 功能:负责 URL 的路由映射,可以添加在 Controller 类或具体的方法上。
  • 类级别和方法级别
    • 添加在类上:所有路由映射都会加上此映射规则。
    • 添加在方法上:只对当前方法生效。

@RequestMapping 常用属性参数

  • value:请求 URL 的路径,支持 URL 模板和正则表达式。
  • method:HTTP 请求方法(GET、POST、PUT、DELETE 等)。
  • consumes:请求的媒体类型(Content-Type),如 application/json
  • produces:响应的媒体类型。
  • params, headers:请求的参数及请求头的值(3.Web开发基础)。

URL 映射示例

  • 基本示例:

    @RequestMapping("/user")
    
  • 通配符匹配示例:

    @RequestMapping("/getJson/*.json")
    

    当请求/getJson/a.json/getJson/b.json时都会匹配到对应方法。

  • 通配符说明:

    • * 匹配任意字符
    • ** 匹配任意路径
    • ? 匹配单个字符
  • 优先级:

    • 无通配符 > 有 * 通配符 > 有 ** 通配符(3.Web开发基础)。

Method 匹配

  • HTTP 请求方法:GET、POST、PUT、DELETE 等。

  • 使用method参数指定请求的 Method 类型:

    @RequestMapping(value = "/example", method = RequestMethod.GET)
    
  • 或者使用快捷注解:

    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @DeleteMapping

3.3 参数传递

@RequestParam

  • 功能:将请求参数绑定到控制器的方法参数上,接收的参数来自 HTTP 请求体或请求 URL 的 QueryString。
  • 简化:当请求参数名称与方法参数名称一致时,可以省略 @RequestParam

@PathVariable

  • 功能:处理动态的 URL,URL 的值作为控制器方法的参数。

@RequestBody

  • 功能:接收来自请求体的参数,处理非 application/x-www-form-urlencoded 编码格式的数据,如 application/jsonapplication/xml 等。

3.4 数据响应

数据响应类型

  • JSON:默认情况下,@RestController 会将对象数据转换为 JSON 格式响应。
  • 视图页面@Controller 返回视图页面,通过模板引擎(如 Thymeleaf)渲染数据。

示例代码

JSON 响应示例

@RestController
public class ExampleController {
    @GetMapping("/json")
    public Map<String, String> getJson() {
        Map<String, String> response = new HashMap<>();
        response.put("message", "Hello, World!");
        return response;
    }
}

视图页面响应示例

@Controller
public class PageController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("name", "World");
        return "hello";
    }
}

对应的 Thymeleaf 模板 hello.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello</title>
</head>
<body>
    <h1 th:text="'Hello, ' + ${name} + '!'"></h1>
</body>
</html>

路由映射示例

@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/users")
    public List<User> getUsers() {
        // 返回用户列表
    }

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // 创建新用户
    }
}

参数传递示例

访问地址:http://127.0.0.1:8080/greet?name=ghj

@RestController
public class ParamController {
    @GetMapping("/greet")
    public String greet(@RequestParam String name) {
        return "Hello, " + name;
    }

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        // 根据 ID 返回用户信息
    }
}

四、文件上传+拦截器

4.1 静态资源访问

默认目录

  • 创建项目:使用 IDEA 创建 Spring Boot 项目,会默认创建 classpath:/static/ 目录,静态资源一般放在这个目录下即可。
  • 自定义静态资源过滤策略:如果默认的静态资源过滤策略不能满足开发需求,也可以自定义静态资源过滤策略。

配置示例

application.properties 中定义过滤规则和静态资源位置:

spring.resources.static-locations=classpath:/static/

# 这个原来默认是/**,也就是你往static里面放一张test.jpg文件,直接http://127.0.0.1:8080/test.jpg就能访问到,改了以后只能通过http://127.0.0.1:8080/images/test.jpg来访问了,文件还是存在static那个目录不变,只是规定了他的访问路径
spring.mvc.static-path-pattern=/images/**

4.2 文件上传

表单编码类型

表单的 enctype 属性决定了表单数据在发送到服务器之前应该如何进行编码。这对数据的解析和处理方式有直接影响。

  1. application/x-www-form-urlencoded

    • 默认编码:这是HTML表单的默认编码方式。

    • 数据格式:表单数据会被编码为键值对(key=value),多个键值对之间用&分隔。例如:

      name=John&age=30&city=New+York
      
    • 用途:适用于简单表单数据提交,如文本输入、单选按钮和复选框等。

    • 限制:不适合文件上传,因为它无法处理二进制数据。

  2. multipart/form-data

    <form method="POST" enctype="multipart/form-data" action="/upload">
        <input type="file" name="file">
        <input type="submit" value="Upload">
    </form>
    
    • 文件上传​:这种编码方式适用于文件上传。

    • 数据格式:表单数据会被分成多个部分(part),每个部分包含一个表单字段的数据。每个部分都有独立的头部信息,用于描述该部分的数据。示例如下:

      --boundary
      Content-Disposition: form-data; name="text1"
      
      text default
      --boundary
      Content-Disposition: form-data; name="file1"; filename="a.txt"
      Content-Type: text/plain
      
      Content of a.txt.
      --boundary--
      
    • 用途:适合处理复杂表单数据,特别是包含文件的表单提交。

    • 优势:能够处理多种类型的数据,包括文本和二进制文件,适应性更强。

代码实现

SpringBoot工程嵌入的tomcat限制了请求的文件大小,每个文件的配置最大为1Mb,单次请求的文件的总数不能大于10Mb。要更改这个默认值需要在配置文件(application.properties)中加入两个配置:

# 单个上传文件的大小
spring.servlet.multipart.max-file-size=10MB
# 一次上传文件的总大小
spring.servlet.multipart.max-request-size=20MB

当表单的enctype="multipart/form-data"时,可以使用MultipartFile获取上传的文件数据,再通过transferTo方法将其写入到磁盘中。

示例代码

package com.example.springboot1.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String up(String nickname, MultipartFile photo, HttpServletRequest request) throws IOException {
        // 为什么需要HttpServletRequest request?
        // 即请求所属的上下文,需要通过HttpServletRequest request获取项目的路径

        System.out.println("nickname: " + nickname);
        // 获取图片的原始名称
        System.out.println("photo.getOriginalFilename(): " + photo.getOriginalFilename());
        // 获取文件类型
        System.out.println("photo.getContentType(): " + photo.getContentType());
        // 获取文件大小
        System.out.println("photo.getSize(): " + photo.getSize());

        // 获取项目的路径
        String path = request.getServletContext().getRealPath("/upload/");
        System.out.println("path: " + path);
        // 保存文件
        saveFile(photo, path);

        return "上传成功";
    }

    private void saveFile(MultipartFile photo, String path) throws IOException {
        // 判断存储的目录是否存在,如果不存在则创建
        File dir = new File(path);
        if (!dir.exists()) {
            // 创建目录
            dir.mkdir();
        }
        // 保存文件
        File file = new File(path + photo.getOriginalFilename());
        photo.transferTo(file);
    }
}

使用Apipost模拟文件上传​

image-20240628005943204

运行结果

nickname: ghj
photo.getOriginalFilename(): 头像_圆形(1).png
photo.getContentType(): image/png
photo.getSize(): 106868
path: C:\Users\67093\AppData\Local\Temp\tomcat-docbase.8080.5162878757086184860\upload\
// 注意到path中有tomcat虚拟机当前运行的地址
image-20240628010557311

这张图片现在只是存到了服务器中,并不在static文件夹中,所以用户不能访问到这张图片,要怎么解决呢?

在配置文件(application.properties)中加入一个配置:

spring.web.resources.static-locations=/upload/
# 为什么upload前面有一个斜线,表示服务器当前目录

浏览器访问http://127.0.0.1:8080/头像_圆形(1).png就可以看到了。

image-20240628011332126

ps. 每次重启项目图片存储的临时虚拟机地址就会改变(你可以理解为重启一次图片就删了),需要重新上传才能访问到。

4.3 拦截器

拦截器在Web系统中非常常见,对于某些全局统一的操作,我们可以把它提取到拦截器中实现。总结起来,拦截器大致有以下几种使用场景:

  • 权限检查:如登录检测,进入处理程序检测是否登录,如果没有,则直接返回登录页面。
  • 性能监控:有时系统在某段时间莫名其妙很慢,可以通过拦截器在进入处理程序之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。
  • 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有提取Locale、Theme信息等,只要是多个处理程序都需要的,即可使用拦截器实现。

SpringBoot定义了Handlerlnterceptor接口来实现自定义拦截器的功能,Handlerlnterceptor接口定义了preHandle、postHandle、afterCompletion三种方法,通过重写这三种方法实现请求前、请求后等操作。

image-20240628012512143

拦截器定义

public class LoginInterceptor extends HandlerInterceptor {
    /**
     * 在请求处理之前进行调用(Controller方法调用之前)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (条件) {
            System.out.println("通过");
            return true;
        } else {
            System.out.println("不通过");
            return false;
        }
    }
}

拦截器注册

  • addPathPatterns 方法定义拦截的地址
  • excludePathPatterns 定义排除某些地址不被拦截
  • 添加的一个拦截器没有 addPathPatterns 任何一个 URL 则默认拦截所有请求
  • 如果没有 excludePathPatterns 任何一个请求,则默认不放过任何一个请求
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");
    }
}
  1. @Configuration

    • 解释:这是一个配置类注解。
    • 作用:Spring 会将这个类作为配置的一部分来处理。
  2. public class WebConfigurer implements WebMvcConfigurer

    • 解释:实现了 WebMvcConfigurer 接口。
    • 作用:用于自定义 Spring MVC 的配置。
  3. @Override public void addInterceptors(InterceptorRegistry registry)

    • 解释:覆盖 WebMvcConfigurer 接口中的 addInterceptors 方法。
    • 作用:用于注册拦截器。
  4. registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");

    • 解释:添加自定义拦截器 LoginInterceptor,并指定拦截的 URL 路径模式。
      • addInterceptor(new LoginInterceptor()):添加 LoginInterceptor 拦截器。
      • addPathPatterns("/user/\**"):拦截所有以 /user/ 开头的请求路径。

到这里可能Java的基础知识有点忘记了,回顾一下extendsimplements 区别?

extends和implements区别_extends implements-CSDN博客

具体代码实现

image-20240628023323986

LoginInterceptor

package com.example.springboot1.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// 上述是新版的写法,旧版是引javax如下:
// import javax.servlet.http.HttpServletRequest;
// import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("LoginInterceptor");
        return true;
    }
}

我们查看接口HandlerInterceptor的源代码,如下,里面什么都没有写:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.web.servlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

WebConfig

package com.example.springboot1.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.springboot1.interceptor.LoginInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");
    }
}

ps. 拦截器要是里面没有写,是不会阻碍后续消息的继续传递的,如图,拦截到了但是还是放行了:

image-20240628024057569 image-20240628024107231

五、RESTful服务+Swagger

5.1 RESTful介绍

RESTfuI是目前流行的互联网软件服务架构设计风格。

REST(Representational StateTransfer,表述性状态转移)一词是由RoyThomasFielding在2000年的博士论文中提出的,它定义了互联网软件服务的架构原则,如果一个架构符合REST原则,则称之为RESTfuI架构。

REST并不是一个标准,它更像一组客户端和服务端交互时的架构理念和设计原则,基于这种架构理念和设计原则的WebAPI更加简洁,更有层次。

image-20240628113517220

RESTful的特点

  • 每一个URI代表一种资源,资源的表现形式是JSON或者HTML。
  • 客户端使用GET、POST、PVT、DELETE四种表示操作方式的动词对服务端资源进行操作:GET用于获取资源,POST用于新建资源(也可以用于更新资源)PUT用于更新资源,DELETE用于删除资源。
  • 通过操作资源的表现形式来实现服务端请求操作。
  • 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都包含必需的信息。

符合RESTful规范的WebAPI需要具备如下两个关键特性

  • 安全性:安全的方法被期望不会产生任何副作用,当我们使用GET操作获取资源时,不会引起资源本身的改变,也不会引起服务器状态的改变。
  • 幂等性:幂等的方法保证了重复进行一个请求和一次请求的效果相同(并不是指响应总是相同的,而是指服务器上资源的状态从第一次请求后就不再改变了),在数学上幂等性是指N次变换和一次变换相同。

HTTP Method

  • HTTP提供了POST、GET、PUT、DELETE等操作类型对某个Web资源进行Create、Read、Update和Delete操作。
  • 一个HTTP请求除了利用URI标志目标资源之外,还需要通过HTTPMethod指定针对该资源的操作类型,一些常见的HTTP方法及其在RESTfuI风格下的使用:(仅作参考)
HTTP 方法 操作 返回值 特定返回值
POST Create 201 (Created),提交或保存资源 404 (Not Found),409 (Conflict) 资源已存在
GET Read 200 (OK),获取资源或数据列表,支持分页、排序和条件查询 200 (OK) 返回资源,404 (Not Found) 资源不存在
PUT Update 200 (OK) 或 204 (No Content),修改资源 404 (Not Found) 资源不存在,405 (Method Not Allowed) 禁止使用改方法调用
PATCH Update 200 (OK) 或 204 (No Content),部分修改 404 (Not Found) 资源不存在
DELETE Delete 200 (OK),资源删除成功 404 (Not Found) 资源不存在,405 (Method Not Allowed) 禁止使用改方法调用

HTTP状态码

HTTP状态码就是服务向用户返回的状态码和提示信息,客户端的每一次请求,服务都必须给出回应,回应包括HTTP状态码和数据两部分。

HTTP定义了40个标准状态码,可用于传达客户端请求的结果。状态码分为以下5个类别:

  • 1xx:信息,通信传输协议级信息
  • 2xx:成功,表示客户端的请求已成功接受
  • 3xx:重定向,表示客户端必须执行一些其他操作才能完成其请求
  • 4xx:客户端错误,此类错误状态码指向客户端
  • 5xx:服务器错误,服务器负责这写错误状态码

RESTfuIAPI中使用HTTP状态码来表示请求执行结果的状态,适用于RESTAPI设计的代码以及对应的HTTP方法:

HTTP 状态码 返回值 HTTP Method 特定返回值
200 OK GET 服务器成功返回用户请求的数据,该操作是幂等的 (Idempotent)
201 Created POST/PUT/PATCH 用户新建或修改数据成功
202 Accepted * 表示一个请求已经进入后台排队(异步任务)
204 No Content DELETE 用户删除数据成功
400 Invalid Request POST/PUT/PATCH 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的
401 Unauthorized * 表示用户没有权限(令牌、用户名、密码错误)
403 Forbidden * 表示用户得到授权(与401错误相对),但是访问是被禁止的
404 Not Found * 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的
406 Not Acceptable GET 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)
410 Gone GET 用户请求的资源被永久删除,且不会再得到的
422 Unprocessable Entity POST/PUT/PATCH 当创建一个对象时,发生一个验证错误
500 Internal Server Error * 服务器发生错误,用户将无法判断发出的请求是否成功

5.2 构建RESTful应用接口

SpringBoot提供的spring-boot-starter-web组件完全支持开发RESTfulAPl,提供了与REST操作方式(GET、POST、PUT、DELETE)对应的注解:

  • @GetMapping:处理GET请求,获取资源。
  • @PostMapping:处理POST请求,新增资源。
  • @PutMapping:处理PUT请求,更新资源。
  • @DeleteMapping:处理DELETE请求,删除资源。
  • @PatchMapping:处理PATCH请求,用于部分更新资源。

在RESTfuI架构中,每个网址代表一种资源,所以URI中建议不要包含动词,只包含名词即可,而且所用的名词往往与数据库的表格名对应。

传统的设计方法如果要 删除一个用户
get http://localhost/del?id=10 涉及动词

使用RESTful的架构

delete http://localhost/user/10

以下是一些更多的例子:

HTTP Method 接口地址 接口说明
POST /user 创建用户
GET /user/id 根据 id 获取用户信息
PUT /user 更新用户
DELETE /user/id 根据 id 删除对应的用户

具体代码实现

image-20240628120636408
package com.example.springboot1.controller;

import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;

import com.example.springboot1.entity.User;

@RestController
public class UserController {

    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable int id) {
        return "根据ID获取用户" + id;
    }

    @PostMapping("/user")
    public String save(User user) {
        return "添加用户";
    }

    @PutMapping("/user")
    public String update(User user) {
        return "更新用户";
    }

    @DeleteMapping("/user/{id}")
    public String deleteById(@PathVariable int id) {
        return "根据ID删除用户";
    }
}

@PathVariable:注解用于从URL路径中提取变量(例如用户ID)。

image-20240628120900428

5.3 使用Swagger生成API文档

介绍

  • Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,是非常流行的API表达工具。
  • Swagger能够自动生成完善的RESTfulAPI文档,,同时并根据后台代码的修改同步更新,同时提供完整的测试页面来调试API。
  • 总的来说两个功能:接口调试+动态生成接口文档
image-20240628130622629

使用

在Spring Boot项目中集成Swagger非常简单,只需在项目中引入springfox-swagger2springfox-swagger-ui依赖即可。

pom.xml 中添加依赖:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

springfox-swagger2:这是Swagger的核心库,提供了自动生成API文档的功能。

springfox-swagger-ui:这是Swagger UI库,提供了一个Web界面,方便查看和测试API。

image-20240628122809058

配置Swagger代码如下(不用记直接用就行了):

@Configuration只要是Spring里面的配置都要加上这个注解

@Bean 注解是 Spring 框架中的一个重要注解,用于告诉 Spring 容器一个方法将返回一个需要注册为 Spring 应用上下文中的 bean 的对象。它通常用于配置类中,结合 @Configuration 注解一起使用。

@Configuration // 告诉Spring容器,这个类是一个配置类
@EnableSwagger2 // 启用Swagger2功能
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                // com包下所有API都交给Swagger2管理
                .apis(RequestHandlerSelectors.basePackage("com"))
                .paths(PathSelectors.any())
                .build();
    }

    // API文档页面显示信息,这部分自己改
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("演示项目API") // 标题
                .description("学习Swagger2的演示项目") // 描述
                .build();
    }
}

💣注意事项

Spring Boot 2.6.X后与Swagger有版本冲突问题,需要在 application.properties中加入以下配置:

# 指定 Spring MVC 在处理 URL 路径时使用的匹配策略。
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

然后会发现改了也还不行,原因是Swagger不更新了,导致跟新版本的不兼容。
SpringBoot的版本从最新版改成2.7.6吧,swagger别改成3.0.0,我们前面写代码用的都是swagger2,改成3的版本页面进不去,我们学生做项目也用不着最新版本的。(参考链接:高版本springboot整合swagger3.0_swagger版本-CSDN博客

小插曲:

突然发现properties中的注释全变成?????了,如图:

image-20240628123516846

解决办法:解决IDEA中.properties文件中文变问号(???)的问题(已解决)_application.properties中文都变成问号-CSDN博客

🎉都解决了以后手动重启项目,访问http://localhost:8080/swagger-ui.html即可看到页面。

image-20240628131048793

常用注释

Swagger提供了一系列注解来描述接口信息,包括接口说明、请求方法、请求 参数、返回信息等。

具体要用的话可以百度,视频简单的讲解:https://www.bilibili.com/video/BV1nV4y1s7ZN?t=1138.5&p=5

注解 属性 说明 示例
@Api value 字符串 可用于类头上,class描述 @Api(value = "xxx", description = "xxx")
description 字符串 可用于类头上,class描述
@ApiOperation value 字符串 可用于方法头上,参数的描述容器 @ApiOperation(value = "xxx", notes = "xxx")
notes 字符串
@ApiImplicitParams @ApiImplicitParam数组 可用于方法头上,参数的描述容器 @ApiImplicitParams({ @ApiImplicitParam(), @ApiImplicitParam(...) })
@ApiImplicitParam name 与参数命名相符 可用于@ApiImplicitParams中的参数描述 示例见上面的设置
value 字符串 参数说明
required 布尔 true/false
dataType 字符串 参数的数据类型
paramType 字符串 参数的请求方式:query/path
defaultValue 字符串 在API参数中默认的值
@ApiResponses @ApiResponse数组 可用于方法头上,参数的描述容器 @ApiResponses({ @ApiResponse(), @ApiResponse(...) })
@ApiResponse code 整数 可用于@ApiResponses中的参数描述 @ApiResponse(code = 200, message = "Successful")
message 字符串 错误描述
@ApiIgnore 忽略某个API
@ApiError 发生错误时的返回信息

六、MyBatis-Plus快速上手

6.1 ORM介绍

  • ORM(Object Relational Mapping,对象关系映射)是为了解决面向对象与关系数据库存在的互不匹配现象的一种技术。
  • ORM通过使用描述对象和数据库之间映射的元数据将程序中的对象自动持久化到关系数据库中。
  • ORM框架的本质是简化编程中操作数据库的编码。
image-20240628141135033

6.2 MyBatis-Plus介绍

  • MyBatis是一款优秀的数据持久层ORM框架,被广泛地应用于应用系统。
  • MyBatis能够非常灵活地实现动态SQL,可以使用XML或注解来配置和映射原生信息,能够轻松地将Java的POJO(Plain OrdinaryJava Object,普通的Java对象)与数据库中的表和字段进行映射关联。
  • MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上做了增强简化了开发。
image-20240628151916099

添加依赖

<!-- MyBatis-Plus依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<!-- MySQL驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
<!-- 数据连接池Druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.20</version>
</dependency>

数据库连接池(Connection Pool)是一种用于管理数据库连接的机制。它通过维护一个数据库连接的缓存池,避免了频繁地创建和销毁数据库连接,从而提高了数据库操作的效率。

全局配置

  • application.properties配置数据库相关信息

    # 指定了数据源的类型为Druid数据源
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    # 指定了用于连接数据库的 JDBC 驱动类。JDBC 驱动是 Java 应用程序与数据库之间通信的桥梁,不同的数据库有不同的 JDBC 驱动类。
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    # 下面的这三行需要根据自己的情况做一些修改
    # mydb为对应的数据库名字
    spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false
    spring.datasource.username=root
    spring.datasource.password=root
    # 指定了日志输出的格式
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
  • 添加@MapperScan注解

    他是数据库操作(映射)相关的组件,相关的操作最后都会放在Mapper包里面,我们需要告诉他我们的Mapper包在哪里。

    @SpringBootApplication
    @MapperScan("com.xx.mapper")
    public class Springboot1Application {
    	主启动程序
    }
    

6.3 MyBatis-Plus CRUD操作

mybatis原来叫ibatis

CRUD 是四个基本操作的缩写,用于对数据库中的数据进行处理。这四个操作分别是:

  1. Create(创建):用于在数据库中插入新的数据记录。
  2. Read(读取):用于从数据库中读取数据记录。
  3. Update(更新):用于更新数据库中的现有数据记录。
  4. Delete(删除):用于删除数据库中的数据记录。

CRUD注解

注解 功能
@Insert 实现插入
@Update 实现更新
@Delete 实现删除
@Select 实现查询
@Result 实现结果集封装
@Results 可以与@Result一起使用,封装多个结果集
@One 实现一对一结果集封装
@Many 实现一对多结果集封装

查询演示

  1. 准备好了对应的数据库如下:

    image-20240629175857892

  2. pom.xml中添加上面提到的依赖-->application.properties配置数据库相关信息-->创建mapper包,启动程序处配置扫描包路径(右键相对路径)。

    image-20240629180511632

  3. 所有数据库的操作都可以让MyBatis来进行,我们不需要创建类,只需要在mapper包中创建一个接口就可以了。

    一般的命名规范是按照操作的表来命名,譬如我现在要操作user表,就将该接口命名为UserMapper

    先做一个查询,查询数据库中的用户,我们可以先定义一个User类,注意要跟数据库中的一一对应才可以起到映射:

    image-20240629181458156

    然后在UserMapper中写:

    package com.example.mpdemo.mapper;
    
    import com.example.mpdemo.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    @Mapper
    public interface UserMapper {
    
        // 先做一个查询,查询数据库中的用户,我们可以先定义一个User类
        @Select("select * from user")
        public List<User> find();
    }
    
  4. 定义完毕后要怎么使用呢?切记不要去实现这个接口,对于Spring来讲,他会自动实例化、管理这个UserMapper,所以我们只需要加上@mapper的注解就可以了,使用的时候只需要定义一个属性就可以了。

    package com.example.mpdemo.controller;
    
    import com.example.mpdemo.entity.User;
    import com.example.mpdemo.mapper.UserMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class UserController {
    
        @Autowired
        private UserMapper userMapper;
    
        @GetMapping("/user")
        public String query() {
    
            List<User> List = userMapper.find();
            System.out.println(List);
            return "查询用户";
        }
    }
    

    @Autowired是Spring提供的功能,他的意思是要注入UserMapper,他会自动实例化mapper中定义的对象并注入,如果没有这个那么属性就是空的。

  5. 注意要报错把SpringBoot的版本改成2.7.6,新版本毛病多哎。运行后访问/user,会出现查询结果如下:

    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f2035dd] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@34567e16] will not be managed by Spring
    ==>  Preparing: select * from user
    ==> Parameters: 
    <==    Columns: id, username, password, birthday
    <==        Row: 1, ghj, 123, 2007-09-24
    <==        Row: 2, lsm, 456, 2015-07-11
    <==        Row: 3, Kono Kaito, 8Te0RKEGIi, 2017-02-23
    <==        Row: 4, Dennis Gonzales, uMn1wnhyYt, 2009-01-08
    <==        Row: 5, Kyle Cole, iWgqwb501Z, 2019-09-02
    <==        Row: 6, Sarah Warren, SHDFVTynwf, 2010-10-22
    <==        Row: 7, Yam Sze Yu, u7Osl4yIyj, 2005-07-28
    <==        Row: 8, Tammy Snyder, lHIV71RHqn, 2024-03-12
    <==        Row: 9, Hung Wai Han, UIrDZhCPR9, 2003-12-02
    <==        Row: 10, Ikeda Hazuki, 8x0mEYGnWo, 2012-05-28
    <==      Total: 10
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f2035dd]
    [User{id=1, username='ghj', password='123', birthday='2007-09-24'}, User{id=2, username='lsm', password='456', birthday='2015-07-11'}, User{id=3, username='Kono Kaito', password='8Te0RKEGIi', birthday='2017-02-23'}, User{id=4, username='Dennis Gonzales', password='uMn1wnhyYt', birthday='2009-01-08'}, User{id=5, username='Kyle Cole', password='iWgqwb501Z', birthday='2019-09-02'}, User{id=6, username='Sarah Warren', password='SHDFVTynwf', birthday='2010-10-22'}, User{id=7, username='Yam Sze Yu', password='u7Osl4yIyj', birthday='2005-07-28'}, User{id=8, username='Tammy Snyder', password='lHIV71RHqn', birthday='2024-03-12'}, User{id=9, username='Hung Wai Han', password='UIrDZhCPR9', birthday='2003-12-02'}, User{id=10, username='Ikeda Hazuki', password='8x0mEYGnWo', birthday='2012-05-28'}]
    

    前半部分是SQL查询日志,后半部分是查询的打印结果。

  6. 一般来说,我们需要把数据转为Json,然后传入前端,需要怎么做呢?

    只需要把返回值改为List就行,他会自动进行转换。

        @GetMapping("/user")
        public List query() {
    
            List<User> List = userMapper.find();
            System.out.println(List);
            return List;
        }
    
    image-20240629184043246

插入演示

使用符号#来表示下方出现的变量或是类中的属性。

@Mapper
public interface UserMapper {

    // 先做一个查询,查询数据库中的用户,我们可以先定义一个User类
    @Select("select * from user")
    public List<User> find();

    @Insert("insert into user values (#{id}, #{username}, #{password}, #{birthday})")
    public int insert(User user);
    // 返回值int表示插入了几条记录
}
@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @PostMapping("/user")
    public String insert(User user) {
        int i = userMapper.insert(user);
        if (i > 0) {
            return "插入成功";
        } else {
            return "插入失败";
        }
    }
}

image-20240629185325244

image-20240629185522146

没有传id,会发现插入成功,IDEA中查看一下SQL日志:

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5463c82a] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1f301cc7] will not be managed by Spring
==>  Preparing: insert into user values (?, ?, ?, ?) # 这里看不到插入的数值
==> Parameters: 0(Integer), sadasd(String), 123(String), 2022-10-23(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5463c82a]

删除、修改操作

就不一一举例了,下面是一个小总结:

@Mapper
public interface UserMapper {
    @Insert("insert into user values(#{id},#{username},#{password},#{birthday})")
    int add(User user);

    @Update("update user set username=#{username},password=#{password},birthday=#{birthday} where id=#{id}")
    int update(User user);

    @Delete("delete from user where id=#{id}")
    int delete(int id);

    @Select("select * from user where id=#{id}")
    User findById(int id);

    @Select("select * from user")
    List<User> getAll();
}

Plus的改进

之前我们学习的这些数据库操作,都是MyBatis中的,MyBatis-Plus又对其进行了简化,之前的语句都可以不写哈哈哈,经典白学(狗头)

那么,在Plus中,要怎么写呢?

其实我不喜欢Plus,大概看看就行了,实际中还是比较习惯用MyBatis

// mapper目录下UserMapper
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 这个是属于Plus的
import com.example.mpdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
	// 注意类的名字User要与数据库中的表一样的名字才可以起作用
}
@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/user")
    public List query() {

        List<User> List = userMapper.selectList(null); // 直接调用里面的方法,查询条件为nill,表示select *
        System.out.println(List);
        return List;
    }

    @PostMapping("/user")
    public String insert(User user) {
        int i = userMapper.insert(user); // 这个不用改,应为里面原来就有一样的insert方法
        if (i > 0) {
            return "插入成功";
        } else {
            return "插入失败";
        }
    }
}

还有更多注解,比如TableName()注解可以解决类名跟表明不一样的问题、TableId(type = IdType.AUTO)可以实现主键自增回传​等等,多去官网查询注解配置 | MyBatis-Plus (baomidou.com)更多实用的注解,可以省很多事情!

七、M-P多表查询及分页查询

MyBatis-Plus只是对单表的查询做了增强,但是多表没有任何改变,所以我们接下来学的是MyBatis的知识点

7.1 多表查询

实现复杂关系映射,可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置。

注解 说明
@Results 代替标签,该注解中可以加入单个或多个@Result注解。
@Result 代替标签和标签,@Result中可以使用以下属性:
- column:数据表的字段名称
- property:类中对应的属性名称
- one:与@One注解配合,进行一对一的映射
- many:与@Many注解配合,进行一对多的映射
@One 代替标签,用于指定查询中返回的单一对象
通过select属性指定用于多表查询的方法
使用格式:@Result(column="", property="", one=@One(select=""))
@Many 代替标签,用于指定查询中返回的集合对象
使用格式:@Result(column="", property="", many=@Many(select=""))

准备好两张关联的表来进行测试,分别是t_user、t_order(用户订单信息,外键uid关联user(id)):

image-20240630105157887

image-20240630105215145

查询每个用户的订单

现在我们的查询要求是,在查询用户的同时还需要查询他名下的所有订单。

因此设计用户类的时候就要做一些修改,必须手动来完成。

  1. 创建Order实体类,跟表中的名字一样,因为我们下面用不到Plus的内容,所以其实类名跟表名不一样也没关系。

    package com.example.mpdemo.entity;
    
    import com.baomidou.mybatisplus.annotation.TableName;
    
    @TableName("t_order") // 这一句其实也可以不加
    public class Order {
        private int id;
        private String order_time;
        private double total;
        private int uid;
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getOrder_time() {
            return order_time;
        }
    
        public void setOrder_time(String order_time) {
            this.order_time = order_time;
        }
    
        public double getTotal() {
            return total;
        }
    
        public void setTotal(double total) {
            this.total = total;
        }
    
        public int getUid() {
            return uid;
        }
    
        public void setUid(int uid) {
            this.uid = uid;
        }
    
        @Override
        public String toString() {
            return "Order{" +
                    "id=" + id +
                    ", order_time='" + order_time + '\'' +
                    ", total=" + total +
                    ", uid=" + uid +
                    '}';
        }
    }
    
  2. 更改实体User中的属性,加入orders属性。注意每个属性都要有对应的get跟set,要不然会无法映射。

    package com.example.mpdemo.entity;
    
    import com.baomidou.mybatisplus.annotation.*;
    
    import java.util.List;
    
    @TableName("t_user")
    public class User {
        @TableId(type = IdType.AUTO)
        private int id;
        private String username;
        private String password;
        private String birthday;
    
        // 描述用户的所有订单
        @TableField(exist = false) // 告诉MyBatis-Plus这个字段不是数据库中的字段
        // 这个注解其实是Plus的注解,我们就算后面把他删掉,得到的还是为空
        private List<Order> orders;
    
        public int getId() {
            return id;
        }
    
        public List<Order> getOrders() {
            return orders;
        }
    
        public void setOrders(List<Order> orders) {
            this.orders = orders;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getBirthday() {
            return birthday;
        }
    
        public void setBirthday(String birthday) {
            this.birthday = birthday;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", birthday='" + birthday + '\'' +
                    ", orders=" + orders +
                    '}';
        }
    }
    
  3. 如果我们直接写一个很简单方法来查询,会发现不能实现,如下:

    -- UserMapper.java
    
    package com.example.mpdemo.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 这个是属于Plus的
    import com.example.mpdemo.entity.User;
    import org.apache.ibatis.annotations.*;
    
    import java.util.List;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    
        // 根据用户id查询信息
        @Select("select * from t_user where id = #{id}")
        User selectById(int id);
    
        // 查询用户及其所有的订单
        @Select("select * from t_user")
        List<User> selectAllUserAndOrder();
    }
    
    -- UserController.java
    
        @GetMapping("/user/findAll")
        public List<User> findAll() {
            return userMapper.selectAllUserAndOrder();
        }
    

    访问对应路径,发现orders属性为空:

    image-20240630153935696
  4. 下面的代码要求能看懂,是对上面

    -- UserMapper.java 
    
    	// 查询用户及其所有的订单
        @Select("select * from t_user")
        List<User> selectAllUserAndOrder();
    

    代码的修改,使其能够实现功能:

    --UserMapper.java 
    
    package com.example.mpdemo.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 这个是属于Plus的
    import com.example.mpdemo.entity.User;
    import org.apache.ibatis.annotations.*;
    
    import java.util.List;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    
        // 根据用户id查询信息
        @Select("select * from t_user where id = #{id}")
        User selectById(int id);
    
        // 查询用户及其所有的订单
        @Select("select * from t_user")
        @Results(
                {
                        @Result(column = "id", property = "id"), // 表示数据库中查出来的id(column),这一列要赋值给属性名id(property)
                        @Result(column = "username", property = "username"),
                        @Result(column = "password", property = "password"),
                        @Result(column = "birthday", property = "birthday"),
                        @Result(
                                column = "id", // 表示根据哪个字段去查询
                                property = "orders", // 表示要赋值的属性
                                javaType = List.class, // 表示要赋值的属性的类型
                                many = @Many(select = "com.example.mpdemo.mapper.OrderMapper.selectByUid") // 表示要调用的方法
                        )
                }
        )
        List<User> selectAllUserAndOrder();
    }
    
    -- OrderMapper.java // 提供根据uid查找订单的方法
    
    package com.example.mpdemo.mapper;
    
    import com.example.mpdemo.entity.Order;
    import org.apache.ibatis.annotations.*;
    
    import java.util.List;
    
    @Mapper
    public interface OrderMapper {
    
        @Select("select * from t_order where uid = #{uid}")
        List<Order> selectByUid(int uid);
    }
    
  5. 我们运行一下,发现查询成功:

    image-20240630162150444

查询每个订单的用户

需求描述:查询每个订单的用户,输入订单id,输出订单信息以及用户信息

举一反三,代码如下:

不要忘记在Controller中注入:

    @Autowired
    private OrderMapper orderMapper;
    @GetMapping("/order")
    // 接收一个id参数,根据id查询订单信息以及用户信息
    public Order queryOrder(int id) {
        return orderMapper.selectOrderAndUser(id);
    }
@Mapper
public interface OrderMapper {

    @Select("select * from t_order where uid = #{uid}")
    List<Order> selectByUid(int uid);

    // 查询每个订单的用户,输入订单id,输出订单信息以及用户信息
    @Select("select * from t_order where id = #{id}")
    @Results(
            {
                    @Result(column = "id", property = "id"),
                    @Result(column = "order_time", property = "order_time"),
                    @Result(column = "total", property = "total"),
                    @Result(column = "uid", property = "uid"),
                    @Result(
                            column = "uid",
                            property = "user",
                            one = @One(select = "com.example.mpdemo.mapper.UserMapper.selectById")
                    )
            }
    )
    Order selectOrderAndUser(int id);
}

image-20240630175352935

7.2 条件查询

顺便说一下

如果我们写SQL语句,上面加上条件,那肯定没什么好说的了,我本人也比较倾向于这种。

如果要使用Plus来做条件查询,见链接条件构造器 | MyBatis-Plus (baomidou.com),下面是一个简单的例子:

// 这个方法的作用是通过username为zhangsan的条件来查询用户,并返回匹配的用户列表。

@GetMapping("/user/find")
public List<User> findByCond() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("username", "zhangsan");
    return userMapper.selectList(queryWrapper);
}

7.3 分页查询

如果一张表中的数据太多的话,我们肯定就要对查询的信息条数做一个限制,类比如SQL中经常用到的select * from xx limit 0,10,那么用Plus怎么做呢?

Plus为我们提供了一个分页的插件,让我们快速的完成。

  1. 编写配置文件

    @Configuration
    public class MyBatisPlusConfig {
        @Bean
        public MybatisPlusInterceptor paginationInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 定义了一个拦截器,当作mysql的分页查询的时候进行拦截处理
            PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); // 我们只需要改动这一行,也就是数据库的类型换成自己的
            interceptor.addInnerInterceptor(paginationInterceptor);
            return interceptor;
        }
    }
    
    image-20240630181110659
  2. Controller中写分页查询,下面是一个示例:

    @GetMapping("/user/findByPage")
    public IPage findByPage() {
        // 设置起始值及每页条数
        Page<User> page = new Page<>(0, 2); // 第一个参数是“你想从第几条开始取”,第二个参数是“你想取几条”
        IPage iPage = userMapper.selectPage(page, null); // null的位置可以放条件
        return iPage;
    }
    

    iPage中不仅仅有当前的查询结果,“总共有多少页、当前是多少页等等”都被封装在了里面,我们需要的时候可以取用。