انتقل إلى المحتوى الرئيسي

الفصل 2: التحقق من البيانات

مقدمة​

التحقق من البيانات ضروري لضمان سلامة وجودة البيانات في تطبيقك. يدمج Spring Boot Bean Validation (JSR 380) للتحقق بسهولة من بيانات الإدخال.


Bean Validation (JSR 380)​

تبعية Maven​

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

ملاحظة: هذه التبعية مضمنة بالفعل في spring-boot-starter-web.


التعليقات التوضيحية الرئيسية للتحقق​

جدول ملخص​

التعليق التوضيحيالاستخدامالمثال
@NotNullلا يمكن أن يكون null@NotNull private String name;
@NotEmptyلا يمكن أن يكون null أو فارغ@NotEmpty private String email;
@NotBlankلا يمكن أن يكون null أو فارغ أو مجرد مسافات@NotBlank private String title;
@Sizeحجم الحد الأدنى/الأقصى@Size(min=3, max=50) private String name;
@Minالقيمة الدنيا@Min(0) private int age;
@Maxالقيمة القصوى@Max(100) private int grade;
@Emailتنسيق بريد إلكتروني صالح@Email private String email;
@Patternتعبير منتظم@Pattern(regexp="[0-9]{10}") private String phone;
@Pastتاريخ في الماضي@Past private LocalDate birthDate;
@Futureتاريخ في المستقبل@Future private LocalDate appointmentDate;

مثال كامل: كيان مع التحقق​

package com.example.demo.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "products")
public class Product {

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

@NotBlank(message = "الاسم لا يمكن أن يكون فارغًا")
@Size(min = 3, max = 50, message = "يجب أن يحتوي الاسم على 3 إلى 50 حرفًا")
private String name;

@NotNull(message = "السعر مطلوب")
@Min(value = 0, message = "لا يمكن أن يكون السعر سالبًا")
private Double price;

@NotNull(message = "الكمية مطلوبة")
@Min(value = 0, message = "لا يمكن أن تكون الكمية سالبة")
private Integer quantity;

@Email(message = "البريد الإلكتروني غير صالح")
private String supplierEmail;

// Constructors, getters, setters
public Product() {}

public Product(String name, Double price, Integer quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}

// Getters et Setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

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

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

public Integer getQuantity() {
return quantity;
}

public void setQuantity(Integer quantity) {
this.quantity = quantity;
}

public String getSupplierEmail() {
return supplierEmail;
}

public void setSupplierEmail(String supplierEmail) {
this.supplierEmail = supplierEmail;
}
}

الاستخدام في الـ Controller مع @Valid​

package com.example.demo.controller;

import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

// CREATE مع التحقق
@PostMapping
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) {
Product created = productService.createProduct(product);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}

// UPDATE مع التحقق
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id,
@Valid @RequestBody Product product) {
Product updated = productService.updateProduct(id, product);
return ResponseEntity.ok(updated);
}

// GET - الكل
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.getAllProducts());
}

// GET - حسب ID
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}

// DELETE
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
}
}

نقاط رئيسية:

  • @Valid يُفعّل التحقق التلقائي
  • إذا فشل التحقق، يقوم Spring بإرجاع كود 400 Bad Request تلقائيًا

إدارة أخطاء التحقق​

استجابة تلقائية (افتراضيًا)​

عندما يفشل التحقق، يقوم Spring بإرجاع:

{
"timestamp": "2025-11-28T10:15:30.000+00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"errors": [
{
"field": "name",
"message": "الاسم لا يمكن أن يكون فارغًا"
},
{
"field": "price",
"message": "لا يمكن أن يكون السعر سالبًا"
}
]
}

تخصيص مع @ControllerAdvice​

package com.example.demo.exception;

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.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

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

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

استجابة مخصصة:

{
"name": "الاسم لا يمكن أن يكون فارغًا",
"price": "لا يمكن أن يكون السعر سالبًا"
}

رسائل مخصصة​

النهج 1: مباشرة في التعليق التوضيحي​

@NotBlank(message = "اسم المنتج مطلوب")
@Size(min = 3, max = 50, message = "يجب أن يحتوي الاسم على {min} إلى {max} حرفًا")
private String name;

النهج 2: ملف الرسائل (messages.properties)​

ملف src/main/resources/messages.properties:

product.name.notblank=اسم المنتج مطلوب
product.name.size=يجب أن يحتوي الاسم على {min} إلى {max} حرفًا
product.price.min=لا يمكن أن يكون السعر أقل من {value}

كيان:

@NotBlank(message = "{product.name.notblank}")
@Size(min = 3, max = 50, message = "{product.name.size}")
private String name;

@Min(value = 0, message = "{product.price.min}")
private Double price;

التحقق المخصص​

إنشاء تعليق توضيحي مخصص​

package com.example.demo.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {
String message() default "رقم الهاتف غير صالح";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

إنشاء المدقق​

package com.example.demo.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {

@Override
public void initialize(ValidPhoneNumber constraintAnnotation) {
}

@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
if (phoneNumber == null) {
return false;
}
// تنسيق كندي: (XXX) XXX-XXXX أو XXX-XXX-XXXX
return phoneNumber.matches("\\(?\\d{3}\\)?[- ]?\\d{3}[- ]?\\d{4}");
}
}

الاستخدام​

@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;

@ValidPhoneNumber(message = "يجب أن يكون رقم الهاتف بالتنسيق الكندي")
private String phoneNumber;

// Getters, setters
}

مجموعات التحقق​

تتيح التحقق بشكل مختلف حسب السياق (إنشاء مقابل تحديث).

public interface CreateValidation {}
public interface UpdateValidation {}

@Entity
public class Product {
@Id
@GeneratedValue
@Null(groups = CreateValidation.class, message = "يجب أن يكون المعرف null عند الإنشاء")
@NotNull(groups = UpdateValidation.class, message = "المعرف مطلوب للتحديث")
private Long id;

@NotBlank(groups = {CreateValidation.class, UpdateValidation.class})
private String name;
}

// Controller
@PostMapping
public ResponseEntity<Product> create(@Validated(CreateValidation.class) @RequestBody Product product) {
// ...
}

@PutMapping("/{id}")
public ResponseEntity<Product> update(@PathVariable Long id,
@Validated(UpdateValidation.class) @RequestBody Product product) {
// ...
}

ملخص الفصل​

النقاط الرئيسية​

  • Bean Validation (JSR 380) للتحقق من البيانات
  • @Valid في الـ controller لتفعيل التحقق
  • رسائل مخصصة في التعليقات التوضيحية أو ملفات الخصائص
  • @ControllerAdvice لإدارة الأخطاء بشكل عام
  • التحقق المخصص مع التعليقات التوضيحية المخصصة

التعليقات التوضيحية الأساسية​

✅ @NotNull, @NotEmpty, @NotBlank
✅ @Size, @Min, @Max
✅ @Email, @Pattern
✅ @Past, @Future
✅ @Valid


الفصل التالي: إدارة أخطاء HTTP

العودة إلى الوحدة 3