SpringBoot+Spring常用注解总结.md 34.7 KB
Newer Older
S
shuang.kou 已提交
1

S
shuang.kou 已提交
2
### 文章目录
S
shuang.kou 已提交
3 4
<!-- TOC -->

S
shuang.kou 已提交
5
- [文章目录](#%e6%96%87%e7%ab%a0%e7%9b%ae%e5%bd%95)
S
shuang.kou 已提交
6 7 8 9
- [0.前言](#0%e5%89%8d%e8%a8%80)
- [1. `@SpringBootApplication`](#1-springbootapplication)
- [2. Spring Bean 相关](#2-spring-bean-%e7%9b%b8%e5%85%b3)
  - [2.1. `@Autowired`](#21-autowired)
ipofss's avatar
ipofss 已提交
10
  - [2.2. `@Component`,`@Repository`,`@Service`, `@Controller`](#22-componentrepositoryservice-controller)
S
shuang.kou 已提交
11 12
  - [2.3. `@RestController`](#23-restcontroller)
  - [2.4. `@Scope`](#24-scope)
ipofss's avatar
ipofss 已提交
13
  - [2.5. `@Configuration`](#25-configuration)
S
shuang.kou 已提交
14 15 16 17 18 19
- [3. 处理常见的 HTTP 请求类型](#3-%e5%a4%84%e7%90%86%e5%b8%b8%e8%a7%81%e7%9a%84-http-%e8%af%b7%e6%b1%82%e7%b1%bb%e5%9e%8b)
  - [3.1. GET 请求](#31-get-%e8%af%b7%e6%b1%82)
  - [3.2. POST 请求](#32-post-%e8%af%b7%e6%b1%82)
  - [3.3. PUT 请求](#33-put-%e8%af%b7%e6%b1%82)
  - [3.4. **DELETE 请求**](#34-delete-%e8%af%b7%e6%b1%82)
  - [3.5. **PATCH 请求**](#35-patch-%e8%af%b7%e6%b1%82)
S
shuang.kou 已提交
20 21 22 23 24 25 26 27 28 29 30
- [4. 前后端传值](#4-%e5%89%8d%e5%90%8e%e7%ab%af%e4%bc%a0%e5%80%bc)
  - [4.1. `@PathVariable` 和 `@RequestParam`](#41-pathvariable-%e5%92%8c-requestparam)
  - [4.2. `@RequestBody`](#42-requestbody)
- [5. 读取配置信息](#5-%e8%af%bb%e5%8f%96%e9%85%8d%e7%bd%ae%e4%bf%a1%e6%81%af)
  - [5.1. `@value`(常用)](#51-value%e5%b8%b8%e7%94%a8)
  - [5.2. `@ConfigurationProperties`(常用)](#52-configurationproperties%e5%b8%b8%e7%94%a8)
  - [5.3. `PropertySource`(不常用)](#53-propertysource%e4%b8%8d%e5%b8%b8%e7%94%a8)
- [6. 参数校验](#6-%e5%8f%82%e6%95%b0%e6%a0%a1%e9%aa%8c)
  - [6.1. 一些常用的字段验证的注解](#61-%e4%b8%80%e4%ba%9b%e5%b8%b8%e7%94%a8%e7%9a%84%e5%ad%97%e6%ae%b5%e9%aa%8c%e8%af%81%e7%9a%84%e6%b3%a8%e8%a7%a3)
  - [6.2. 验证请求体(RequestBody)](#62-%e9%aa%8c%e8%af%81%e8%af%b7%e6%b1%82%e4%bd%93requestbody)
  - [6.3. 验证请求参数(Path Variables 和 Request Parameters)](#63-%e9%aa%8c%e8%af%81%e8%af%b7%e6%b1%82%e5%8f%82%e6%95%b0path-variables-%e5%92%8c-request-parameters)
S
shuang.kou 已提交
31 32
- [7. 全局处理 Controller 层异常](#7-%e5%85%a8%e5%b1%80%e5%a4%84%e7%90%86-controller-%e5%b1%82%e5%bc%82%e5%b8%b8)
- [8. JPA 相关](#8-jpa-%e7%9b%b8%e5%85%b3)
S
shuang.kou 已提交
33 34 35 36 37 38 39 40 41 42
  - [8.1. 创建表](#81-%e5%88%9b%e5%bb%ba%e8%a1%a8)
  - [8.2. 创建主键](#82-%e5%88%9b%e5%bb%ba%e4%b8%bb%e9%94%ae)
  - [8.3. 设置字段类型](#83-%e8%ae%be%e7%bd%ae%e5%ad%97%e6%ae%b5%e7%b1%bb%e5%9e%8b)
  - [8.4. 指定不持久化特定字段](#84-%e6%8c%87%e5%ae%9a%e4%b8%8d%e6%8c%81%e4%b9%85%e5%8c%96%e7%89%b9%e5%ae%9a%e5%ad%97%e6%ae%b5)
  - [8.5. 声明大字段](#85-%e5%a3%b0%e6%98%8e%e5%a4%a7%e5%ad%97%e6%ae%b5)
  - [8.6. 创建枚举类型的字段](#86-%e5%88%9b%e5%bb%ba%e6%9e%9a%e4%b8%be%e7%b1%bb%e5%9e%8b%e7%9a%84%e5%ad%97%e6%ae%b5)
  - [8.7. 增加审计功能](#87-%e5%a2%9e%e5%8a%a0%e5%ae%a1%e8%ae%a1%e5%8a%9f%e8%83%bd)
  - [8.8. 删除/修改数据](#88-%e5%88%a0%e9%99%a4%e4%bf%ae%e6%94%b9%e6%95%b0%e6%8d%ae)
  - [8.9. 关联关系](#89-%e5%85%b3%e8%81%94%e5%85%b3%e7%b3%bb)
- [9. 事务 `@Transactional`](#9-%e4%ba%8b%e5%8a%a1-transactional)
S
shuang.kou 已提交
43 44 45
- [10. json 数据处理](#10-json-%e6%95%b0%e6%8d%ae%e5%a4%84%e7%90%86)
  - [10.1. 过滤 json 数据](#101-%e8%bf%87%e6%bb%a4-json-%e6%95%b0%e6%8d%ae)
  - [10.2. 格式化 json 数据](#102-%e6%a0%bc%e5%bc%8f%e5%8c%96-json-%e6%95%b0%e6%8d%ae)
S
shuang.kou 已提交
46 47 48 49 50
  - [10.3. 扁平化对象](#103-%e6%89%81%e5%b9%b3%e5%8c%96%e5%af%b9%e8%b1%a1)
- [11. 测试相关](#11-%e6%b5%8b%e8%af%95%e7%9b%b8%e5%85%b3)

<!-- /TOC -->
### 0.前言
S
shuang.kou 已提交
51

S
shuang.kou 已提交
52
_大家好,我是 Guide 哥!这是我的 221 篇优质原创文章。如需转载,请在文首注明地址,蟹蟹!_
S
shuang.kou 已提交
53

S
shuang.kou 已提交
54
本文已经收录进我的 75K Star 的 Java 开源项目 JavaGuide:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
S
shuang.kou 已提交
55

S
shuang.kou 已提交
56
可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法,掌握搞懂,使用 SpringBoot 来开发项目基本没啥大问题了!
S
shuang.kou 已提交
57 58 59

**为什么要写这篇文章?**

S
shuang.kou 已提交
60
最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多,我看了文章内容之后属实觉得质量有点低,并且有点会误导没有太多实际使用经验的人(这些人又占据了大多数)。所以,自己索性花了大概 两天时间简单总结一下了。
S
shuang.kou 已提交
61

S
shuang.kou 已提交
62
**因为我个人的能力和精力有限,如果有任何不对或者需要完善的地方,请帮忙指出!Guide 哥感激不尽!**
S
shuang.kou 已提交
63 64 65 66 67

### 1. `@SpringBootApplication`

这里先单独拎出`@SpringBootApplication` 注解说一下,虽然我们一般不会主动去使用它。

S
shuang.kou 已提交
68
_Guide 哥:这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。_
S
shuang.kou 已提交
69 70 71 72 73 74 75 76 77 78

```java
@SpringBootApplication
public class SpringSecurityJwtGuideApplication {
      public static void main(java.lang.String[] args) {
        SpringApplication.run(SpringSecurityJwtGuideApplication.class, args);
    }
}
```

S
shuang.kou 已提交
79
我们可以把 `@SpringBootApplication`看作是 `@Configuration``@EnableAutoConfiguration``@ComponentScan` 注解的集合。
S
shuang.kou 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

```java
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ......
}

package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}
```

S
shuang.kou 已提交
106
根据 SpringBoot 官网,这三个注解的作用分别是:
S
shuang.kou 已提交
107 108

- `@EnableAutoConfiguration`:启用 SpringBoot 的自动配置机制
S
shuang.kou 已提交
109 110
- `@ComponentScan`: 扫描被`@Component` (`@Service`,`@Controller`)注解的 bean,注解默认会扫描该类所在的包下所有的类。
- `@Configuration`:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
S
shuang.kou 已提交
111 112 113 114 115

### 2. Spring Bean 相关

#### 2.1. `@Autowired`

S
shuang.kou 已提交
116
自动导入对象到类中,被注入进的类同样要被 Spring 容器管理比如:Service 类注入到 Controller 类中。
S
shuang.kou 已提交
117 118 119 120 121 122 123 124 125 126 127 128

```java
@Service
public class UserService {
  ......
}

@RestController
@RequestMapping("/users")
public class UserController {
   @Autowired
   private UserService userService;
S
shuang.kou 已提交
129
   ......
S
shuang.kou 已提交
130 131 132
}
```

ipofss's avatar
ipofss 已提交
133
#### 2.2. `@Component`,`@Repository`,`@Service`, `@Controller`
S
shuang.kou 已提交
134

S
shuang.kou 已提交
135
我们一般使用 `@Autowired` 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 `@Autowired` 注解自动装配的 bean 的类,可以采用以下注解实现:
S
shuang.kou 已提交
136

S
shuang.kou 已提交
137
- `@Component` :通用的注解,可标注任意类为 `Spring` 组件。如果一个 Bean 不知道属于哪个层,可以使用`@Component` 注解标注。
S
shuang.kou 已提交
138
- `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。
S
shuang.kou 已提交
139
- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
ipofss's avatar
ipofss 已提交
140
- `@Controller` : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
S
shuang.kou 已提交
141

S
shuang.kou 已提交
142
#### 2.3. `@RestController`
S
shuang.kou 已提交
143

S
shuang.kou 已提交
144
`@RestController`注解是`@Controller和`@`ResponseBody`的合集,表示这是个控制器 bean,并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器。
S
shuang.kou 已提交
145

S
shuang.kou 已提交
146
_Guide 哥:现在都是前后端分离,说实话我已经很久没有用过`@Controller`。如果你的项目太老了的话,就当我没说。_
S
shuang.kou 已提交
147

S
shuang.kou 已提交
148
单独使用 `@Controller` 不加 `@ResponseBody`的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。`@Controller` +`@ResponseBody` 返回 JSON 或 XML 形式数据
S
shuang.kou 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

关于`@RestController``@Controller`的对比,请看这篇文章:[@RestController vs @Controller](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485544&idx=1&sn=3cc95b88979e28fe3bfe539eb421c6d8&chksm=cea247a3f9d5ceb5e324ff4b8697adc3e828ecf71a3468445e70221cce768d1e722085359907&token=1725092312&lang=zh_CN#rd)

#### 2.4. `@Scope`

声明 Spring Bean 的作用域,使用方法:

```java
@Bean
@Scope("singleton")
public Person personSingleton() {
    return new Person();
}
```

S
shuang.kou 已提交
164
**四种常见的 Spring Bean 的作用域:**
S
shuang.kou 已提交
165 166 167

- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype : 每次请求都会创建一个新的 bean 实例。
S
shuang.kou 已提交
168 169
- request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
- session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
S
shuang.kou 已提交
170

ipofss's avatar
ipofss 已提交
171
#### 2.5. `@Configuration`
S
shuang.kou 已提交
172

ipofss's avatar
ipofss 已提交
173
一般用来声明配置类,可以使用 `@Component`注解替代,不过使用`@Configuration`注解声明配置类更加语义化。
S
shuang.kou 已提交
174 175 176 177 178 179 180 181 182 183 184 185

```java
@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}
```

S
shuang.kou 已提交
186
### 3. 处理常见的 HTTP 请求类型
S
shuang.kou 已提交
187

S
shuang.kou 已提交
188
**5 种常见的请求类型:**
S
shuang.kou 已提交
189 190 191 192 193 194 195

- **GET** :请求从服务器获取特定资源。举个例子:`GET /users`(获取所有学生)
- **POST** :在服务器上创建一个新的资源。举个例子:`POST /users`(创建学生)
- **PUT** :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:`PUT /users/12`(更新编号为 12 的学生)
- **DELETE** :从服务器删除特定的资源。举个例子:`DELETE /users/12`(删除编号为 12 的学生)
- **PATCH** :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。

S
shuang.kou 已提交
196
#### 3.1. GET 请求
S
shuang.kou 已提交
197 198 199 200 201 202 203 204 205 206

`@GetMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.GET)`

```java
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
 return userRepository.findAll();
}
```

S
shuang.kou 已提交
207
#### 3.2. POST 请求
S
shuang.kou 已提交
208

S
shuang.kou 已提交
209
`@PostMapping("users")` 等价于`@RequestMapping(value="/users",method=RequestMethod.POST)`
S
shuang.kou 已提交
210 211 212 213 214 215 216 217 218 219

关于`@RequestBody`注解的使用,在下面的“前后端传值”这块会讲到。

```java
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {
 return userRespository.save(user);
}
```

S
shuang.kou 已提交
220
#### 3.3. PUT 请求
S
shuang.kou 已提交
221 222 223 224 225 226 227 228 229 230 231

`@PutMapping("/users/{userId}")` 等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)`

```java
@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId,
  @Valid @RequestBody UserUpdateRequest userUpdateRequest) {
  ......
}
```

S
shuang.kou 已提交
232
#### 3.4. **DELETE 请求**
S
shuang.kou 已提交
233 234 235 236 237 238 239 240 241 242

`@DeleteMapping("/users/{userId}")`等价于`@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)`

```java
@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){
  ......
}
```

S
shuang.kou 已提交
243
#### 3.5. **PATCH 请求**
S
shuang.kou 已提交
244

S
shuang.kou 已提交
245
一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。
S
shuang.kou 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260

```java
  @PatchMapping("/profile")
  public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) {
        studentRepository.updateDetail(studentUpdateRequest);
        return ResponseEntity.ok().build();
    }
```

### 4. 前后端传值

**掌握前后端传值的正确姿势,是你开始 CRUD 的第一步!**

#### 4.1. `@PathVariable` 和 `@RequestParam`

S
shuang.kou 已提交
261
`@PathVariable`用于获取路径参数,`@RequestParam`用于获取查询参数。
S
shuang.kou 已提交
262 263 264 265 266

举个简单的例子:

```java
@GetMapping("/klasses/{klassId}/teachers")
S
shuang.kou 已提交
267
public List<Teacher> getKlassRelatedTeachers(
S
shuang.kou 已提交
268 269 270 271 272 273
         @PathVariable("klassId") Long klassId,
         @RequestParam(value = "type", required = false) String type ) {
...
}
```

S
shuang.kou 已提交
274
如果我们请求的 url 是:`/klasses/{123456}/teachers?type=web`
S
shuang.kou 已提交
275 276 277 278 279

那么我们服务获取到的数据就是:`klassId=123456,type=web`

#### 4.2. `@RequestBody`

S
shuang.kou 已提交
280
用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且**Content-Type 为 application/json** 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用`HttpMessageConverter`或者自定义的`HttpMessageConverter`将请求的 body 中的 json 字符串转换为 java 对象。
S
shuang.kou 已提交
281 282 283

我用一个简单的例子来给演示一下基本使用!

S
shuang.kou 已提交
284
我们有一个注册的接口:
S
shuang.kou 已提交
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

```java
@PostMapping("/sign-up")
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {
  userService.save(userRegisterRequest);
  return ResponseEntity.ok().build();
}
```

`UserRegisterRequest`对象:

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterRequest {
    @NotBlank
    private String userName;
    @NotBlank
    private String password;
    @NotBlank
    private String fullName;
}
```

我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:

```json
{"userName":"coder","fullName":"shuangkou","password":"123456"}
```

这样我们的后端就可以直接把 json 格式的数据映射到我们的 `UserRegisterRequest` 类上。

G
guide 已提交
318
![](images/spring-annotations/@RequestBody.png)
S
shuang.kou 已提交
319

S
shuang.kou 已提交
320
👉 需要注意的是:**一个请求方法只可以有一个`@RequestBody`,但是可以有多个`@RequestParam`和`@PathVariable`**。 如果你的方法必须要用两个 `@RequestBody`来接受数据的话,大概率是你的数据库设计或者系统设计出问题了!
S
shuang.kou 已提交
321 322 323

### 5. 读取配置信息

S
shuang.kou 已提交
324
**很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。**
S
shuang.kou 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384

**下面我们来看一下 Spring 为我们提供了哪些方式帮助我们从配置文件中读取这些配置信息。**

我们的数据源`application.yml`内容如下::

```yaml
wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油!

my-profile:
  name: Guide哥
  email: koushuangbwcx@163.com

library:
  location: 湖北武汉加油中国加油
  books:
    - name: 天才基本法
      description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。
    - name: 时间的秩序
      description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。
    - name: 了不起的我
      description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?
```

#### 5.1. `@value`(常用)

使用 `@Value("${property}")` 读取比较简单的配置信息:

```java
@Value("${wuhan2020}")
String wuhan2020;
```

#### 5.2. `@ConfigurationProperties`(常用)

通过`@ConfigurationProperties`读取配置信息并与 bean 绑定。

```java
@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties {
    @NotEmpty
    private String location;
    private List<Book> books;

    @Setter
    @Getter
    @ToString
    static class Book {
        String name;
        String description;
    }
  省略getter/setter
  ......
}
```

你可以像使用普通的 Spring bean 一样,将其注入到类中使用。

#### 5.3. `PropertySource`(不常用)

S
shuang.kou 已提交
385
`@PropertySource`读取指定 properties 文件
S
shuang.kou 已提交
386 387 388 389 390 391 392 393

```java
@Component
@PropertySource("classpath:website.properties")

class WebSite {
    @Value("${url}")
    private String url;
S
shuang.kou 已提交
394

S
shuang.kou 已提交
395 396 397 398 399
  省略getter/setter
  ......
}
```

S
shuang.kou 已提交
400
更多内容请查看我的这篇文章:《[10 分钟搞定 SpringBoot 如何优雅读取配置文件?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486181&idx=2&sn=10db0ae64ef501f96a5b0dbc4bd78786&chksm=cea2452ef9d5cc384678e456427328600971180a77e40c13936b19369672ca3e342c26e92b50&token=816772476&lang=zh_CN#rd)》 。
S
shuang.kou 已提交
401 402 403 404 405

### 6. 参数校验

**数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。**

S
shuang.kou 已提交
406
**JSR(Java Specification Requests)** 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
S
shuang.kou 已提交
407

S
shuang.kou 已提交
408
校验的时候我们实际用的是 **Hibernate Validator** 框架。Hibernate Validator 是 Hibernate 团队最初的数据校验框架,Hibernate Validator 4.x 是 Bean Validation 1.0(JSR 303)的参考实现,Hibernate Validator 5.x 是 Bean Validation 1.1(JSR 349)的参考实现,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现。
S
shuang.kou 已提交
409

S
shuang.kou 已提交
410
SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。如下图所示(通过 idea 插件—Maven Helper 生成):
S
shuang.kou 已提交
411

G
guide 已提交
412
![](https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images/2020-08/c7bacd12-1c1a-4e41-aaaf-4cad840fc073.png)
S
shuang.kou 已提交
413

S
shuang.kou 已提交
414
非 SpringBoot 项目需要自行引入相关依赖包,这里不多做讲解,具体可以查看我的这篇文章:《[如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485783&idx=1&sn=a407f3b75efa17c643407daa7fb2acd6&chksm=cea2469cf9d5cf8afbcd0a8a1c9cc4294d6805b8e01bee6f76bb2884c5bc15478e91459def49&token=292197051&lang=zh_CN#rd)》。
S
shuang.kou 已提交
415

S
shuang.kou 已提交
416
👉 需要注意的是: **所有的注解,推荐使用 JSR 注解,即`javax.validation.constraints`,而不是`org.hibernate.validator.constraints`**
S
shuang.kou 已提交
417 418 419

#### 6.1. 一些常用的字段验证的注解

S
shuang.kou 已提交
420
- `@NotEmpty` 被注释的字符串的不能为 null 也不能为空
S
shuang.kou 已提交
421 422 423 424 425
- `@NotBlank` 被注释的字符串非 null,并且必须包含一个非空白字符
- `@Null` 被注释的元素必须为 null
- `@NotNull` 被注释的元素必须不为 null
- `@AssertTrue` 被注释的元素必须为 true
- `@AssertFalse` 被注释的元素必须为 false
S
shuang.kou 已提交
426 427 428 429 430
- `@Pattern(regex=,flag=)`被注释的元素必须符合指定的正则表达式
- `@Email` 被注释的元素必须是 Email 格式。
- `@Min(value)`被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- `@Max(value)`被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- `@DecimalMin(value)`被注释的元素必须是一个数字,其值必须大于等于指定的最小值
S
shuang.kou 已提交
431
- `@DecimalMax(value)` 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
S
shuang.kou 已提交
432 433 434
- `@Size(max=, min=)`被注释的元素的大小必须在指定的范围内
- `@Digits (integer, fraction)`被注释的元素必须是一个数字,其值必须在可接受的范围内
- `@Past`被注释的元素必须是一个过去的日期
S
shuang.kou 已提交
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
- `@Future` 被注释的元素必须是一个将来的日期
- ......

#### 6.2. 验证请求体(RequestBody)

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

    @NotNull(message = "classId 不能为空")
    private String classId;

    @Size(max = 33)
    @NotNull(message = "name 不能为空")
    private String name;

    @Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
    @NotNull(message = "sex 不能为空")
    private String sex;

    @Email(message = "email 格式不正确")
    @NotNull(message = "email 不能为空")
    private String email;

}
```

我们在需要验证的参数上加上了`@Valid`注解,如果验证失败,它将抛出`MethodArgumentNotValidException`

```java
@RestController
@RequestMapping("/api")
public class PersonController {

    @PostMapping("/person")
    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
        return ResponseEntity.ok().body(person);
    }
}
```

#### 6.3. 验证请求参数(Path Variables 和 Request Parameters)

**一定一定不要忘记在类上加上 `Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。**

```java
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {

    @GetMapping("/person/{id}")
    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
        return ResponseEntity.ok().body(id);
    }
}
```

S
shuang.kou 已提交
495
更多关于如何在 Spring 项目中进行参数校验的内容,请看《[如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485783&idx=1&sn=a407f3b75efa17c643407daa7fb2acd6&chksm=cea2469cf9d5cf8afbcd0a8a1c9cc4294d6805b8e01bee6f76bb2884c5bc15478e91459def49&token=292197051&lang=zh_CN#rd)》这篇文章。
S
shuang.kou 已提交
496

S
shuang.kou 已提交
497
### 7. 全局处理 Controller 层异常
S
shuang.kou 已提交
498

S
shuang.kou 已提交
499
介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。
S
shuang.kou 已提交
500 501 502 503 504 505

**相关注解:**

1. `@ControllerAdvice` :注解定义全局异常处理类
2. `@ExceptionHandler` :注解声明异常处理方法

S
shuang.kou 已提交
506
如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出`MethodArgumentNotValidException`,我们来处理这个异常。
S
shuang.kou 已提交
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521

```java
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    /**
     * 请求参数异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
       ......
    }
}
```
S
shuang.kou 已提交
522

S
shuang.kou 已提交
523 524 525 526 527
更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章:

1. [SpringBoot 处理异常的几种常见姿势](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485568&idx=2&sn=c5ba880fd0c5d82e39531fa42cb036ac&chksm=cea2474bf9d5ce5dcbc6a5f6580198fdce4bc92ef577579183a729cb5d1430e4994720d59b34&token=2133161636&lang=zh_CN#rd)
2. [使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486379&idx=2&sn=48c29ae65b3ed874749f0803f0e4d90e&chksm=cea24460f9d5cd769ed53ad7e17c97a7963a89f5350e370be633db0ae8d783c3a3dbd58c70f8&token=1054498516&lang=zh_CN#rd)

S
shuang.kou 已提交
528
### 8. JPA 相关
S
shuang.kou 已提交
529 530 531 532 533

#### 8.1. 创建表

`@Entity`声明一个类对应一个数据库实体。

A
AethLi 已提交
534
`@Table` 设置表名
S
shuang.kou 已提交
535 536 537 538 539 540 541

```java
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
S
shuang.kou 已提交
542
    private Long id;
S
shuang.kou 已提交
543 544 545 546 547 548 549 550 551 552 553 554
    private String name;
    private String description;
    省略getter/setter......
}
```

#### 8.2. 创建主键

`@Id` :声明一个字段为主键。

使用`@Id`声明之后,我们还需要定义主键的生成策略。我们可以使用 `@GeneratedValue` 指定主键生成策略。

S
shuang.kou 已提交
555
**1.通过 `@GeneratedValue`直接使用 JPA 内置提供的四种主键生成策略来指定主键生成策略。**
S
shuang.kou 已提交
556 557 558 559 560 561 562 563 564

```java
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
```

JPA 使用枚举定义了 4 中常见的主键生成策略,如下:

S
shuang.kou 已提交
565
_Guide 哥:枚举替代常量的一种用法_
S
shuang.kou 已提交
566 567

```java
S
shuang.kou 已提交
568
public enum GenerationType {
S
shuang.kou 已提交
569 570 571 572 573

    /**
     * 使用一个特定的数据库表格来保存主键
     * 持久化引擎通过关系数据库的一张特定的表格来生成主键,
     */
S
shuang.kou 已提交
574
    TABLE,
S
shuang.kou 已提交
575 576 577 578

    /**
     *在某些数据库中,不支持主键自增长,比如Oracle、PostgreSQL其提供了一种叫做"序列(sequence)"的机制生成主键
     */
S
shuang.kou 已提交
579
    SEQUENCE,
S
shuang.kou 已提交
580 581 582 583

    /**
     * 主键自增长
     */
S
shuang.kou 已提交
584
    IDENTITY,
S
shuang.kou 已提交
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

    /**
     *把主键生成策略交给持久化引擎(persistence engine),
     *持久化引擎会根据数据库在以上三种主键生成 策略中选择其中一种
     */
    AUTO
}

```

`@GeneratedValue`注解默认使用的策略是`GenerationType.AUTO`

```java
public @interface GeneratedValue {

    GenerationType strategy() default AUTO;
    String generator() default "";
}
```

S
shuang.kou 已提交
605
一般使用 MySQL 数据库的话,使用`GenerationType.IDENTITY`策略比较普遍一点(分布式系统的话需要另外考虑使用分布式 ID)。
S
shuang.kou 已提交
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664

**2.通过 `@GenericGenerator`声明一个主键策略,然后 `@GeneratedValue`使用这个策略**

```java
@Id
@GeneratedValue(generator = "IdentityIdGenerator")
@GenericGenerator(name = "IdentityIdGenerator", strategy = "identity")
private Long id;
```

等价于:

```java
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
```

jpa 提供的主键生成策略有如下几种:

```java
public class DefaultIdentifierGeneratorFactory
		implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService {

	@SuppressWarnings("deprecation")
	public DefaultIdentifierGeneratorFactory() {
		register( "uuid2", UUIDGenerator.class );
		register( "guid", GUIDGenerator.class );			// can be done with UUIDGenerator + strategy
		register( "uuid", UUIDHexGenerator.class );			// "deprecated" for new use
		register( "uuid.hex", UUIDHexGenerator.class ); 	// uuid.hex is deprecated
		register( "assigned", Assigned.class );
		register( "identity", IdentityGenerator.class );
		register( "select", SelectGenerator.class );
		register( "sequence", SequenceStyleGenerator.class );
		register( "seqhilo", SequenceHiLoGenerator.class );
		register( "increment", IncrementGenerator.class );
		register( "foreign", ForeignGenerator.class );
		register( "sequence-identity", SequenceIdentityGenerator.class );
		register( "enhanced-sequence", SequenceStyleGenerator.class );
		register( "enhanced-table", TableGenerator.class );
	}

	public void register(String strategy, Class generatorClass) {
		LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() );
		final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass );
		if ( previous != null ) {
			LOG.debugf( "    - overriding [%s]", previous.getName() );
		}
	}

}
```

#### 8.3. 设置字段类型

`@Column` 声明字段。

**示例:**

S
shuang.kou 已提交
665
设置属性 userName 对应的数据库字段名为 user_name,长度为 32,非空
S
shuang.kou 已提交
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682

```java
@Column(name = "user_name", nullable = false, length=32)
private String userName;
```

设置字段类型并且加默认值,这个还是挺常用的。

```java
Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;
```

#### 8.4. 指定不持久化特定字段

`@Transient` :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。

S
shuang.kou 已提交
683
如果我们想让`secrect` 这个字段不被持久化,可以使用 `@Transient`关键字声明。
S
shuang.kou 已提交
684 685 686 687

```java
Entity(name="USER")
public class User {
S
shuang.kou 已提交
688

S
shuang.kou 已提交
689 690 691
    ......
    @Transient
    private String secrect; // not persistent because of @Transient
S
shuang.kou 已提交
692

S
shuang.kou 已提交
693 694 695
}
```

S
shuang.kou 已提交
696
除了 `@Transient`关键字声明, 还可以采用下面几种方法:
S
shuang.kou 已提交
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717

```java
static String secrect; // not persistent because of static
final String secrect = Satish; // not persistent because of final
transient String secrect; // not persistent because of transient
```

一般使用注解的方式比较多。

#### 8.5. 声明大字段

`@Lob`:声明某个字段为大字段。

```java
@Lob
private String content;
```

更详细的声明:

```java
S
shuang.kou 已提交
718
@Lob
S
shuang.kou 已提交
719
//指定 Lob 类型数据的获取策略, FetchType.EAGER 表示非延迟 加载,而 FetchType. LAZY 表示延迟加载 ;
S
shuang.kou 已提交
720
@Basic(fetch = FetchType.EAGER)
S
shuang.kou 已提交
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
//columnDefinition 属性指定数据表对应的 Lob 字段类型
@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL")
private String content;
```

#### 8.6. 创建枚举类型的字段

可以使用枚举类型的字段,不过枚举字段要用`@Enumerated`注解修饰。

```java
public enum Gender {
    MALE("男性"),
    FEMALE("女性");

    private String value;
    Gender(String str){
        value=str;
    }
}
```

```java
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
S
shuang.kou 已提交
748
    private Long id;
S
shuang.kou 已提交
749 750 751 752 753 754 755 756
    private String name;
    private String description;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    省略getter/setter......
}
```

S
shuang.kou 已提交
757
数据库里面对应存储的是 MAIL/FEMAIL。
S
shuang.kou 已提交
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791

#### 8.7. 增加审计功能

只要继承了 `AbstractAuditBase`的类都会默认加上下面四个字段。

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public abstract class AbstractAuditBase {

    @CreatedDate
    @Column(updatable = false)
    @JsonIgnore
    private Instant createdAt;

    @LastModifiedDate
    @JsonIgnore
    private Instant updatedAt;

    @CreatedBy
    @Column(updatable = false)
    @JsonIgnore
    private String createdBy;

    @LastModifiedBy
    @JsonIgnore
    private String updatedBy;
}

```

S
shuang.kou 已提交
792
我们对应的审计功能对应地配置类可能是下面这样的(Spring Security 项目):
S
shuang.kou 已提交
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810

```java

@Configuration
@EnableJpaAuditing
public class AuditSecurityConfiguration {
    @Bean
    AuditorAware<String> auditorAware() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext())
                .map(SecurityContext::getAuthentication)
                .filter(Authentication::isAuthenticated)
                .map(Authentication::getName);
    }
}
```

简单介绍一下上面设计到的一些注解:

S
shuang.kou 已提交
811 812
1. `@CreatedDate`: 表示该字段为创建时间时间字段,在这个实体被 insert 的时候,会设置值
2. `@CreatedBy` :表示该字段为创建人,在这个实体被 insert 的时候,会设置值
S
shuang.kou 已提交
813 814 815

   `@LastModifiedDate``@LastModifiedBy`同理。

S
shuang.kou 已提交
816
`@EnableJpaAuditing`:开启 JPA 审计功能。
S
shuang.kou 已提交
817 818 819

#### 8.8. 删除/修改数据

S
shuang.kou 已提交
820
`@Modifying` 注解提示 JPA 该操作是修改操作,注意还要配合`@Transactional`注解使用。
S
shuang.kou 已提交
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838

```java
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

    @Modifying
    @Transactional(rollbackFor = Exception.class)
    void deleteByUserName(String userName);
}
```

#### 8.9. 关联关系

- `@OneToOne` 声明一对一关系
- `@OneToMany` 声明一对多关系
- `@ManyToOne`声明多对一关系
- `MangToMang`声明多对多关系

S
shuang.kou 已提交
839
更多关于 Spring Boot JPA 的文章请看我的这篇文章:[一文搞懂如何在 Spring Boot 正确中使用 JPA](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485689&idx=1&sn=061b32c2222869932be5631fb0bb5260&chksm=cea24732f9d5ce24a356fb3675170e7843addbfcc79ee267cfdb45c83fc7e90babf0f20d22e1&token=292197051&lang=zh_CN#rd)
S
shuang.kou 已提交
840 841 842 843 844 845 846 847 848 849 850 851 852

### 9. 事务 `@Transactional`

在要开启事务的方法上使用`@Transactional`注解即可!

```java
@Transactional(rollbackFor = Exception.class)
public void save() {
  ......
}

```

S
shuang.kou 已提交
853
我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事物只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事物在遇到非运行时异常时也回滚。
S
shuang.kou 已提交
854 855 856

`@Transactional` 注解一般用在可以作用在`类`或者`方法`上。

S
shuang.kou 已提交
857
- **作用于类**:当把`@Transactional 注解放在类上时,表示所有该类的`public 方法都配置相同的事务属性信息。
S
shuang.kou 已提交
858 859
- **作用于方法**:当类配置了`@Transactional`,方法也配置了`@Transactional`,方法的事务会覆盖类的事务配置信息。

S
shuang.kou 已提交
860
更多关于关于 Spring 事务的内容请查看:
S
shuang.kou 已提交
861

S
shuang.kou 已提交
862 863
1. [可能是最漂亮的 Spring 事务管理详解](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484943&idx=1&sn=46b9082af4ec223137df7d1c8303ca24&chksm=cea249c4f9d5c0d2b8212a17252cbfb74e5fbe5488b76d829827421c53332326d1ec360f5d63&token=1082669959&lang=zh_CN#rd)
2. [一口气说出 6 种 @Transactional 注解失效场景](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486483&idx=2&sn=77be488e206186803531ea5d7164ec53&chksm=cea243d8f9d5cacecaa5c5daae4cde4c697b9b5b21f96dfc6cce428cfcb62b88b3970c26b9c2&token=816772476&lang=zh_CN#rd)
S
shuang.kou 已提交
864

S
shuang.kou 已提交
865
### 10. json 数据处理
S
shuang.kou 已提交
866

S
shuang.kou 已提交
867
#### 10.1. 过滤 json 数据
S
shuang.kou 已提交
868

S
shuang.kou 已提交
869
**`@JsonIgnoreProperties` 作用在类上用于过滤掉特定字段不返回或者不解析。**
S
shuang.kou 已提交
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898

```java
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {

    private String userName;
    private String fullName;
    private String password;
    @JsonIgnore
    private List<UserRole> userRoles = new ArrayList<>();
}
```

**`@JsonIgnore`一般用于类的属性上,作用和上面的`@JsonIgnoreProperties` 一样。**

```java

public class User {

    private String userName;
    private String fullName;
    private String password;
   //生成json时将userRoles属性过滤
    @JsonIgnore
    private List<UserRole> userRoles = new ArrayList<>();
}
```

S
shuang.kou 已提交
899
#### 10.2. 格式化 json 数据
S
shuang.kou 已提交
900

S
shuang.kou 已提交
901
`@JsonFormat`一般用来格式化 json 数据。:
S
shuang.kou 已提交
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920

比如:

```java
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;
```

#### 10.3. 扁平化对象

```java
@Getter
@Setter
@ToString
public class Account {
    @JsonUnwrapped
    private Location location;
    @JsonUnwrapped
    private PersonInfo personInfo;
S
shuang.kou 已提交
921

S
shuang.kou 已提交
922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
  @Getter
  @Setter
  @ToString
  public static class Location {
     private String provinceName;
     private String countyName;
  }
  @Getter
  @Setter
  @ToString
  public static class PersonInfo {
    private String userName;
    private String fullName;
  }
}

```

未扁平化之前:

```json
{
    "location": {
        "provinceName":"湖北",
        "countyName":"武汉"
    },
    "personInfo": {
        "userName": "coder1234",
        "fullName": "shaungkou"
    }
}
```

S
shuang.kou 已提交
955
使用`@JsonUnwrapped` 扁平对象之后:
S
shuang.kou 已提交
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980

```java
@Getter
@Setter
@ToString
public class Account {
    @JsonUnwrapped
    private Location location;
    @JsonUnwrapped
    private PersonInfo personInfo;
    ......
}
```

```json
{
  "provinceName":"湖北",
  "countyName":"武汉",
  "userName": "coder1234",
  "fullName": "shaungkou"
}
```

### 11. 测试相关

S
shuang.kou 已提交
981
**`@ActiveProfiles`一般作用于测试类上, 用于声明生效的 Spring 配置文件。**
S
shuang.kou 已提交
982 983 984 985 986 987 988 989 990 991 992 993 994 995

```java
@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase {
  ......
}
```

**`@Test`声明一个方法为测试方法**

**`@Transactional`被声明的测试方法的数据会回滚,避免污染测试数据。**

S
shuang.kou 已提交
996
**`@WithMockUser` Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。**
S
shuang.kou 已提交
997 998 999 1000 1001 1002 1003 1004 1005 1006

```java
    @Test
    @Transactional
    @WithMockUser(username = "user-id-18163138155", authorities = "ROLE_TEACHER")
    void should_import_student_success() throws Exception {
        ......
    }
```

S
shuang.kou 已提交
1007
_暂时总结到这里吧!虽然花了挺长时间才写完,不过可能还是会一些常用的注解的被漏掉,所以,我将文章也同步到了 Github 上去,Github 地址: 欢迎完善!_
S
shuang.kou 已提交
1008

S
shuang.kou 已提交
1009
本文已经收录进我的 75K Star 的 Java 开源项目 JavaGuide:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)