JavaRush /Курси /Модуль 5. Spring /Валідація даних у REST API з використанням анотації @Vali...

Валідація даних у REST API з використанням анотації @Valid

Модуль 5. Spring
Рівень 10 , Лекція 8
Відкрита

Перш ніж занурюватись у код, поставимо просте, але важливе питання: навіщо взагалі потрібна валідація? Уявімо, що ваш REST API — це вхід на вечірку, а дані запиту — це гості. Ви не можете пустити всіх підряд: спочатку треба переконатися, що дані відповідають певним критеріям (наприклад, вік більше 18, немає підозрілих значень на кшталт "NaN" замість імені). Якщо на вечірку потраплять недоречні "гості", додаток може "влаштувати концерт", впасти з помилкою або "підпалити сервер".

Валідація вирішує цю проблему, перевіряючи дані запиту ще до їх обробки. Це допомагає запобігти безглуздим помилкам на сервері, забезпечує цілісність даних і спрощує дебаг.


Основи Bean Validation API

Spring використовує специфікацію Bean Validation API для реалізації перевірки даних. Ця специфікація з'явилась у Java EE 6 ще 2009 року (так, це дуже давня магія в термінах IT). На практиці ми використовуємо реалізацію Hibernate Validator, яка входить до Spring Boot за замовчуванням.

Основна ідея Bean Validation — використання анотацій для вказання правил перевірки на рівні моделі даних (тех самих класів DTO або сутностей). Ось список популярних анотацій:

Аннотація Опис
@NotNull Поле не може бути null
@Size Вказує мінімальну та максимальну допустиму довжину рядка
@Min Мінімальне значення для числових полів
@Max Максимальне значення для числових полів
@Email Перевіряє формат рядка як адресу електронної пошти
@Pattern Дозволяє задати регулярний вираз для перевірки рядка

Ці анотації чіпляються за поля і творять магію валідації, але їхня потужність розкривається лише завдяки @Valid.


Застосування Bean Validation у Spring REST API

Тепер перейдемо від теорії до практики. Ми будемо валідувати вхідні дані в запиті. Для прикладу створимо просте API для керування користувачами (бо користувачі подобаються всім API).

Створення DTO для користувача

DTO (Data Transfer Object) — це клас, який передає дані між клієнтом і сервером. Спочатку створимо DTO з анотаціями валідації:


package com.example.demo.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public class UserDTO {

    @NotNull(message = "Ім'я користувача не може бути порожнім")
    @Size(min = 2, max = 50, message = "Ім'я користувача має містити від 2 до 50 символів")
    private String name;

    @NotNull(message = "Електронна пошта обов'язкова")
    @Email(message = "Невірний формат електронної пошти")
    private String email;

    @NotNull(message = "Пароль не може бути порожнім")
    @Size(min = 6, message = "Пароль повинен містити щонайменше 6 символів")
    private String password;

    // Геттери та сеттери...

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Тут ми задали обмеження для кожного поля. Наприклад, ім'я користувача має містити від 2 до 50 символів, а email має відповідати формату.


Додавання валідації в контролер

Валідація даних відбувається автоматично, якщо ви вкажете анотацію @Valid перед об'єктом в запиті. Давайте створимо контролер для створення користувача:


package com.example.demo.controller;

import com.example.demo.dto.UserDTO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO user) {
        // Переданий об'єкт вже пройшов валідацію, якщо ми тут!
        return new ResponseEntity<>("Користувач успішно створений: " + user.getName(), HttpStatus.CREATED);
    }
}

Отже, що тут відбувається?

  1. Ми додали @Valid перед параметром user. Ця анотація сигналізує Spring, що об'єкт потрібно перевірити перед виконанням методу.
  2. Якщо дані в user не відповідають вимогам, Spring автоматично згенерує помилку 400 (Bad Request) і поверне опис помилки.

Тепер давайте змоделюємо ситуацію, де клієнт надсилає некоректні дані.


Приклад некоректного запиту

Клієнт надсилає HTTP POST-запит на /users з таким тілом:


{
    "name": "",
    "email": "not-an-email",
    "password": "123"
}

Spring спрацює і відправить у відповідь помилку з описом:


{
    "timestamp": "2023-10-10T12:00:00.000+00:00",
    "status": 400,
    "errors": [
        "Ім'я користувача не може бути порожнім",
        "Невірний формат електронної пошти",
        "Пароль повинен містити щонайменше 6 символів"
    ]
}

Зверніть увагу, що повідомлення про помилки беруться з полів message в анотаціях нашого DTO.


Обробка помилок валідації

Стандартна обробка помилок не завжди зручна. Давайте кастомізуємо її за допомогою @ExceptionHandler.

Створимо глобальний обробник виключень:


package com.example.demo.exception;

import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();

        for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
            errors.put(fieldError.getField(), fieldError.getDefaultMessage());
        }

        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

Тепер наша відповідь на некоректний запит виглядатиме так:


{
    "name": "Ім'я користувача не може бути порожнім",
    "email": "Невірний формат електронної пошти",
    "password": "Пароль повинен містити щонайменше 6 символів"
}

Таке подання помилок простіше читати і обробляти клієнтам.


Застосування в реальному житті та типові помилки

Де це стане в нагоді?

  • На співбесідах. Вміння валідувати дані — це базовий навик для будь-якого backend-розробника. Вас обов'язково запитають: "Як ви обробляєте невалідні дані в API?"
  • У реальних проєктах. Валідація захищає сервер від сміттєвих даних і полегшує супровід коду.

Типові помилки

  • Забули @Valid. Якщо ви не додасте @Valid, то дані просто не будуть перевірятися.
  • Неправильні повідомлення про помилки. Завжди намагайтесь писати зрозумілі та дружні до користувача повідомлення.
  • Обмеження на рівні бази даних. Пам'ятайте, що валідація на рівні коду не замінює обмеження на рівні бази, такі як NOT NULL або CHECK.

Тепер ви не лише знаєте, як валідувати дані в Spring, а й можете уникнути типових пасток!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ