Hibernate Validator: REST API වලට Validation දාමු! | SC Guide

Hibernate Validator: REST API වලට Validation දාමු! | SC Guide

කට්ටියම කොහොමද? අද අපි කතා කරන්නේ software development වලදී අත්‍යවශ්‍යම දෙයක් ගැන – ඒ තමයි Data Validation! ඔයා වෙබ් සයිට් එකක් හදනකොට, mobile app එකක් හදනකොට, නැත්නම් backend API එකක් හදනකොට user කෙනෙක් දෙන data එක හරියටම අපිට ඕන format එකට තියෙනවද කියලා බලන එක මාර වැදගත්. මේක හරියටම නොකළොත් මොකද වෙන්නේ? හිතලා බලන්නකෝ, කෙනෙක් email එකක් දෙන තැනට "මම" කියලා දැම්මොත්? නැත්නම් වයස දාන තැනට -50 කියලා දැම්මොත්? ලොකු අවුල් වෙන්න පුළුවන් නේද? Database corrupt වෙන්න පුළුවන්, security issues එන්න පුළුවන්, application එක crash වෙන්නත් පුළුවන්.

ඉතින්, මේ වගේ තත්ත්වයන් වළක්වා ගන්න අපිට validation කියන concept එක පාවිච්චි කරන්න වෙනවා. Java ලෝකයේදී මේ වැඩේට අපිට ගොඩක් උදව් වෙන tool එකක් තමයි Hibernate Validator කියන්නේ. අද මේ post එකෙන් අපි බලමු Hibernate Validator පාවිච්චි කරලා කොහොමද අපේ application වලට data validation එකක් add කරගන්නේ කියලා. විශේෂයෙන්ම, REST API එකක input validation කොහොමද කරන්නේ කියන එක practical විදිහට ඉගෙන ගමු.

Data Validation කියන්නේ මොකක්ද?

සරලවම කිව්වොත්, Data Validation කියන්නේ application එකකට එන data එක නිවැරදිද, සම්පූර්ණද, සහ අපේ නීතිරීති වලට අනුකූලද කියලා පරීක්ෂා කරන ක්‍රියාවලිය. මේක අපි කරන්නේ data එක process කරන්න කලින්. හිතන්නකෝ, ඔයා online form එකක් fill කරනවා. ඒ form එකේ ඔයාගේ නම, email එක, phone number එක වගේ දේවල් ඉල්ලනවා. ඔයා email එකක් දෙන තැනට හරියටම email format එකට නැති එකක් දැම්මොත්, form එක submit කරන්න දෙන්නේ නෑ නේද? ඒක තමයි validation එකක්.

මෙහෙම validation එකක් කිරීමෙන් මොකද වෙන්නේ? ප්‍රධාන වශයෙන්ම, data integrity එක ආරක්ෂා වෙනවා. ඒ කියන්නේ අපේ database එකට වැරදි data ඇතුල් වීම වළක්වනවා. ඒ වගේම, security එකත් වැඩි වෙනවා. Malicious data ඇතුල් වීමෙන් SQL Injection වගේ ප්‍රහාර වලින් ආරක්ෂා වෙන්න පුළුවන්. ඒ විතරක් නෙවෙයි, user experience එකත් වැඩි වෙනවා, මොකද userට ඉක්මනටම තමන්ගේ වැරැද්ද හදාගන්න අවස්ථාව ලැබෙනවා.

Hibernate Validator වලට හැඳින්වීමක්

Java ලෝකයේ validation වලට තියෙන standard එක තමයි Bean Validation API (JSR 380) එක. මේ standard එකෙන් කියන්නේ මොකක්ද? “මෙන්න මේ වගේ annotations (උදා: @NotNull, @Size) පාවිච්චි කරලා ඔයාගේ Java objects වල properties validate කරන්න පුළුවන්. ඒ වගේම මේ validation rules define කරන්න පොදු ක්‍රමවේදයක් තියෙන්න ඕනේ” කියලා. Bean Validation API එක කියන්නේ specification එකක් විතරයි, ඒ කියන්නේ ඒක ක්‍රියාත්මක කරන විදිහ ගැන විතරයි කියන්නේ.

දැන් Hibernate Validator කියන්නේ මේ Bean Validation API එකේ reference implementation එක. ඒ කියන්නේ JSR 380 standard එකේ තියෙන හැමදේම Hibernate Validator වල ක්‍රියාත්මක කරලා තියෙනවා. Hibernate Validator කියන්නේ ඒක Hibernate ORM (Object-Relational Mapping) framework එකේ කොටසක් නෙවෙයි. ඒක standalone library එකක් විදිහටත් පාවිච්චි කරන්න පුළුවන්. Spring Boot වගේ framework වලදී මේක default විදිහටම එන නිසා අපිට මේක වෙනම add කරන්න ඕන වෙන්නේ නෑ.

පොදු Annotations සහ ඒවා භාවිතා කරන හැටි

Hibernate Validator එකේදී අපිට ගොඩක් ප්‍රයෝජනවත් වෙන annotations ගොඩක් තියෙනවා. අපි දැන් ඒවායින් කිහිපයක් ගැන බලමු:

@NotNull, @NotBlank, @NotEmpty

  • @NotNull: මේක පාවිච්චි කරන්නේ field එකක value එක null වෙන්න බෑ කියලා කියන්න. String, Integer, Custom Object ඕන එකකට මේක දාන්න පුළුවන්.
  • @NotEmpty: මේක String, Collection, Map, Array වගේ දේවල් වලට විතරයි පාවිච්චි කරන්න පුළුවන්. null වෙන්නත් බෑ, හිස් වෙන්නත් බෑ (ඒ කියන්නේ length එක 0 වෙන්න බෑ). String එකක් නම් "" (empty string) වෙන්න බෑ.
  • @NotBlank: මේක String වලට විතරයි පාවිච්චි කරන්න පුළුවන්. null වෙන්නත් බෑ, හිස් වෙන්නත් බෑ, white space වලින් විතරක් තියෙන්නත් බෑ (" " වගේ).

මෙන්න මේකේ වෙනස පොඩි code එකකින් බලමු:


public class UserDetails {
    @NotNull(message = "Username cannot be null")
    private String username;

    @NotEmpty(message = "Email cannot be empty")
    private String email;

    @NotBlank(message = "Password cannot be blank")
    private String password;

    // Getters and Setters
}

@Size

මේක String, Collection, Map, Array වල size එක හරි length එක හරි check කරන්න පාවිච්චි කරනවා. min සහ max කියලා parameters දෙකක් තියෙනවා.


public class Product {
    @Size(min = 3, max = 50, message = "Product name must be between 3 and 50 characters")
    private String productName;

    @Size(min = 1, max = 5, message = "Tags must be between 1 and 5")
    private List<String> tags;

    // Getters and Setters
}

@Min, @Max

මේවා සංඛ්‍යාත්මක අගයන් (numeric values) වලට පාවිච්චි කරනවා. @Min කියන්නේ අවම අගයත්, @Max කියන්නේ උපරිම අගයත් කියන්න.


public class Order {
    @Min(value = 1, message = "Quantity must be at least 1")
    private int quantity;

    @Max(value = 1000, message = "Price cannot exceed 1000")
    private double price;

    // Getters and Setters
}

@Email

මේක String එකක් valid email address එකක්ද කියලා check කරන්න පාවිච්චි කරනවා.


public class User {
    @Email(message = "Please provide a valid email address")
    private String email;

    // Getters and Setters
}

@Pattern

මේක ගොඩක්ම powerful. String එකක් Regular Expression (regex) එකකට අනුකූලද කියලා check කරන්න මේක පාවිච්චි කරනවා. උදාහරණයක් විදිහට, phone number එකක්, national ID number එකක් (NIC) වගේ දේවල් validate කරන්න මේක පාවිච්චි කරන්න පුළුවන්.


public class Customer {
    @Pattern(regexp = "^\\d{10}$", message = "Mobile number must be 10 digits")
    private String mobileNumber;

    @Pattern(regexp = "^(19|20)\\d{2}(?:[0-49]\\d{2}|500)\\d{4}[vVxX]$", message = "Invalid NIC format") // Example for new NIC (might need more robust regex)
    private String nic;

    // Getters and Setters
}

REST API එකකට Validation එකක් දාමු

හරි, දැන් අපි බලමු මේවා කොහොමද practical Spring Boot REST API එකකට දාගන්නේ කියලා. මේක පට්ට ලේසියි!

පළමුව, dependency එක add කරගමු:

ඔයා Spring Boot project එකක් පාවිච්චි කරනවා නම්, spring-boot-starter-validation dependency එක pom.xml එකට add කරන්න ඕනේ. මේකෙන් Hibernate Validator සහ අවශ්‍ය අනිත් dependencies ඔක්කොම automatically download වෙනවා.


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

දෙවනුව, Request DTO එකක් හදමු:

අපි user registration එකක් සඳහා request body එකක් විදිහට පාවිච්චි කරන DTO (Data Transfer Object) එකක් හදමු. මේකට අපි කලින් කතා කරපු annotations ටික පාවිච්චි කරමු.


// src/main/java/com/scguide/validationdemo/dto/UserRegistrationRequest.java
package com.scguide.validationdemo.dto;

import jakarta.validation.constraints.*; // Use jakarta.validation for Spring Boot 3+

public class UserRegistrationRequest {

    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
    private String username;

    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters long")
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#&()\[\]{}:;',?*~$^.+=-\\]).*$",
             message = "Password must contain at least one digit, one lowercase, one uppercase, and one special character")
    private String password;

    @NotBlank(message = "Email is required")
    @Email(message = "Please provide a valid email address")
    private String email;

    @Min(value = 18, message = "You must be at least 18 years old")
    @Max(value = 100, message = "Age cannot exceed 100")
    @NotNull(message = "Age is required") // int primitive type cannot be null, so we can use Integer wrapper class for nullability check
    private Integer age;

    // Constructors, Getters, and Setters
    public UserRegistrationRequest() {}

    public UserRegistrationRequest(String username, String password, String email, Integer age) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.age = age;
    }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

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

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

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

තෙවනුව, Controller එකට @Valid දාමු:

දැන් අපේ Spring Boot REST Controller එකේ, request body එක විදිහට එන UserRegistrationRequest object එක validate කරන්න @Valid annotation එක පාවිච්චි කරනවා.


// src/main/java/com/scguide/validationdemo/controller/UserController.java
package com.scguide.validationdemo.controller;

import com.scguide.validationdemo.dto.UserRegistrationRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid; // Use jakarta.validation for Spring Boot 3+

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

    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@Valid @RequestBody UserRegistrationRequest request) {
        // If validation fails, Spring will automatically throw MethodArgumentNotValidException
        // We'll handle this exception globally.
        // If it reaches here, the 'request' object is valid.
        System.out.println("User registered: " + request.getUsername());
        return ResponseEntity.ok("User registered successfully!");
    }
}

හතරවනුව, Global Exception Handling:

Validation failed වුනාම, Spring Boot විසින් MethodArgumentNotValidException එකක් throw කරනවා. මේ exception එක අල්ලලා (catch කරලා) userට තේරෙන විදිහට error message එකක් දෙන්න අපි Global Exception Handler එකක් හදමු.


// src/main/java/com/scguide/validationdemo/exception/GlobalExceptionHandler.java
package com.scguide.validationdemo.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 new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
    }
}

දැන් බලන්න, ඔයා POST /api/users/register එකට වැරදි data එකක් යැව්වොත්, 400 Bad Request status එකත් එක්ක මොන field එකේ මොන error එකද තියෙන්නේ කියලා clear message එකක් ලැබෙනවා. පට්ට ලේසියි නේද?

උදාහරණයක් විදිහට, මේ වගේ request body එකක් යැව්වොත්:


{
    "username": "ab",
    "password": "abc",
    "email": "invalid-email",
    "age": 10
}

ඔයාට මෙන්න මේ වගේ response එකක් ලැබෙන්න ඕනේ:


{
    "password": "Password must be at least 8 characters long",
    "email": "Please provide a valid email address",
    "username": "Username must be between 3 and 20 characters",
    "age": "You must be at least 18 years old"
}

නිගමනය

ඉතින් යාලුවනේ, ඔයාලට තේරෙන්න ඇති නේද Data Validation කියන්නේ පොඩි දෙයක් නෙවෙයි කියලා. Hibernate Validator වගේ library එකක් පාවිච්චි කරලා කොච්චර ලේසියෙන් අපේ applications වලට robustness එකයි, security එකයි, user experience එකයි වැඩි කරගන්න පුළුවන්ද කියලා. මේකෙන් අපේ code එකත් clean වෙනවා, මොකද validation logic එක model එක ඇතුළෙම තියෙන නිසා.

මේ article එක ඔයාලට වැදගත් වෙන්න ඇති කියලා හිතනවා. අනිවාර්යයෙන්ම ඔයාලගේ project වලට මේ validation techniques ටික apply කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, නැත්නම් ඔයාලටත් මේ ගැන තව දේවල් add කරන්න තියෙනවා නම්, පහලින් comment එකක් දාගෙන යන්න. ඉක්මනටම තවත් අලුත් දෙයක් අරගෙන එන්නම්! සුභ දවසක්!