SpringBoot 介绍

  • Spring Boot是由Pivotal团队提供的基于Spring的全新框架,旨在简化Spring应用的初始搭建和开发过程。

  • Spring Boot是所有基于Spring开发项目的起点。

  • Spring Boot就是尽可能地简化应用开发的门槛,让应用开发、测试、部署变得更加简单。

SpringBoot 特点

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

Spring Boot 与 SSM

  • SSM是由三个独立的框架组合而成的,分别是Spring、Spring MVC和MyBatis的缩写。这三个框架经常结合使用,构建Java Web应用程序:
    • Spring:用于依赖注入和事务管理。
    • Spring MVC:用于Web层,处理HTTP请求。
    • MyBatis:是一个持久层框架,用于与数据库交互。
  • Spring Boot可以很好地与Spring MVCMyBatis结合使用,但其目的是简化整个应用程序的配置和部署。

SpringBoot3 环境要求

  • Spring Boot 2.7 是最后一个支持 JDK 8 的版本,根据官方公告,Spring Boot 2.7.x 会在2023年11月停止维护。
  • 未来能够获得官方免费维护的版本只有Spring Boot 3.0及以上版本,由于Spring Boot 3.x版本要求Java 17作为最低版本,因此需要安装JDK 17或以上版本运行。

Apache Maven

简单介绍

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

alt text

Maven 的作用

  • 项目构建:提供标准的,跨平台的自动化构建项目的方式
  • 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免资源间的版本冲突等问题
  • 统一开发结构:提供标准的,统一的项目开发结构,如下图所示:

alt text

下载 Maven

访问 Apache Maven 的官方下载页:

https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/

下载 “Binary zip archive”,注意不要讲 maven 解压到中文路径下。

Maven 仓库

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

alt text

配置国内 Maven 镜像

maven 默认连接的远程仓库位置并不在国内,因此有时候下载速度非常慢,可以配置一个国内站点镜像,可用于加速下载资源。

编辑文件:~/.m2/settings.xml 在 mirrors 标签下添加如下配置:

1
2
3
4
5
6
<mirrors>
<mirror>
<id>huaweicloud</id>
<url>https://repo.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>

这里以配置华为镜像源为例。

idea 中配置 Maven

默认情况下,idea 集成开发工具,自带 Maven,我们需要设置为我们自己安装的 Maven。

alt text

SpringBoot 入门

创建 SpringBoot 项目

Spring Initializr是一个在线工具,用于快速生成一个新的 Spring Boot 项目:https://start.spring.io/

当然国内也可以使用阿里云的镜像:http://start.spring.io 创建。

两者不同的是,SpringBoot 官方创建的 SpringBoot 版本偏新,而 阿里云镜像创建的则偏国内主流。

alt text

alt text

这里我们采用阿里云的镜像创建。我这里使用 idea 配置阿里云镜像创建 SpringBoot Web 项目。

alt text

alt text

创建完成的目录如下:

alt text

SpringBoot 目录结构

  • src/main/java/: 此目录包含项目的主要 Java 源代码
  • src/main/resources/: 存放项目的资源文件,例如配置文件、国际化属性文件、SQL 脚本等。
  • src/test/: 此目录用于存放项目的测试代码和测试资源。
  • pom.xml: Maven的配置文件,定义了项目的依赖、插件和其他设置
  • 项目名+Application.java: 这是 Spring Boot 应用程序的入口点。它通常包含 @SpringBootApplication 注释,并包含 main 方法来启动应用程序。

SpringBoot 系统配置

配置文件

Spring Boot 使用配置文件来配置和定制应用程序的行为,例如指定 SpringBoot 程序的端口、文件上传的路径等。

默认配置文件为:application.properties位于 src/main/resources/ 目录中。

当然除了 application.properties 我们还可以将后缀修改为 .yamlyml SpringBoot 程序会自动识别。

不同的是 properties 配置文件采用的是 key=value 的形式,而 yaml 则采用缩进的方式来表示层级关系。

例如:

alt text

alt text

实际项目中,yamlyml 格式的配置文件更受程序员们的青睐,后续配置文件我们也已 yml 为主。

自定义配置属性

SpringBoot 有一些内置的配置如:

  • 启动和服务器配置 server.port
  • 数据库连接spring.datasource
  • 日志设置logging.level

同时配置文件还支持一些自定义的属性配置,方便开发时使用。如与外部 API 交互的 URL、超时时间、API 密钥、应用程序的版本号、默认语言等。

Spring Boot 中框架提供了 @Value@ConfigurationProperties 两个关键注解从配置文件中读取并注入属性值。

@Value 的使用案例

@value 注解的作用是读取配置文件中的属性值,并将其注入到字段中。

首先在配置文件中添加如下自定义属性:

1
2
3
4
5
6
7
# 自定义属性
app:
name: firstSpringBootProject
custom:
host: 127.0.0.1
username: root
password: 123456

在程序中获取属性值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.qwesec.demo.entity;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* @author X1ongSec
* @date 2024/12/14 15:40
*/

@Component // 将该类交给spring管理
public class MyAppPropertites {
// 获取配置文件中的app.name属性值,将其赋值到appName变量中
@Value("${app.name}")
private String appName;

public String getAppName() {
return appName;
}

public void setAppName(String appName) {
this.appName = appName;
}
}

alt text

接着,在测试集类中,我们通过依赖注入的方式,获取到 MyAppPropertites 类的实例,并打印出 appName 属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.qwesec.demo;

import com.qwesec.demo.entity.MyAppPropertites;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {
@Autowired // 自动注入
private MyAppPropertites appPropertites;

@Test
void contextLoads() {
System.out.println(appPropertites.getAppName()); // 打印 appName 属性
}
}

alt text

可以发现成功输出了配置文件中的属性。

@ConfigurationProperties 的使用案例

@ConfigurationProperties 注解的作用是:将配置文件中的属性映射到 JavaBean 中,从而实现配置文件的统一管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.qwesec.demo.entity;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
*@author X1ongSec
*@date 2024/12/14 16:01
*/

@Component
@ConfigurationProperties(prefix = "app.custom")
public class Server {
private String host;
private String username;
private String password;

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

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;
}
}

alt text

接着在测试类中测试执行:

alt text

SpringBoot WEB 应用开发

MVC 架构应用

MVC 的介绍和工作流程

使用Spring Boot开发Web应用程序时,Spring MVC是其背后的主要驱动力,它为Web应用提供了模型-视图-控制器(MVC)的架构和组件。

MVC,即模型(Model)视图(View)控制器(Controller),是一种设计模式,用于将应用程序分为三个主要组件。

alt text

开发基本的 Web 应用

Spring Boot没有严格的项目结构,但它有一些约定,一个典型的Spring Boot项目的包结构如下:

目录 描述
com.example.myapp 项目的根包,通常以域名或公司名称的反转来命名
controller 存放处理请求和响应的控制器类
service 包含业务逻辑的服务类
repository 数据访问和持久化层,使用 MyBatis-Plus 等技术时可能命名为 mapper 或 dao
model 存放模型类,通常是 POJO(Plain Old Java Object)类,用于表示应用程序中的数据结构
config 存放配置类,用于配置应用程序的一些特殊设置,如数据库连接、安全配置等
dto 数据传输对象(Data Transfer Object),这里存放 DTO 类,用于在层之间传输数据,特别是在 Controller 和 Service 层之间
util 存放工具类,通常包含一些辅助性方法或工具函数

构建一个能够响应用户请求的Web服务,只需要经过以下三步:

  1. 引入spring-boot-starter-web依赖。
  2. 创建一个带有 @RestController 注解的类。
  3. 在此类中定义一些带有 @RequestMapping 或其相关注解的方法。

alt text

访问:

alt text

spring-boot-starter-web

  • Spring Boot将传统Web开发的mvc、json、tomcat等框架整合,提供了spring-boot-starter-web组件,简化了Web应用配置。

  • 创建SpringBoot项目勾选Spring Web选项后,会自动将spring-boot-starter-web组件加入到项目中。

  • spring-boot-starter-web启动器主要包括web、webmvc、json、tomcat等基础依赖组件,作用是提供Web开发场景所需的所有底层依赖。

  • webmvc为Web开发的基础框架,json为JSON数据解析组件,tomcat为自带的容器依赖。

alt text

控制器注解

@Controller 的使用案例

@Controller 注解,控制器类中的方法通常返回一个字符串,代表逻辑视图的名称。

这个视图名将被解析为一个实际的视图,例如一个JSPThymeleafFreeMarker 模板。

现如今一般都是前后端分离的界面,因此很少使用@Controller注解,而是使用 @RestController 注解。

alt text

使用@Controller注解,页面返回的不再是 index.html 字符串,而是 index.html 页面。

alt text

如果要返回 index.html 字符串,则需要使用 @ResponseBody 注解。

alt text

alt text

因此 @Controller 注解和 @ResponseBody 注解结合使用,才能返回字符串。并且通过只使用 @Controller 我们可以发现,这里很明显是配合模版来使用。

@RestController 的使用案例

@RestController@Controller@ResponseBody 注解的结合体。它标记一个类为控制器,且类中的每个方法默认返回数据而非视图。

alt text

访问:

alt text

可以看到,这里 SpringBoot 自动将 User 对象转为 JSON 格式返回,这是由于 SpringBoot 内嵌了 Jackson 框架。

在前后端分离的项目中,后端只需要提供 API 接口供前端调用,前端将后端返回的 JSON 数据解析在页面中即可。

请求和响应

请求映射

@RequestMapping 注解是Spring MVC中的核心注解,用于将请求路径映射到特定的控制器方法。

属性 描述 示例
value/path 定义 URI 模式,是 @RequestMapping 最常用的属性,类型为 String[] @RequestMapping("/books")
method 定义 HTTP 请求方法,例如 GET、POST、PUT、DELETE 等,类型为 RequestMethod[] @RequestMapping(value="/books", method=RequestMethod.GET)
params 定义请求必须满足的参数条件。可以指定参数存在、不存在或具有特定的值,类型为 String[] @RequestMapping(value="/books", params="type=novel")
只匹配有 type=novel 参数的请求
headers 定义必须满足的请求头条件,类型为 String[] @RequestMapping(value="/books", headers="Referer=http://www.example.com/")
只匹配来自指定 Referer 的请求

请求映射中的常见匹配方式

  1. @RequestMapping("/books") 会将所有到/books的请求映射到控制器方法
  2. @RequestMapping 注解的method属性允许根据HTTP请求的方法(如GET、POST、PUT、DELETE等)来进一步限定请求的匹配:
1
@RequestMapping(value = "/users", method = RequestMethod.GET)

为了简化开发,Spring 还提供了一些快捷注解:

1
2
3
4
5
@GetMapping(value = "/user") // HTTP的 GET 方法。
@PostMapping(value = "/user") // HTTP的 POST 方法。
@PutMapping(value = "/user") // HTTP的PUT方法。
@DeleteMapping(value = "/user") // HTTP 的 DELETE 方法。
@PatchMapping(value = "/user") // HTTP 的 PATCH 方法。

请求参数绑定

在Web应用中,服务端经常需要获取浏览器传递的数据,例如用于搜索查询、分页、排序和过滤等场景。

在Spring Boot中,有多种注解可用于将请求中传递的数据绑定到处理器方法的参数中,以便获取并处理这些数据。常用的注解包括:

  • @PathVariable: 从URI模板中提取值,如从/books/{id}中提取id。
  • @RequestParam: 获取查询参数或表单数据。
  • @RequestBody: 将请求主体(通常为JSON或XML)绑定到方法参数。
  • @RequestHeader: 获取请求头的值。
  • @CookieValue: 从cookie中提取值。

@PathVariable 注解

@PathVariable 注解主要从请求路径中获取值,如 /books/{id} 则请求 /books/1 获取的 ID 值就为1。

alt text

请求:

alt text

可以看到在路径中的 399 就被后端获取到设置为 Book 类的 id 属性值了。

@RequestParam 注解

@RequestParam 注解主要从请求参数中获取值,如 /books?id=1 则请求 /books?id=1 获取的 ID 值就为1。

alt text

请求:

alt text

如果请求参数与方法中的 id 参数相同,则可以忽略 @RequestParam 注解。

当然也可以接收多个参数同时还能接收通过 POST 请求接收的参数,如果要同时接收多种请求方式传入的参数,就需要使用到 @RequestMapping 注解。

下面看示例:接收多个请求方式传入的多个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class BookController {

@RequestMapping("/api/books/addBook")
public Book addBook(@RequestParam("id") Integer id, @RequestParam("title") String title, @RequestParam("author") String author, @RequestParam("publisher") String publisher) {
Book book = new Book();
book.setId(id);
book.setTitle(title);
book.setAuthor(author);
book.setPublisher(publisher);
return book;
}
}

这里需要传入 id、title、author、publisher 参数。

GET 请求:

alt text

POST 请求:

由于浏览器默认不支持 POST 请求,所以我们需要使用工具,我这里使用的是 ApiPost:

alt text

由于我们在控制器中没有接收 price 和 mark 同时并没有为其赋值,所以这两值为默认值 null

@RequestBody 注解

相信很多同学看到这里,都觉得接收多个参数时非常麻烦,那么有没有便捷的方法呢?当然有!

那就是使用对象来接收参数。

@RequestBody 注解的作用是将 HTTP 请求体 中的内容绑定到方法参数上,并通过 HttpMessageConverter 转换为所需的 Java 对象

1
2
3
4
5
6
7
8
9
@RestController
public class BookController {

@PostMapping("/api/books/addBook")

public Book addBook(@RequestBody Book book) {
return book;
}
}

请求:

alt text

可以看到,这里可以很轻松的获取到请求体中的内容,并且可以将其转换为所需的 Java 对象并为相关属性自动赋值。

不需要我们手动的进行 book.setId() 等。 但是 Book 类 必须存在 getter 方法!否则会返回 406 状态码 Not Acceptable。

同时 @RequestBody注解 仅支持接收 JSON 格式和XML的格式请求体,如果请求体是 application/x-www-form-urlencoded 格式,则无法正确解析,会返回 400 状态码 Bad Request。

这种方法也是我们开发中特别常用的,因此也就有了 实体类 的存在。

@RequestHeader 注解的使用

@RequestHeader 注解用于获取请求头中的信息

这里以获取请求头中的 User-AgentHost 字段为例:

1
2
3
4
5
6
7
8
@RestController
public class HeaderController {

@GetMapping("/api/details")
public String getDetails(@RequestHeader("User-Agent") String userAgent, @RequestHeader("Host") String host) {
return "User-Agent: " + userAgent + "<br /> Host: " + host;
}
}

请求:

alt text

@CookieValue 注解的使用

@CookieValue 注解用于获取请求中的 Cookie 值。

这里以获取 Cookie 中的 name 值为例,如果没有 name 值则返回默认值 guest:

1
2
3
4
5
6
7
@RestController
public class CookieController {
@GetMapping("/api/getCookName")
public String getCookieByName(@CookieValue(value = "name", defaultValue = "guest") String name) {
return "name: " + name;
}
}

alt text

如果没有设置,则返回默认值 guest:

alt text

JSON 响应

spring-boot-starter-web 依赖自带了 Jackson 库,它可以自动将Java对象序列化为JSON格式。

Jackson 支持将各种Java对象序列化为JSON,包括列表(List)、映射(Map)、集合(Set)、基本数据类型及其包装类等。

当使用@RestController注解时,Spring Boot会自动使用Jackson库来完成这些对象的序列化。

ResponseEntity

ResponseEntity 内置类用于全面控制HTTP响应,包括状态码、头部信息和响应体内容。

1
2
3
4
5
6
7
8
9
10
11
GetMapping("/item/{id}")
public ResponseEntity<Item> getItem(@PathVariable Long id) {
Item item = itemService.findById(id); // 调用服务层获取Item,此处仅为模拟
if (item != null) {
// 如果找到Item,返回状态码200 OK和Item对象
return ResponseEntity.ok(item);
} else {
// 如果没有找到Item,返回状态码404 Not Found
return ResponseEntity.notFound().build();
}
}

当然常用的还有:

1
2
3
return ResponseEntity.badRequest().body(null); // 返回400
return ResponseEntity.internalServerError().build(); // 返回500
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); // 自定义返回的状态码,这里以 404 为例

一般情况下,我们会自己定义一个通用类,用于构建和返回 HTTP 响应如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.qwesec.demo.common;

/**
* @author X1ongSec
* @date 2024/12/14 18:49
*/

public class R<T> {
private boolean success;
private String msg;
private T data;
private int code;

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public static <T> R<T> ok(T data) {
R<T> r = new R<T>();
r.setSuccess(true);
r.setMsg("ok");
r.setData(data);
r.setCode(20000);
return r;
}

public static <T> R<T> error(String msg, int errorCode) {
R<T> r = new R<T>();
r.setSuccess(false);
r.setMsg(msg);
r.setCode(errorCode);
return r;
}
}

接着控制器调用响应即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class BookController {
@GetMapping("/api/books/{id}")
public R<Book> getBook(@PathVariable Long id) {
Book book = new Book();
book.setId(1);
book.setTitle("Java学习记录");
book.setAuthor("X1ong");
book.setPublisher("www.qwesec.com");
book.setPrice(49.9);
book.setMark(9.9);
return R.ok(book);
}
}

内容如下:

alt text

如果遇到错误则返回【这里以未认证的返回示例】,由于还没有到数据库查询,因此只是模拟:

1
2
3
4
5
6
@RestController
public class BookController {
@GetMapping("/api/books/{id}")
public R<Book> getBook(@PathVariable Long id) {
return R.error("Unauthorized", 40001);
}

alt text

国内开发貌似更偏向于这种风格。

构建 RESTful 服务

RESTful 概述

  • RESTful服务是基于REST(Representational State Transfer,表述性状态转移)架构风格的Web服务。

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

RESTful 特点

  • 在 RESTful 架构中,所有的事物都视为资源,这些资源通过 URI(统一资源标识符)进行标识。例如,在提供书籍和作者信息的服务中,“书”和“作者”都被视为资源,它们分别可以通过类似 /books/authors 的 URI 进行访问。

  • 资源可以有不同的表示形式,如 JSON、XML 等。客户端请求一个资源时,服务器返回该资源的特定表示形式。客户端和服务器之间的交互完全通过这些表示形式进行。

  • 在 RESTful 架构中,所有的交互都必须是无状态的。这意味着每个请求必须包含所有必要的信息,以便服务器能够理解和处理该请求,而不依赖于之前的请求或存储在服务器上的上下文信息。

表述性状态转移

  • 表述性(Representational):指的是数据的表现形式。例如,一个“书”资源可以用 JSON、XML 或 HTML 格式表示。访问资源时,得到的是资源的一个“表述”,而不是资源本身。

  • 状态(State):指资源的当前状态,如书籍的书名、作者、出版日期等。 REST 是“状态无关”的,意味着每个请求都是独立的,包含了处理请求所需的全部信息。

  • 转移(Transfer):指状态的传递。当客户端请求一个资源时,服务器将资源的“表述”传递给客户端,即状态转移。

RESTful 设计原则

  • 在REST架构中,核心概念是资源,它们通常用名词表示,例如/users而不是/getUser。资源可以是单个实体或对象集合,如用户、订单、产品等。

  • 每个资源基本上都有创建、读取、更新和删除操作。RESTful服务利用HTTP方法来执行对资源的操作,具体包括:

    • GET:用于获取资源
    • POST:用于创建新的资源
    • PUT:用于更新或替换资源
    • DELETE:用于删除资源

SB 实现 RESTful 服务

实现 RESTful 所用的注解

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

用户管理模块 API 示例

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

具体实现示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
public class UserController {
@PostMapping("/user")
public String addUser(User user) {
return "创建用户成功";
}

@GetMapping("/user/{id}")
public String getUserById(@PathVariable Long id) {
return "根据id查询用户成功";
}

@PutMapping("/user/{id}")
public String updateUser(@PathVariable Long id, User user) {
return "更新用户成功";
}

@DeleteMapping("/user/{id}")
public String deleteUserById(@PathVariable Long id) {
return "删除用户成功";
}
}

使用 Swagger 生成 API 文档

介绍

swagger 是当下比较流行的实时接口文文档生成工具。接口文档是当前前后端分离项目中必不可少的工具,在前后端开发之前,后端要先出接口文档,前端根据接口文档来进行项目的开发,双方开发结束后在进行联调测试。

swagger 版本

swagger分为swagger2 和swagger3两个常用版本。二者区别不是很大,下面主要以 swagger2 为例进行介绍。

在引入方面,swag2 需要引入两个 jar 包,而 swag3 只需要引入一个即可。

引入依赖

1
2
3
4
5
6
7
8
9
10
11
<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>

引入配置

首先需要添加一个注解 @EnableSwagger2 表示开启 swagger 功能,这个注解可以在 SpringBoot 的启动器上,也可以单独定义一个 swagger 的配置类。

这里我们采用第二种方式进行演示,在 config 包下创建 SwaggerConfig 类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.qwesec.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.qwesec.demo.controller"))
.paths(PathSelectors.any())
.build();
}

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

其中 com.qwesec.demo.controller 为控制器的所在包,用于 swagger 扫描。

可能遇到的错误

如果你使用的是 SpringBoot 2.6.x 版本,可能会遇到如下错误,导致项目无法启动:

alt text

这是由于两个版本不兼容导致,解决方法就是在 SpringBoot 中添加如下配置:

1
2
3
4
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher

接着重启项目即可:

alt text

给 Controller 添加注解

其实到这里我们就可以访问了,路径为:http://ip:port/swagger-ui.html 如下:

alt text

这里对所有的 Controller 进行了一个扫描和展示,但是并没有显示每个控制器的作用,因此我们需要再控制器上添加注解对控制器进行描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
@Api(value = "用户接口", tags = {"用户接口"})
public class UserController {
@PostMapping("/user")
@ApiOperation("新增用户")
public String addUser(User user) {
return "创建用户成功";
}

@GetMapping("/user/{id}")
@ApiOperation("根据id查询用户")
public String getUserById(@PathVariable Long id) {
return "根据id查询用户成功";
}

@PutMapping("/user/{id}")
@ApiOperation("根据id更新用户信息")
public String updateUser(@PathVariable Long id, User user) {
return "更新用户成功";
}

@DeleteMapping("/user/{id}")
@ApiOperation("根据id删除用户")
public String deleteUserById(@PathVariable Long id) {
return "删除用户成功";
}
}

访问:

alt text

Swagger 的其他注解

alt text

文件上传功能

文件上传原理

浏览器上传文件的过程是基于HTTP协议进行的,并使用特定的请求类型和编码方式来传输文件数据。

alt text

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<br>
<input type="submit" value="upload" />
</form>
</body>
</html>

SB 实现文件上传功能

  • 在 Spring Boot 中处理文件上传主要依赖于 Spring Web 的 MultipartFile 接口。
  • MultipartFile 是一个专门用于处理HTTP请求中上传文件的接口
  • 当客户端(如浏览器)通过 multipart/form-data 格式的表单提交文件时,Spring MVC可以将这些文件映射为 MultipartFile 对象,以便在服务器端进行处理。

MultipartFile 接口

以下是 MultipartFile 接口的一些主要方法:

方法名 描述
byte[] getBytes() 返回文件的内容为字节数组
String getContentType() 返回文件的 MIME 类型,如 image/jpegtext/plain
InputStream getInputStream() 返回一个 InputStream,允许你读取文件的内容
String getName() 返回参数名称,例如:当表单中的 input 元素的 name 属性为“file”时,这个方法会返回“file”
String getOriginalFilename() 返回客户端在文件系统中的原始文件名
boolean isEmpty() 返回文件是否为空
void transferTo(File dest) 将上传的文件保存到目标文件或目录中

在控制器中,可以使用 @RequestParam 注解与 MultipartFile 类型的参数来接收上传的文件。

在这个示例中,假设有一个包含名为 fileinput 元素的表单用于文件上传。当用户提交表单时,可以在服务器端使用 MultipartFile 对象来访问和处理这个文件。

1
2
3
4
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
// 处理上传的文件
}

上传配置

application.properties 中,可以配置文件上传的相关属性,例如文件大小限制和存储位置。

  • spring.servlet.multipart.max-file-size 定义了单个上传文件的最大大小。如果文件超过此限制,将抛出异常。
  • spring.servlet.multipart.max-request-size 定义了整个 multipart 请求的最大大小,包括所有文件和表单数据。

例如:

1
2
3
4
5
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB

为了统一管理文件的上传路径,我们可以在配置文件中自定义一个属性,假设为 upload.path

1
2
3
4
5
6
7
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
upload:
path: uploads

当然我们还可以将文件上传到 oss 存储桶中进行存储。

后端代码实现

首先创建一个 FileUploadController 类,用于处理文件上传请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.qwesec.demo.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.qwesec.demo.common.R;

@RestController
@RequestMapping("/api/v2/files")
public class FileUploadController {

@Value("${upload.path}")
private String UPLOAD_DIR;

private final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList("jpg", "png", "jpeg", "pdf", "docx"));

private Path targetPath;
private String fileName;

@PostConstruct
public void init() {
try {
// 确保创建静态资源文件夹
String staticUploadDir = Paths.get(System.getProperty("user.dir"),UPLOAD_DIR).toString();
Files.createDirectories(Paths.get(staticUploadDir));
} catch (IOException e) {
throw new RuntimeException("无法创建上传目录:" + UPLOAD_DIR, e);
}
}

@PostMapping("/upload")
public R<?> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return R.error("上传的文件不能为空!", 50000);
}

String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.contains(".")) {
return R.error("无效的文件名!", 50000);
}

String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();

if (!ALLOWED_EXTENSIONS.contains(fileExtension)) {
return R.error("上传的文件类型不支持!", 50000);
}

try {
// 确保文件存储到静态目录
String staticUploadDir = Paths.get(System.getProperty("user.dir"), UPLOAD_DIR).toString();
fileName = System.currentTimeMillis() + "." + fileExtension;
targetPath = Paths.get(staticUploadDir, fileName);

file.transferTo(targetPath.toFile());

} catch (IOException e) {
return R.error("文件上传失败:" + e.getMessage(), 50000);
}

// 返回上传文件的相对 URL 路径
return R.ok("/" + UPLOAD_DIR + "/" + fileName);
}
}

这里用到了响应封装类 R,用于返回 JSON 格式的数据。

以上代码将我们上传的文件保存在项目路径下的 uploads 目录下,并返回一个包含文件相对路径的 JSON 对象,但是项目路径下的 uploads 前端是无法访问到的:

alt text

alt text

可以看到这里返回404,前端无法访问到上传的文件,因此我们还需要添加 uploads 为静态目录。

静态资源目录

在 Spring Boot 应用中,如果不进行任何自定义配置,框架默认会搜索以下类路径下的目录并将它们作为静态资源目录:

  • /META-INF/resources/
  • /resources/
  • /static/
  • /public/

将静态文件(如HTML、CSS、JS或图片)放置在这些目录下,Spring Boot 会自动提供这些文件的访问。

通过在 application.properties 文件中设置 spring.resources.static-locations 属性来指定静态资源的路径,例如:

1
2
3
spring:
resources:
static-locations: classpath:/custom-path/,classpath:/another-path/

如果在这些目录下有一个名为 image.jpg 的文件,可以通过如下 URL 访问:

1
http://yourserver:port/image.jpg

这里就需要在 resources 目录下创建 custom-path 文件夹。

配置类设置静态目录

当然我们还可以创建配置类的形式指定静态资源路径,这里我们通过配置类的形式设置静态目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.qwesec.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${upload.path}")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置 /uploads/** 映射到上传目录
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:./" + uploadPath + "/");
}
}

再次上传文件并访问:

alt text

alt text

文件下载功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.qwesec.demo.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/api/files")
public class FileDownloadController {
@Value("${upload.path}")
private String uploadDir;

@GetMapping("/download")
public ResponseEntity<?> downloadFile(@RequestParam("filename") String fileName) {
try {
// 清洁文件名避免安全风险
fileName = StringUtils.cleanPath(fileName);
// 检查文件名是否含有不安全的路径序列,防止任意文件下载
if (fileName.contains("..")) {
return null;
}
Path path = Paths.get(uploadDir, fileName);
Resource resource = new UrlResource(path.toUri());
if (resource.exists()) {
return ResponseEntity.ok().header("Content-Disposition","attachment; filename=\"" + resource.getFilename() + "\"").body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (MalformedURLException e) {
return ResponseEntity.status(500).body("文件下载失败:" + e.getMessage());
}
}
}

访问:http://localhost:8090/api/files/download?filename=1734335322733.jpg 即可完成下载。

其中filename为要下载的文件名称,只允许下载 uploads 目录下的文件,防止使用 .. 进行目录穿越,造成任意文件下载(读取)漏洞。

数据验证与异常处理

数据验证

介绍

  • Spring Boot 通过集成 Hibernate Validator 和使用 Java 的 Bean Validation API,为开发者提供了一套强大、灵活且易于使用的数据验证机制。
  • 要在Spring Boot应用程序中使用数据验证,首先需要添加相关的依赖,找pom.xml中加入以下依赖:

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

验证相关的注解

注解名称 描述
@Size(min=, max=) 确保字段的值的大小/长度在指定的范围内。
@Min(value=) 确保字段的值大于或等于给定的最小值,仅对数字类型有效。
@Max(value=) 确保字段的值小于或等于给定的最大值,仅对数字类型有效。
@NotBlank 确保某个字符串属性在验证时不为空,并且其去除首尾空白后的长度至少为1。
@Email 确保字段值是电子邮件地址。
@Pattern(regexp=) 确保字段的值与给定的正则表达式匹配。

使用案例

这里我们可以创建一个 dao 包并创建 UserDTO 的类,当然我们也可以在 entity 创建 User 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User {
@Size(min = 1, max = 12, message = "用户名最小不能小于1个字符,最大不能超过12字符!")
private String username;

@Size(min = 8, max = 16, message = "密码长度最小不能小于8个字符,最大不能超过16字符!")
private String password;

@NotBlank(message = "性别不能为空!")
private String gender;

@NotNull(message = "手机号码不能为空!")
@Pattern(regexp = "^\\d{11}$", message = "手机号码格式错误!")
private String phone;

@NotBlank( message = "邮箱不能为空!")
@Email(message = "邮箱格式有误!")
private String mail;

// 省略 getter 和 setter 以及 构造方法
}

以上验证了 用户名和密码 以及 gender 和 手机号和邮箱。

其中 usernamepassword 的注解 @Size 可以设置最小和最大长度,gender 参数使用了 @NotBlank 注解,确保其不为空,并且去除首尾空白符后长度至少为1。phone 参数使用了 @Pattern 注解,确保其符合指定的正则表达式,即满足11位手机号码的格式。最后使用自带的 @Email 注解 和 NotBlank 来验证邮箱的格式以及确保该值不为空且满足邮箱的格式。

创建控制器进行测试:

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/api/v2")
@Api(value = "用户接口", tags = {"用户接口"})
public class UserController {
@PostMapping("/user")
public R<User> addUser(@RequestBody @Valid User user) {
return R.ok(user);
}
}

这里需要使用 @Valid 注解来启用 User 类中的验证功能。

接着来访问测试,不传入邮箱参数:

alt text

可以看到返回 400 状态码,来到程序看输出信息:

alt text

由于这里抛出的是 MethodArgumentNotValidException 异常,是在进入你的业务方法之前抛出的,所以即使你在业务方法内部使用 try-catch,也无法捕获它

alt text

但是我们需要将错误信息返回给前端。于是就需要使用全局异常器来处理异常,将具体的错误信息返回给前端,前端反馈给用户。

全局异常处理

介绍

  • 全局异常处理允许在一个集中的位置处理所有的异常,确保整体的用户体验和应用的响应行为始终如一。

  • 在 Spring Boot 中,全局异常处理通常是通过使用 @RestControllerAdvice@ExceptionHandler 注解来实现的。

  • @RestControllerAdvice@ControllerAdvice 的特殊变种,它默认将结果作为JSON 返回,非常适合 RESTful 服务。

  • @RestControllerAdvice 允许为多个 @RestController 类定义全局、跨切面的行为,它们不直接处理HTTP请求。

  • 当需要定义一些对多个控制器都适用的行为时,例如,当多个控制器都需要相同的异常处理逻辑时, @RestControllerAdvice 是最合适的选择。

  • @ExceptionHandler 用于处理控制器中的特定异常。这个注解提供了一种优雅的方式来集中处理特定的异常类型,而不是在每个控制器方法中使用 try-catch 块。

使用案例

1
2
3
4
5
6
7
8
@RestControllerAdvice
public class GlobalExceptionHandler{
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getFieldError(). getDefaultMessage(); // 返回第一个错误信息
return R.error(errorMessage, 500);
}
}

使用我们使用了 @RestControllerAdvice 注解,表示这个类是一个全局异常处理器。然后我们定义了一个 @ExceptionHandler 注解,用于处理 MethodArgumentNotValidException 类型的异常。

接着获取错误的信息并调用 R.error 方法返回给前端。R 类是我们在前面自己封装的一个响应类。

最后在我们要使用全局异常处理器的 Controller 上添加 @Validated 注解,这样在参数校验时,就会使用全局异常处理器来处理异常。

1
2
3
4
5
6
7
8
9
10
@RestController
@Validated
@RequestMapping("/api/v2")
@Api(value = "用户接口", tags = {"用户接口"})
public class UserController {
@PostMapping("/user")
public R<User> addUser(@RequestBody @Valid User user) {
return R.ok(user);
}
}

测试邮箱参数为空:

alt text

测试密码参数小于8位:

alt text

可以看到将错误(验证)信息返回给前端了。最后经过前端处理响应给用户。

最后通过测试:

alt text

当然,这里不仅可以返回验证信息,全局异常处理还可以返回其他错误信息,我们之前是通过 try-catch 来捕获异常,返回给前端的,现在我们可以通过全局异常处理,统一返回给前端。

这里我们故意写一个错误:

alt text

接着在全局处理器中定义捕获该异常并将错误信息以 json 格式返回给前端:

alt text

alt text

当然也可以直接返回 被除数不能为0 字样,这样就可以很优雅的处理这些异常了哈哈。

拦截器

前言

  • 拦截器(Interceptor)是一种设计模式,用于在某个操作或请求的前后插入特定的行为或处理逻辑。

  • 在Web开发中,拦截器通常用于在处理HTTP请求的前后执行某些操作:

    • 身份验证和授权:在请求到达目标处理器之前,进行用户身份验证和权限检查,例如对接口进行校验,防止未授权漏洞的产生。
    • 日志记录:记录请求的详细信息,如来源IP、请求的URL、请求方法以及响应时间和执行时长。
    • 数据处理与监控: 预加载请求所需的数据,记录请求处理时间以进行性能监控,限制

HandlerInterceptor 接口

  • preHandle():请求处理之前调用。如果返回true,请求继续进行;返回false,请求将中断。
  • postHandle():在请求被处理之后,但在视图被渲染之前调用。
  • afterCompletion():请求处理完毕后调用,这包括视图的渲染。通常用于资源清理操作。

创建拦截器

定义一个类 CustomInterCeptor 实现 HandlerInterceptor 接口,这里为了规范,我们创建 Interceptor 包,在该包下创建相应的拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CustomInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("前置操作");
// 返回true 表示放行,false 表示拦截,不继续往下执行
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后逻辑处理");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("请求完成后的逻辑处理");
}
}

拦截器配置

接着我们在 WebConfig 类配置拦截器,指定拦截哪些路径和排除那些路径:

1
2
3
4
5
6
7
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/api/v2/user/**").excludePathPatterns("/api/v2/user/login").excludePathPatterns("/api/v2/user/register");
}
}

WebConfig 类实现了 WebMvcConfigurer 接口,并重写了 addInterceptors 方法,指定使用 CustomInterceptor 作为拦截器,要拦截的路径为 "/api/v2/user/ 路径下的所有接口,但是排除登陆和注册接口,也就是说除了登陆和注册,其他都会被拦截器所拦截,因此该功能一般用于鉴权。

测试拦截器

在拦截器配置中我们拦截了 /api/v2/user/ 路径下的所有接口,但是排除了 loginregister 接口,因此我们编写 Controller 进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping("/api/v2")
public class UserController {

@GetMapping("/user/{id}")
public R<?> getUserById(@PathVariable Integer id) {
System.out.println("getUserById方法执行了!");
return R.ok("success");
}

@GetMapping("/user/login")
public R<?> login() {
return R.ok("登陆功能");
}

@GetMapping("/user/register")
public R<?> register() {
return R.ok("注册功能");
}
}

运行项目并访问:

alt text

可以发现请求会先进入拦截器中的 preHandle 方法当中进行判断是否往下执行。如果往下执行,则会执行 getUserById 方法中的代码。

最后再执行后逻辑处理和请求完成后的逻辑处理。

我们尝试访问 loginregister 接口,可以发现拦截器不会拦截,因为这两个接口在排除列表中。

alt text

可以发现,并未被拦截器所拦截。

数据库集成和持久化

Mybatis 的基础使用

数据持久化和 ORM

在现代应用开发中,数据持久化是一个关键概念,它涉及将数据保存在持久存储如(数据库中),以确保即时在程序关闭或崩溃下,数据仍然被保留。直接与数据库及交互可能操作复杂且容易出错,尤其在面向对象编程中, ORM 显得尤为重要。

对象关系映射(Object-Relational Mapping, ORM)允许开发者以操作对象的方式与数据库交互,简化了数据库操作过程。

MyBatis 是一种半自动 ORM 框架。它允许开发者直接编写 SQL,同时提供了结果映射到对象的能力。与完全自动的 ORM 工具相比,MyBatis 提供更多的灵活性和控制力。

alt text

Mybatis 的核心特性如下:

  • 灵活的 SQL 查询,允许按照开发者的的风格编写 SQL,充分利用数据库功能。
  • 强大的映射功能:支持多种复杂映射,如一对一,一对多等。
  • 双向配置:提供直接和 XML 两种配置方式
  • 动态SQL:支持根据运行时条件改变的 SQL 查询。
  • 内置连接池和事务管理,简化连接和事务管理,也支持集成第三方工具

SprintBoot 集成 Mybatis

前言

Mybatis 官方为简化 Mybatis 在 SpringBoot 项目中的集成,提供了 mybatis-spring-boot-starter,通过这个 starter 开发者可以轻松的将 mybatis 集成到 SpringBoot 应用当中。

提供如下便捷:

  • 自动配置:SpringBoot Starter 的核心优势在于其自动配置的能力,它会自动配置 Mybatis 的关键组件,如 SqlSessionFactorySqlSessionTemplate
  • 无需 XML:尽管 Mybatis 支持 XML 配置,但通过使用 Starter 开发者可以通过纯 Java 配置和注解使用 Mybatis,从而减少对 XML 的依赖。
  • 集成 Spring 事务:Starter 与 Spring 事务无缝集成,允许使用 Spring@Transactional 注解来管理事务。
  • 灵活的属性配置:通过 SpringBoot 的配置文件(application.properties) 可以轻松的配置 Mybatis 的各种属性。

引入依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.2</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

我这里使用的 SpringBoot 版本为 2.6.13,故而这里需要选择 2.3.2,高版本的可以选择 3.0.x, 不然会因为依赖不兼容问题,导致运行出错。

配置数据源

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springbootdemo?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
username: root
password: toor
  • spring.datasource.driver-class-name 指定数据库驱动类,在 mysql8 驱动中,则需要添加 cj`。
  • spring.datasource.url 定义数据库服务器的地址和端口以及要操作的数据控,该案例中的数据库名为 springbootdemo 此处的URL包含了数据库地址(localhost:3306),数据库名称(springbootdemo)以及一些连接参数如禁用SSL,设置服务器时区为 UTC,最后设置字符集编码为 UTF-8
  • spring.datasource.username 指定连接数据库所使用的用户名
  • spring.datasource.password 指定连接数据库所使用的密码

测试所需的数据库和表创建

我这里创建的数据库名称为:springbootdemo,各位同学根据需求创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE DATABASE springbootdemo;

CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`order_date` date NOT NULL,
`amount` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;


CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8_bin NOT NULL,
`email` varchar(255) COLLATE utf8_bin NOT NULL,
`registered_date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

插入测试数据:

1
2
3
4
5
6
7
8
9
10
11
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (1, 1, '2024-12-18', 2000.00);
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (2, 1, '2024-12-18', 3000.00);
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (3, 1, '2024-12-18', 18000.00);
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (4, 2, '2024-12-17', 2300.00);
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (5, 2, '2024-12-17', 100.00);
INSERT INTO `orders` (`id`, `user_id`, `order_date`, `amount`) VALUES (6, 3, '2024-12-18', 1000.00);


INSERT INTO `users` (`id`, `username`, `email`, `registered_date`) VALUES (1, 'admin', 'admin@qwesec.com', '2024-12-18');
INSERT INTO `users` (`id`, `username`, `email`, `registered_date`) VALUES (2, 'test', 'test@qwesec.com', '2024-12-15');
INSERT INTO `users` (`id`, `username`, `email`, `registered_date`) VALUES (3, 'demo', 'demo@qwesec.com', '2024-12-18');

定义数据库表映射的实体类

定义对数据库中 users 表和 orders 表的映射对象:

1
2
3
4
5
6
7
public class User {
private Integer id;
private String username;
private String email;
private Date registeredDate;
// 省略 getter setter 以及构造方法
}
1
2
3
4
5
6
7
public class Order {
private Integer id;
private Integer userId;
private Date orderDate;
private Double amount;
// 省略 getter setter 以及构造方法
}

这里定义的实体类属性默认要与表的字段名一致,但是表中字段可能存在下划线命名法,如 registered_date 而 Java 则常用的是驼峰命名法,Mybatis 提供了自动映射这两种命名格式的功能,要启动这一特性,需要在 application.yaml 添加如下配置:

1
2
3
mybatis:
configuration:
map-underscore-to-camel-case: true

由于 users 表中包含日期类型的字段,因此在序列化和反序列化含有日期和时间的对象时,时区设置可能会导致问题出现, Spring boot 使用的 Jackson 默认采用 GMT (格林尼治标准时间),作为时区,为避免时区显示错误,建议在 application.yaml 配置文件中添加如下配置:

1
2
3
spring:
jackson:
time-zone: Asia/Shanghai # 设置为上海时区

Mybatis 映射介绍

Mybatis 映射是其核心功能之一,主要负责描述如何将 SQL 查询结果转为 Java 对象,以及如何从 Java 对象转换到 SQL 查询参数。

映射定义了数据库与 Java 对象之间的数据转换规则。

映射的主要组成部分如下

  • SQL 语句:要执行的具体 SQL 操作,如查询、插入、更新或删除。
  • 输入映射:描述如何从 Java 对象提取值,并作为 SQL 语句的参数。
  • 输出映射:描述如何从 SQL 查询结果中提取值,并填充到 Java 对象的属性中

映射可以通过以下两个方式定义:

  • XML 映射:在 XML 文件中定义 SQL 语句、输入映射和输出映射。适用于复杂的查询和映射场景。
  • 注解映射:在 Java 接口方法上使用 MyBatis 注解(如 @Select、@Insert)定义映射。适用于简单场景。

Mapper 接口是映射的 Java 表现形式,提供了类型安全的方式来使用映射。每个 Mapper 接口方法对应一个 SQL 操作。

当使用 MyBatis 时,主要分为以下三步:

  1. 定义 Mapper 接口:创建一个接口,其中包含与 SQL 操作相
  2. 对应的方法。
  3. 定义映射方法:在 Mapper 接口中的方法中,通过注解或与外部 XML 文件关联来定义 SQL 语句。
  4. 调用映射方法:在应用程序中调用 Mapper 接口的方法,MyBatis 会找到相应的映射并执行 SQL 语句,返回处理后的结果。

Mybatis 映射

Mapper 接口一般都被放在 mapper 包下,接口名称一般由 数据表名+Mapper 进行命名,这里以操作 users 表为例:

1
2
3
4
5
@Mapper
public interface UserMapper {
@Select("SELECT * FROM `users` WHERE id = #{id}")
User getUserById(Integer id);
}
  • @Mapper 注解表示接口为 Mybatis Mapper 接口,允许 Mybatis 生成代理对象来实现该接口,可以在应用程序中不编写任何具体实现的情况下,直接调用这个接口的方法来与数据库进行交互。这个注解还指示 SpringBoot 容器扫描并管理该接口作为一个 Bean,使得它可以在其他组件(如Services、Controller)中通过 @Autowired 注解成功注入。
  • @Select 注解用于查询数据,该注解的值是一个 SQL 语句,其中 ${id} 是一个参数占位符,用于将方法参数的值替换进去。

如果每个 Mapper 都在 mapper 包下,那么我们就可以在 SpringBoot 的启动器上添加 @MapperScan 如下代码所示:

1
2
3
4
5
6
7
8
@MapperScan("com.qwesec.demo.mapper")
@SpringBootApplication
public class DemoApplication {

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

通过这种方式 SpringBoot 会自动扫描 com.qwesec.demo.mapper 包下的所有接口,都会被 Mybatis 自动识别并注册为 Mapper 接口,这样就不用单独为一个 Mapper 添加 @Mapper 注解了。

一个简单的查询示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@Validated
@RequestMapping("/api/v2")
@Api(value = "用户接口", tags = {"用户接口"})
public class UserController {

@Autowired
private UserMapper userMapper;

@GetMapping("/user/{id}")
public User getUserById(@PathVariable Integer id) {
User user = userMapper.getUserById(id);
return user;
}
}

测试:

alt text

alt text

注解方式操作数据库

  • 在 MyBatis 中,注解方式提供了一种更加简洁的方法来执行数据库的 CRUD(创建、读取、更新、删除)操作:
  • @Select: 执行SELECT查询。
  • @Insert: 执行INSERT操作。
  • @Update: 执行UPDATE操作。
  • @Delete: 执行DELETE操作。

编写 Mapper 文件,添加增删改查的注解和方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Mapper
public interface UserMapper {
@Select("SELECT * FROM `users` WHERE id = #{id}")
User getUserById(long id);

@Select("SELECT * FROM `users`")
List<User> getAllUser();

@Insert("INSERT INTO `users` (username, email, registered_date) VALUES (#{username}, #{email}, #{registeredDate})")
int insertUser(User user);

@Update("UPDATE `users` SET username = #{username}, email = #{email} WHERE id = #{id} ")
int updateUserById(User user);

@Delete("DELETE FROM `users` WHERE id = #{id}")
int deleteUserById(long id);
}

接着来到控制器中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@RestController
@RequestMapping("/api/v2")
@Api(value = "用户接口", tags = {"用户接口"})
public class UserController {

@Autowired
private UserMapper userMapper;

@GetMapping("/user/{id}")
public R<User> getUserById(@PathVariable long id) {
User user = userMapper.getUserById(id);
return R.ok(user);
}

@GetMapping("/user/all")
public R<List<User>> getAllUser() {
return R.ok(userMapper.getAllUser());
}

@PostMapping("/user")
public R<?> addUser(@RequestBody User user) {
userMapper.insertUser(user);
return R.ok("操作成功");
}

@Delete("/user/{id}")
public R<?> deleteUser(@PathVariable long id) {
userMapper.deleteUserById(id);
return R.ok("删除成功");
}

@PutMapping("/user/")
public R<?> updateUser(@RequestBody User user) {
userMapper.updateUserById(user);
return R.ok("操作成功");
}
}

根据ID查询用户测试:

alt text

查询所有用户测试:

alt text

添加用户测试:

alt text

alt text

更新用户测试:

alt text

alt text

删除用户测试:

alt text

alt text

持续更新…

参考文献