1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/GiteeOS-springboot-guide

Клонировать/Скачать
spring-bean-validation.md 42 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.06.2025 10:20 f496caa

Важность валидации данных не требует пояснений, даже если валидация осуществляется на фронтенде, необходимо ещё раз провести валидацию данных, передаваемых на бэкенд, чтобы избежать ситуации, когда пользователи обходят браузер и отправляют незаконные запросы напрямую через HTTP-инструменты.

Наиболее распространённый подход выглядит примерно так. Мы используем if/else-инструкции для проверки каждого параметра запроса.

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

    @PostMapping
    public ResponseEntity<PersonRequest> save(@RequestBody PersonRequest personRequest) {
        if (personRequest.getClassId() == null
                || personRequest.getName() == null
                || !Pattern.matches("(^Man$|^Woman$|^UGM$)", personRequest.getSex())) {

        }
        return ResponseEntity.ok().body(personRequest);
    }
}

Такой код наверняка знаком многим разработчикам, и многие открытые проекты используют подобный подход для валидации входных параметров.

Однако, такой подход не рекомендуется, так как он явно нарушает принцип единой ответственности. Включение большого количества небизнес-логики в бизнес-логику делает код трудным для поддержки и усложняет бизнес-логику.

На самом деле, есть простые способы улучшить этот код, и это основная тема данной статьи.

Без лишних слов, ниже я приведу примеры, основанные на моем опыте работы над проектами, которые демонстрируют, как можно элегантно проводить валидацию параметров в приложениях Spring Boot (что применимо и к обычным Java-приложениям).Если вы не знакомы с этим, обязательно изучите, так как вы сможете сразу применить это к вашим проектам.

Кроме того, примеры кода в данной статье используют последнюю версию Spring Boot 2.4.5! (на момент 21 апреля 2021 года)

Адрес репозитория с примерами кода: https://github.com/CodingDocs/springboot-guide/tree/master/source-code/bean-validation-demo.

Добавление зависимостей

Если вы разрабатываете обычное Java-приложение, вам могут понадобиться следующие зависимости:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.9.Final</version>
</dependency>
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>javax.el</artifactId>
    <version>2.2.6</version>
</dependency>

Однако, я уверен, что большинство из вас использует Spring Boot для разработки.

Если вы используете Spring Boot, всё становится намного проще, вам достаточно добавить зависимость spring-boot-starter-web, которая включает все необходимые зависимости. В нашем примере проекте также используется Lombok.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
```## Валидация ввода контроллера

### Валидация тела запроса

Валидация тела запроса означает валидацию параметров методов, помеченных аннотацией `@RequestBody`.

**`PersonController`**

Мы добавили аннотацию `@Valid` к параметрам, которые нужно валидировать. Если валидация не пройдет, будет выброшено исключение `MethodArgumentNotValidException`. По умолчанию Spring преобразует это исключение в HTTP статус 400 (ошибка запроса).

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

    @PostMapping
    public ResponseEntity<PersonRequest> save(@RequestBody @Valid PersonRequest personRequest) {
        return ResponseEntity.ok().body(personRequest);
    }
}

PersonRequest

Мы используем аннотации валидации для проверки параметров запроса!

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PersonRequest {

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

Пояснение регулярных выражений:

  • ^string : Соответствует строкам, начинающимся с string
  • string$ : Соответствует строкам, заканчивающимся на string
  • ^string$ : Точное соответствие строке string
  • (^Man$|^Woman$|^UGM$) : Значение должно быть одним из Man, Woman, UGMGlobalExceptionHandler

Пользовательский обработчик исключений может помочь нам поймать исключения и выполнить некоторые простые действия. Если вы не понимаете код обработки исключений ниже, вы можете ознакомиться с этой статьей «SpringBoot: несколько способов обработки исключений».

@ControllerAdvice(assignableTypes = {PersonController.class})
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
    }
}

Проверка через тестирование

Ниже я использую MockMvc для имитации запросов к Controller для проверки, работает ли это. Конечно, вы также можете использовать инструменты, такие как Postman, для проверки.```java @SpringBootTest @AutoConfigureMockMvc public class PersonControllerTest { @Autowired private MockMvc mockMvc; }

    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
               .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .andExpect(MockMvcResultMatchers.jsonPath("sex").value("sex значение не входит в допустимый диапазон"))
                .andExpect(MockMvcResultMatchers.jsonPath("name").value("name не может быть пустым"));
    }
}
```**Проверка с помощью Postman**```

```markdown
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Проверка случая, когда параметры недопустимы, выбрасывается异常并且可以正确被捕获
     */
    @Test
    public void should_check_person_value() throws Exception {
        PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                .classId("82938390").build();
        mockMvc.perform(post("/api/personRequest")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
                .![](https://img-blog.csdnimg.cn/2bk.png)

### Проверка параметров запроса

Проверка параметров запроса (Path Variables и Request Parameters) включает проверку методов, помеченных аннотациями `@PathVariable` и `@RequestParam`.

**`PersonController`**

**Не забудьте добавить аннотацию `@Validated` на уровне класса, чтобы Spring мог выполнять валидацию параметров методов.**

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

    @GetMapping("/{id}")
    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5, message = "Превышено допустимое значение id") Integer id) {
        return ResponseEntity.ok().body(id);
    }

    @PutMapping
    public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6, message = "Превышено допустимое значение name") String name) {
        return ResponseEntity.ok().body(name);
    }
}
```

**`ExceptionHandler`**

```java
  @ExceptionHandler(ConstraintViolationException.class)
  ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
     return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
  }
```

**Проверка с помощью тестов**

```java
@Test
public void should_check_path_variable() throws Exception {
    mockMvc.perform(get("/api/person/6")
                    .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isBadRequest())
      .andExpect(content().string("getPersonByID.id: Превышено допустимое значение id"));
}

@Test
public void should_check_request_param_value2() throws Exception {
    mockMvc.perform(put("/api/person")
                    .param("name", "snailclimbsnailclimb")
                    .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isBadRequest())
      .andExpect(content().string("getPersonByName.name: Превышено допустимое значение name"));
}
```**Проверка с помощью Postman**

![](https://img-blog.csdnimg.cn/20210421190508416.png)

![](https://img-blog.csdnimg.cn/20210421190810975.png)

## Проверка методов в сервисе

Можно также выполнять валидацию входных данных для любого Spring Bean, а не только для параметров контроллеров. Это можно сделать с помощью комбинации аннотаций `@Validated` и `@Valid`. В общем случае, мы предпочитаем использовать такой подход в проектах.

**ВАЖНО! Не забудьте добавить аннотацию `@Validated` к классу, этот параметр позволяет Spring'у проверять параметры методов.**

```java
@Service
@Validated
public class PersonService {

    public void validatePersonRequest(@Valid PersonRequest personRequest) {
        // выполнение действий
    }

}
```

**Тестирование:**

```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonServiceTest {
    @Autowired
    private PersonService service;

    @Test
    public void should_throw_exception_when_person_request_is_not_valid() {
        try {
            PersonRequest personRequest = PersonRequest.builder().sex("Man22")
                    .classId("82938390").build();
            service.validatePersonRequest(personRequest);
        } catch (ConstraintViolationException e) {
            // выводим сообщение об ошибке
            e.getConstraintViolations().forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
        }
    }
}
```

Результат вывода:

```
name не может быть пустым
sex значение не входит в диапазон допустимых значений
```

## Программный подход к валидации параметров

В некоторых случаях может потребоваться ручная проверка параметров и получение результатов валидации.Мы можем получить пример валидатора через фабрику `Validator`. Если вы работаете с Spring Bean, вы также можете использовать аннотацию `@Autowired` для внедрения валидатора.```java
@Autowired
Validator validator
```

Пример использования:

```java
/**
 * Ручная проверка объекта
 */
@Test
public void check_person_manually() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();
    PersonRequest personRequest = PersonRequest.builder().sex("Man22")
            .classId("82938390").build();
    Set<ConstraintViolation<PersonRequest>> violations = validator.validate(personRequest);
    violations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
}
```

Результат вывода:

```
sex значение не входит в диапазон допустимых значений
name не может быть пустым
```

## Создание пользовательских валидаторов (практически полезно)

Если встроенные аннотации валидации не удовлетворяют ваши требования, вы можете создать свои собственные.

### Пример 1: Валидация значения определенного поля в диапазоне допустимых значений

Пример добавления нового требования: полю `Region` в классе `PersonRequest` можно присвоить только одно из трех значений: `China`, `China-Taiwan`, `China-HongKong`.

**Шаг 1: Создайте аннотацию `Region`.**

```java
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = RegionValidator.class)
@Documented
public @interface Region {

    String message() default "Значение Region не входит в допустимый диапазон";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
```

**Шаг 2: Реализуйте интерфейс `ConstraintValidator` и переопределите метод `isValid`.**

```java
public class RegionValidator implements ConstraintValidator<Region, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        HashSet<String> regions = new HashSet<>();
        regions.add("China");
        regions.add("China-Taiwan");
        regions.add("China-HongKong");
        return regions.contains(value);
    }
}
```Теперь вы можете использовать эту аннотацию:

```java
@Region
private String region;
```

**Тестирование**

```java
PersonRequest personRequest = PersonRequest.builder()
        .region("Shanghai").build();
mockMvc.perform(post("/api/person")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
        .andExpect(MockMvcResultMatchers.jsonPath("region").value("Значение region не входит в допустимый диапазон"));
```

**Проверка с помощью Postman**

![](https://img-blog.csdnimg.cn/20210421203330978.png)

### Пример 2: Валидация номера телефона

Проверка корректности номера телефона с помощью регулярных выражений. Вы можете найти подходящие регулярные выражения для различных операторов на веб-сайте.

`PhoneNumber.java`

```java
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface PhoneNumber {
    String message() default "Некорректный номер телефона";
    Class<?>[] groups() default {};
    Class<?>[] payload() default {};
}
```

`PhoneNumberValidator.java`

```java
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
    @Override
    public boolean isValid(String phoneField, ConstraintValidatorContext context) {
        if (phoneField == null) {
            // может быть null
            return true;
        }
        //  大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
        // ^ 匹配输入字符串开始的位置
        // \d 匹配一个或多个数字,其中 \ 要转义,所以是 \\d
        // $ 匹配输入字符串结尾的位置
        String regExp = "^[1]((3[0-9])|(4[5-9])|(5[0-3,5-9])|([6][5,6])|(7[0-9])|(8[0-9])|(9[1,8,9]))\\d{8}$";
        return phoneField.matches(regExp);
    }
}
```

Перевод завершен, теперь мы можем использовать этот аннотацию.```java
@PhoneNumber(message = "некорректный формат номера телефона")
@NotNull(message = "номер телефона не может быть пустым")
private String phoneNumber;
```

**Тестирование**

```java
PersonRequest personRequest = PersonRequest.builder()
  	.phoneNumber("1816313815").build();
mockMvc.perform(post("/api/person")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(personRequest)))
  .andExpect(MockMvcResultMatchers.jsonPath("phoneNumber").value("некорректный формат номера телефона"));
```

![](https://img-blog.csdnimg.cn/20210421204116640.png)

## Использование групп для валидации

Группы для валидации мы редко используем, и не рекомендуем использовать их в проектах, так как они сложны для понимания и реализации. Достаточно знать основные моменты!

Когда у нас есть разные методы для работы с объектом и для них требуются разные правила валидации, мы можем использовать группы для валидации.

Я напишу простой пример, чтобы вы могли лучше понять!

**1. Создаем два интерфейса, которые будут представлять разные группы валидации**

```java
public interface AddPersonGroup {
}
public interface DeletePersonGroup {
}
```

**2. Использование групп для валидации**

```java
@Data
public class Person {
    // Когда группа валидации - DeletePersonGroup, поле group не должно быть пустым
    @NotNull(groups = DeletePersonGroup.class)
    // Когда группа валидации - AddPersonGroup, поле group должно быть пустым
    @Null(groups = AddPersonGroup.class)
    private String group;
}

@Service
@Validated
public class PersonService {

    @Validated(AddPersonGroup.class)
    public void validatePersonGroupForAdd(@Valid Person person) {
        // делаем что-то
    }
```
``````md
    @Validated(DeletePersonGroup.class)
    public void validatePersonGroupForDelete(@Valid Person person) {
        // выполняет какое-то действие
    }}
```

Проверка через тесты:

```java
  @Test(expected = ConstraintViolationException.class)
  public void should_check_person_with_groups() {
      Person person = new Person();
      person.setGroup("group1");
      service.validatePersonGroupForAdd(person);
  }

  @Test(expected = ConstraintViolationException.class)
  public void should_check_person_with_groups2() {
      Person person = new Person();
      service.validatePersonGroupForDelete(person);
  }
```

Использование групп для проверки создает ощущение, что это противоположно лучшим практикам и ухудшает поддерживаемость кода. Лучше избегать этого!

## Обзор часто используемых аннотаций для проверки

`JSR303` определяет стандарт `validation-api` для `Bean Validation` (проверки), но не предоставляет реализацию. `Hibernate Validation` является реализацией этого стандарта `hibernate-validator` и добавляет дополнительные аннотации, такие как `@Email`, `@Length`, `@Range` и другие. `Spring Validation` использует `Hibernate Validation` в качестве основы.

**Аннотации, предоставляемые JSR**:

- `@Null` — проверяет, что значение равно `null`.
- `@NotNull` — проверяет, что значение не равно `null`.
- `@Size` — проверяет длину строки, коллекции или массива.
- `@Digits` — проверяет, что строка представляет собой число с определенным количеством цифр до и после запятой.
- `@Past` — проверяет, что значение является датой в прошлом.
- `@Future` — проверяет, что значение является датой в будущем.
- `@Pattern` — проверяет, что строка соответствует определенному регулярному выражению.
- `@Min` — проверяет, что значение больше или равно указанному числу.
- `@Max` — проверяет, что значение меньше или равно указанному числу.
- `@DecimalMin` — проверяет, что значение больше или равно указанному десятичному числу.
- `@DecimalMax` — проверяет, что значение меньше или равно указанному десятичному числу.
- `@Digits` — проверяет, что строка представляет собой число с определенным колич�数字转换错误,正确的翻译应该是:

- `@Null` — проверяет, что значение равно `null`.
- `@NotNull` — проверяет, что значение не равно `null`.
- `@Size` — проверяет длину строки, коллекции или массива.
- `@Digits` — проверяет, что строка представляет собой число с определенным количеством цифр до и после запятой.
- `@Past` — проверяет, что значение является датой в прошлом.
- `@Future` — проверяет, что значение является датой в будущем.
- `@Pattern` — проверяет, что строка соответствует определенному регулярному выражению.
- `@Min` — проверяет, что значение больше или равно указанному числу.
- `@Max` — проверяет, что значение меньше или равно указанному числу.
- `@DecimalMin` — проверяет, что значение больше или равно указанному десятичному числу.
- `@DecimalMax` — проверяет, что значение меньше или равно указанному десятичному числу.
- `@Digits` — проверяет, что строка представляет собой число с определенным количеством цифр до и после запятой.- `@Null` Элемент, помеченный этой аннотацией, должен быть null.
 - `@NotNull` Элемент, помеченный этой аннотацией, не должен быть null.
 - `@AssertTrue` Элемент, помеченный этой аннотацией, должен быть true.
 - `@AssertFalse` Элемент, помеченный этой аннотацией, должен быть false.
 - `@Min(value)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть больше или равно указанному минимальному значению.
 - `@Max(value)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть меньше или равно указанному максимальному значению.
 - `@DecimalMin(value)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть больше или равно указанному минимальному значению.
 - `@DecimalMax(value)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть меньше или равно указанному максимальному значению.
 - `@Size(max=, min=)` Элемент, помеченный этой аннотацией, должен быть строкой, длина которой должна быть в пределах указанного диапазона.
 - `@Digits(integer, fraction)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть в пределах допустимого диапазона.
 - `@Past` Элемент, помеченный этой аннотацией, должен быть датой, которая находится в прошлом.
 - `@Future` Элемент, помеченный этой аннотацией, должен быть датой, которая находится в будущем.- `@Pattern(regex=,flag=)` Элемент, помеченный этой аннотацией, должен соответствовать указанному регулярному выражению.**Аннотации, предоставляемые Hibernate Validator**:

- `@NotBlank(message =)` Элемент, помеченный этой аннотацией, должен быть строкой, длина которой должна быть больше нуля.
- `@Email` Элемент, помеченный этой аннотацией, должен быть адресом электронной почты.
- `@Length(min =, max =)` Элемент, помеченный этой аннотацией, должен быть строкой, длина которой должна быть в пределах указанного диапазона.
- `@NotEmpty` Элемент, помеченный этой аннотацией, должен быть строкой, длина которой должна быть больше нуля.
- `@Range(min =, max =, message =)` Элемент, помеченный этой аннотацией, должен быть числом, значение которого должно быть в пределах указанного диапазона.

```## Расширение

Часто мне задают вопрос: "Какая разница между `@NotNull` и `@Column(nullable = false)`?"

Отвечаю кратко:

- `@NotNull` — это аннотация для валидации бинов JSR 303, и она не связана напрямую с ограничениями базы данных.
- `@Column(nullable = false)` — это способ указания JPA, что колонка должна быть неотъемлемой при создании таблицы.

Вкратце, первое используется для валидации, а второе — для указания ограничений таблицы при её создании в базе данных.
```

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/GiteeOS-springboot-guide.git
git@api.gitlife.ru:oschina-mirror/GiteeOS-springboot-guide.git
oschina-mirror
GiteeOS-springboot-guide
GiteeOS-springboot-guide
master