Spring Boot Auditing | @CreatedBy, @LastModifiedBy Sinhala Guide | SC Guide

Spring Boot Auditing | @CreatedBy, @LastModifiedBy Sinhala Guide | SC Guide

අපි කොහොමද යකෝ මේක change කරේ කවුද, කවදාද කියලා හොයාගන්නෙ? 😅 මේ ප්‍රශ්නෙ ඔයාගෙ මනසටත් ඇවිත් තියෙනවද? එහෙනම් මේ blog post එක ඔයාටම තමයි. Software development කරනකොට, විශේෂයෙන්ම Enterprise applications හදනකොට, data වෙනස්කම් track කරන එක හරිම වැදගත් වැඩක්.

හිතන්න, ඔයාගේ System එකේ Customer කෙනෙක්ගේ Address එකක් වැරදිලා. කවුද ඒක වෙනස් කරේ? කවදාද? එහෙම නැත්නම්, System එකේ Bug එකක් ආවා, ඊට කලින් මොන data එකද change කරේ? කවුද ඒක කරේ? මේ වගේ ප්‍රශ්න වලට උත්තර දෙන්න පුළුවන් නම්, Debugging වගේම, System එකේ Security සහ Accountability එක වැඩි වෙනවා.

අද අපි බලමු, Spring Boot වගේ පට්ටම Framework එකක් භාවිතා කරලා, මේ Auditing කියන concept එක කොහොමද Implement කරන්නේ කියලා. විශේෂයෙන්ම, Spring Data JPA එකේ තියෙන @CreatedBy සහ @LastModifiedBy කියන Annotations හරහා, Entity එකක වෙනසක් කරේ කවුද කියලා හොයාගන්න විදිහ අපි සරලව කතා කරමු.

කලබල වෙන්න එපා, මං ඔයාට පියවරෙන් පියවර මේක කොහොමද කරන්නේ කියලා කියලා දෙන්නම්. එහෙනම්, අපි පටන් ගමු!

ඇයි අපිට Auditing ඕනෙ? 🤔

හරි, මුලින්ම බලමු ඇයි අපිට මේ වගේ දෙයක් ඕනෙ වෙන්නෙ කියලා. ගොඩක් වෙලාවට developer කෙනෙක් විදියට අපි Product එකක Feature එකක් විතරයි හදන්නෙ. ඒත්, ඒ Feature එක වැඩ කරනකොට, ඒකෙන් මොන data එකද change වෙන්නෙ, කවුද change කරන්නෙ වගේ දේවල් track කරන එක, හරිම වැදගත්. මෙන්න මේ වගේ හේතු නිසා අපිට Auditing අවශ්‍ය වෙනවා:

  1. වගවීම (Accountability): System එකේ මොකක් හරි වැරැද්දක් වුනොත්, ඒකට කවුද වගකියන්න ඕනෙ කියලා හොයාගන්න Auditing උදව් වෙනවා. User කෙනෙක් මොන data එකද වෙනස් කරේ, කවදාද වගේ දේවල් පැහැදිලිව දකින්න පුළුවන්.
  2. දෝෂ නිරාකරණය (Debugging): Production එකේ Bug එකක් ආවොත්, Auditing records බලලා, අන්තිමට මොන data එකද වෙනස් කරේ, කවුද ඒක කරේ කියලා හොයාගන්න පුළුවන්. ඒකෙන් Bug එකට හේතුව ඉක්මනටම හොයාගන්න පුළුවන්.
  3. නියාමනය සහ අනුකූලතාව (Compliance & Regulations): සමහර Industry වලට, නීතිමය හේතු නිසා data changes track කරන්න වෙනවා (උදා: Financial sector). Auditing කියන්නෙ ඒ අවශ්‍යතාවය සපුරගන්න තියෙන හොඳම ක්‍රමයක්.
  4. ආරක්ෂාව (Security): System එකේ අනවසර වෙනස්කම් සිද්ධ වෙනවා නම්, Auditing records බලලා ඒව හඳුනාගන්න පුළුවන්. ඒ වගේම, Security Audit එකක් කරනකොට මේ records හරිම වැදගත් වෙනවා.

දැන් ඔයාට තේරෙනවා ඇති නේද, Auditing කියන්නෙ නිකන්ම නිකන් Feature එකක් නෙවෙයි කියලා? ඒක Production ready System එකකට අනිවාර්යයෙන්ම ඕන කරන දෙයක්.

Spring Data Auditing කියන්නෙ මොකක්ද? 🤔

හරි, දැන් අපි බලමු Spring Boot මේ වැඩේට අපිට උදව් කරන්නෙ කොහොමද කියලා. Spring Data JPA කියන්නෙ JPA (Java Persistence API) පාවිච්චි කරලා Database එකත් එක්ක වැඩ කරන්න පුළුවන් Framework එකක්. මේකේ Auditing කියලා Feature එකක් තියෙනවා, ඒක තමයි අපේ වැඩේට අත දෙන්නෙ.

Spring Data Auditing වලදී, අපිට පහසුවෙන්ම Entity එකක creation date, last modified date, created by user, සහ last modified by user කියන දේවල් track කරන්න පුළුවන්. මේකට අපිට විශේෂ Code lines ගොඩක් ලියන්න ඕනෙ නෑ. Annotations කිහිපයක් සහ Configuration ටිකක් විතරයි අවශ්‍ය වෙන්නෙ.

Auditing Enable කරමු

මුලින්ම, අපේ Spring Boot Application එකේ Auditing enable කරන්න ඕනෙ. ඒකට, Main Application Class එකේ (@SpringBootApplication තියෙන Class එකේ) @EnableJpaAuditing කියන Annotation එක දාන්න ඕනෙ. මෙන්න මෙහෙම:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing // Auditing enable කිරීම
public class AuditingDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuditingDemoApplication.class, args);
    }
}

මේ @EnableJpaAuditing Annotation එක දැම්මාට පස්සෙ, Spring Data JPA එක තේරුම් ගන්නවා, අපිට Auditing අවශ්‍යයි කියලා.

@CreatedBy, @LastModifiedBy භාවිතා කරමු 🛠️

දැන් අපේ Entity එකේ fields වලට මේ Auditing values set කරන්න ඕනෙ. ඒකට අපි @CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate කියන Annotations භාවිතා කරනවා.

මේ fields වලට data එන්නෙ කොහෙන්ද? ඒක තමයි ප්‍රශ්නෙ. @CreatedDate සහ @LastModifiedDate වලට System එකේ current time එක automatically set වෙනවා. ඒත් @CreatedBy සහ @LastModifiedBy වලට user ගේ id එකක් හෝ username එකක් වගේ දෙයක් set වෙන්න ඕනෙ. ඒක provide කරන්න, Spring එකේ AuditorAware කියන Interface එකක් තියෙනවා.

AuditorAware Interface එක Implement කරමු

AuditorAware Interface එකට, අපිට අවශ්‍ය Type එකේ Auditor කෙනෙක්ව return කරන්න පුළුවන්. ගොඩක් වෙලාවට අපි String Type එකෙන් Username එකක් return කරනවා, නැත්නම් Long Type එකෙන් User ID එකක් return කරනවා. මෙතනදී, අපි String Type එකෙන් Username එකක් return කරන විදිහ බලමු. මේ Class එක Spring Component එකක් විදියට register කරන්න ඕනෙ.

import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component("auditorAware") // Spring Component එකක් ලෙස register කිරීම
public class SecurityAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        // මෙතන තමයි SecurityContextHolder එකෙන් Logged-in User කෙනාව ගන්න ඕනෙ
        // සරල උදාහරණයක් විදියට අපි system_user කියලා දාමු
        // Real world application එකක නම්, Spring Security පාවිච්චි කරලා User කෙනාව ගන්න පුළුවන්.
        // උදා: SecurityContextHolder.getContext().getAuthentication().getName();
        return Optional.of("system_user"); 
    }
}

මේ SecurityAuditorAware Class එකේ getCurrentAuditor() method එක තමයි වැදගත්. මේකෙන් තමයි Spring Data JPA එකට @CreatedBy සහ @LastModifiedBy fields වලට data ලබා දෙන්නෙ. Real world application එකක නම්, ඔයාට පුළුවන් Spring Security Framework එක භාවිතා කරලා, දැනට Login වෙලා ඉන්න User ගේ username එක හෝ ID එක මේ method එකෙන් return කරන්න.

Entity එකේ Auditing Fields එකතු කරමු

දැන් අපේ Entity එකට Auditing fields එකතු කරමු. මේවා හැම Entity එකටම අවශ්‍ය වෙන නිසා, අපි BaseEntity කියලා Abstract Class එකක් හදමු. ඊට පස්සෙ, අපේ අනිත් Entities වලට මේ BaseEntity එක Extend කරන්න පුළුවන්.

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@MappedSuperclass // මේ Class එක Table එකකට map වෙන්නේ නැහැ, නමුත් Subclasses වලට Fields share කරනවා.
@EntityListeners(AuditingEntityListener.class) // Auditing events listen කිරීමට
public abstract class BaseEntity {

    @CreatedBy
    @Column(name = "created_by", updatable = false) // Record එක create කරනකොට set වෙනවා, update කරන්න බෑ.
    protected String createdBy;

    @CreatedDate
    @Column(name = "created_date", updatable = false) // Record එක create කරනකොට set වෙනවා, update කරන්න බෑ.
    protected LocalDateTime createdDate;

    @LastModifiedBy
    @Column(name = "last_modified_by") // Record එක update කරනකොට set වෙනවා.
    protected String lastModifiedBy;

    @LastModifiedDate
    @Column(name = "last_modified_date") // Record එක update කරනකොට set වෙනවා.
    protected LocalDateTime lastModifiedDate;

    // Getters and Setters
    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public LocalDateTime getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(LocalDateTime createdDate) {
        this.createdDate = createdDate;
    }

    public String getLastModifiedBy() {
        return lastModifiedBy;
    }

    public void setLastModifiedBy(String lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }

    public LocalDateTime getLastModifiedDate() {
        return lastModifiedDate;
    }

    public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    @Override
    public String toString() {
        return "BaseEntity{" +
               "createdBy='" + createdBy + '\'' +
               ", createdDate=" + createdDate +
               ", lastModifiedBy='" + lastModifiedBy + '\'' +
               ", lastModifiedDate=" + lastModifiedDate +
               '}';
    }
}

@MappedSuperclass කියන Annotation එක භාවිතා කරන්නෙ මේ Class එක Database Table එකකට map වෙන්නේ නැතුව, ඒක Extend කරන Subclasses වලට මේ fields share කරන්නයි. @EntityListeners(AuditingEntityListener.class) කියන්නෙ මේ Auditing events listen කරන්න අවශ්‍ය බව Spring Data JPA එකට කියන එකයි.

දැන් ඔයාට තේරෙනවා ඇති @Column(updatable = false) කියලා දැම්මම, ඒ field එකට data set වුනාට පස්සෙ ආයෙ update කරන්න බැරි වෙන විදිහට set වෙනවා කියලා. @CreatedBy, @CreatedDate කියන fields record එක create කරනකොට විතරක් set වෙනවා. @LastModifiedBy, @LastModifiedDate කියන fields record එක update කරන හැම වෙලාවෙම update වෙනවා.

ප්‍රායෝගික උදාහරණයක් (Practical Example) 🚀

හරි, දැන් අපි මේ හැමදේම එකට එකතු කරලා, පොඩි Project එකක් හදලා බලමු.

Project එක Setup කරමු

මුලින්ම, Spring Initializr (start.spring.io) එකට ගිහින් අලුත් Spring Boot Project එකක් හදන්න. මේ Dependencies ටික add කරන්න:

  • Spring Web
  • Spring Data JPA
  • H2 Database (සරලව Local machine එකේ Run කරන්න)

application.properties (or application.yml) file එකට මේ Settings ටික දාන්න:

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

Product Entity එක හදමු

දැන් අපි Product කියලා Entity එකක් හදමු. මේක BaseEntity එක Extend කරනවා.

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Product extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    // Constructors
    public Product() {
    }

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // Getters and 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;
    }

    @Override
    public String toString() {
        return "Product{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", price=" + price +
               ", createdBy='" + getCreatedBy() + '\'' +
               ", createdDate=" + getCreatedDate() +
               ", lastModifiedBy='" + getLastModifiedBy() + '\'' +
               ", lastModifiedDate=" + getLastModifiedDate() +
               '}';
    }
}

Product Repository එක හදමු

සරලව JpaRepository එක Extend කරන Interface එකක් හදමු.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

Data Initializer එකක් හදලා Test කරමු

දැන් අපි CommandLineRunner එකක් භාවිතා කරලා, Application එක Start වෙනකොට data save කරලා, update කරලා බලමු.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DataInitializer implements CommandLineRunner {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("\n--- Spring Boot Auditing Demo Started ---");

        // 1. අලුත් Product එකක් Create කරමු
        Product laptop = new Product("Dell XPS Laptop", 1500.00);
        productRepository.save(laptop);
        System.out.println("Saved new Product: " + laptop);

        Thread.sleep(2000); // පොඩ්ඩක් වෙලා යමු කියලා simulate කරමු

        // 2. Product එක Update කරමු
        laptop.setPrice(1450.00);
        productRepository.save(laptop);
        System.out.println("Updated Product: " + laptop);

        Thread.sleep(1000); // තව පොඩ්ඩක් වෙලා යමු

        // 3. තව Product එකක් Create කරමු
        Product mouse = new Product("Logitech MX Master Mouse", 99.00);
        productRepository.save(mouse);
        System.out.println("Saved another Product: " + mouse);

        System.out.println("\n--- All Products in DB --- ");
        productRepository.findAll().forEach(p -> System.out.println(p));

        System.out.println("--- Demo Finished ---\n");
    }
}

Run කරලා Result එක බලමු!

දැන් ඔයාගේ Application එක Run කරන්න. Console එකේ Output එක බලන්න.

මුලින්ම, Dell XPS Laptop එක Save කරනකොට, createdBy සහ createdDate fields ටික set වෙනවා. lastModifiedBy සහ lastModifiedDate fields වලටත් ඒ values ටිකම set වෙනවා, මොකද ඒක අලුත් record එකක් නිසා.

Saved new Product: Product{id=1, name='Dell XPS Laptop', price=1500.0, createdBy='system_user', createdDate=2023-10-27T10:30:00.123456, lastModifiedBy='system_user', lastModifiedDate=2023-10-27T10:30:00.123456}

ඊට පස්සෙ, අපි laptop එකේ price එක update කරනකොට, lastModifiedBy සහ lastModifiedDate fields update වෙනවා. createdBy සහ createdDate fields එහෙම්මම තියෙනවා.

Updated Product: Product{id=1, name='Dell XPS Laptop', price=1450.0, createdBy='system_user', createdDate=2023-10-27T10:30:00.123456, lastModifiedBy='system_user', lastModifiedDate=2023-10-27T10:30:02.789012}

Logitech MX Master Mouse එක Save කරනකොටත් ඒ විදිහමයි.

Saved another Product: Product{id=2, name='Logitech MX Master Mouse', price=99.0, createdBy='system_user', createdDate=2023-10-27T10:30:03.456789, lastModifiedBy='system_user', lastModifiedDate=2023-10-27T10:30:03.456789}

මෙතනදී createdBy එකට හැමවෙලාවෙම system_user කියලා එන්නෙ, අපි SecurityAuditorAware එකේ return කරපු value එක තමයි. ඔයාට පුළුවන් මේක, Logged-in User කෙනෙක්ව return කරන විදිහට වෙනස් කරන්න.

නිගමනය (Conclusion) 🔚

ඉතින්, ඔන්න ඕක තමයි Spring Boot Auditing වල @CreatedBy සහ @LastModifiedBy භාවිතා කරන විදිහ. මේක අපේ Applications වලට වගවීම, ආරක්ෂාව, සහ දෝෂ නිරාකරණය වගේ වැදගත් දේවල් එකතු කරනවා. Spring Data JPA එක නිසා මේ වැඩේ හරිම පහසුයි. සරල Configuration ටිකකින් සහ Annotations කිහිපයකින් අපිට මේ Auditing functionality එක Implement කරන්න පුළුවන්.

ඔයාලත් මේක ඔයාලගේ Project වලට Implement කරලා බලන්න. මොකද, theory විතරක් මදි, ප්‍රායෝගිකව කරනකොට තමයි ගොඩක් දේවල් තේරෙන්නෙ. මේ ගැන මොනාහරි ප්‍රශ්න තියෙනවා නම්, නැත්නම් ඔයා දන්න අලුත් දෙයක් තියෙනවා නම්, පහලින් comment එකක් දාන්න. හැමෝටම උදව් වෙයි!

මතක තියාගන්න, Software development කියන්නෙ හැමදාම අලුත් දේවල් ඉගෙන ගන්න එකනෙ බන්! Happy coding! 😉