Валидация – это механизм проверки данных объекта на корректность перед их сохранением в базу данных. Валидация реализуется набором аннотаций Jakarta Bean Validation, которые обычно применяются к сущностям и DTO. В этом уроке мы поговорим о том, как добавить правила валидации и как выполнить саму валидацию.
Описание валидации
Возьмем для примера модель User с добавленными аннотациями для валидации:
package io.hexlet.spring.model;
// Остальные импорты
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;
    @Column(unique = true)
    @Email
    private String email;
    @NotBlank
    private String firstName;
    @NotNull
    @Size(min = 8)
    private String password;
}
В этом примере:
- @Emailпроверяет, что- emailсодержит корректный адрес
- @NotBlankпроверяет, что- firstNameсодержит хотя бы один цифро-буквенный символ
- @NotNullпроверяет, что- passwordне пустой
- @Sizeпроверяет, что минимальная длина пароля составляет восемь символов
Есть и множество других аннотаций. Заранее знать их не нужно, но имеет смысл периодически просматривать их список в спецификации.
Автоматическая валидация
Валидация выполняется с помощью аннотации @Valid, которая применяется в контроллере:
package io.hexlet.spring.controller.api;
// Остальные импорты
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api")
public class UsersController {
    @Autowired
    private UserRepository repository;
    @Autowired
    private UserMapper userMapper;
    @PostMapping("/users")
    @ResponseStatus(HttpStatus.CREATED)
    // Валидация происходит до вызова метода
    public UserDTO create(@Valid @RequestBody User user) {
        // Логика создания
    }
}
Аннотация @Valid идет в паре с @RequestBody. Сама валидация вызывается уже на получившемся объекте, в нашем примере — это user. При успешной валидации вызывается метод контроллера, при неуспешной — возникает исключение MethodArgumentNotValidException. Spring Boot обрабатывает это исключение автоматически и возвращает ошибку 400 Bad Request:
{
    "timestamp": 1695940603767,
    "status": 400,
    "error": "Bad Request",
    "message": "Validation failed for object='user'. Error count: 2",
    "errors": [
        {
            "codes": [
                "NotNull.user.firstName",
                "NotNull.firstName",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.firstName",
                        "firstName"
                    ],
                    "defaultMessage": "firstName",
                    "code": "firstName"
                }
            ],
            "defaultMessage": "must not be null",
            "objectName": "user",
            "field": "firstName",
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "NotNull.user.slug",
                "NotNull.slug",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.email",
                        "email"
                    ],
                    "defaultMessage": "email",
                    "code": "email"
                }
            ],
            "defaultMessage": "must not be null",
            "objectName": "user",
            "field": "email",
            "bindingFailure": false,
            "code": "NotNull"
        }
    ],
    "path": "/api/users/1"
}
В примере выше обратите внимание на тело запроса. Здесь мы используем для него саму сущность, но в реальном коде там почти наверняка будет использоваться DTO. В этом случае нам придется дополнительно повесить валидацию на DTO:
package io.hexlet.spring.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class UserCreateDTO {
    @Email
    private String email;
    @NotBlank
    private String firstName;
    @NotNull
    @Size(min = 8)
    private String password;
}
Теперь мы можем заменить сущность на DTO:
public UserDTO create(@Valid @RequestBody UserCreateDTO user) {
    // Код
}
На выходе мы получаем аннотации, добавленные в саму сущность и в ее DTO, используемые для создания или обновления. Это неизбежно приводит к дублированию аннотаций, что придется регулярно делать в реальном коде.
Самостоятельная работа
Валидация помогает гарантировать, что в базу сохраняются только корректные данные. Spring Boot поддерживает стандартные аннотации jakarta.validation.*, которые легко интегрируются с DTO и сущностями.
- Добавьте зависимости для валидации - В - build.gradle.ktsподключите Hibernate Validator (обычно он уже есть в Spring Boot Starter Validation).- Пример- dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") }
- Добавьте аннотации валидации в сущности и DTO - Например, в сущность пользователя и DTO для создания поста: - Пример: User.java- @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Size(min = 2, max = 50) private String firstName; @NotBlank @Size(min = 2, max = 50) private String lastName; @Email @NotBlank private String email; // геттеры/сеттеры }- Пример: PostCreateDTO.java- public class PostCreateDTO { @NotBlank @Size(min = 5, max = 100) private String title; @NotBlank @Size(min = 10, max = 1000) private String content; private boolean published; // геттеры/сеттеры }
- Включите валидацию в контроллерах - Используйте - @Validпри приёме DTO.- Пример: PostController.java- @RestController @RequestMapping("/posts") public class PostController { private final PostRepository postRepository; private final PostMapper postMapper; public PostController(PostRepository postRepository, PostMapper postMapper) { this.postRepository = postRepository; this.postMapper = postMapper; } @PostMapping public ResponseEntity<PostDTO> create(@Valid @RequestBody PostCreateDTO dto) { Post post = postMapper.toEntity(dto); postRepository.save(post); return ResponseEntity.status(HttpStatus.CREATED).body(postMapper.toDTO(post)); } }
- Обрабатывайте ошибки валидации - Чтобы возвращать статус 422 Unprocessable Entity, добавьте глобальный обработчик ошибок: - Пример: GlobalExceptionHandler.java- @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()) ); return ResponseEntity.unprocessableEntity().body(errors); } }
Итог
- Все входящие данные (в DTO и сущностях) автоматически проверяются.
- При ошибках пользователь получает статус 422 и понятное описание проблемы.
- В контроллерах используется @Valid, что делает код чище.
- Безопасность и надёжность приложения повысились.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.