Зарегистрируйтесь, чтобы продолжить обучение

Валидация сущностей Spring Boot

Валидация – это механизм проверки данных объекта на корректность перед их сохранением в базу данных. Валидация реализуется набором аннотаций 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 и сущностями.

  1. Добавьте зависимости для валидации

    В build.gradle.kts подключите Hibernate Validator (обычно он уже есть в Spring Boot Starter Validation).

    Пример
    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-validation")
    }
    
  2. Добавьте аннотации валидации в сущности и 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;
    
        // геттеры/сеттеры
    }
    
  3. Включите валидацию в контроллерах

    Используйте @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));
        }
    }
    
  4. Обрабатывайте ошибки валидации

    Чтобы возвращать статус 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, что делает код чище.
  • Безопасность и надёжность приложения повысились.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff