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 අවශ්ය වෙනවා:
- වගවීම (Accountability): System එකේ මොකක් හරි වැරැද්දක් වුනොත්, ඒකට කවුද වගකියන්න ඕනෙ කියලා හොයාගන්න Auditing උදව් වෙනවා. User කෙනෙක් මොන data එකද වෙනස් කරේ, කවදාද වගේ දේවල් පැහැදිලිව දකින්න පුළුවන්.
- දෝෂ නිරාකරණය (Debugging): Production එකේ Bug එකක් ආවොත්, Auditing records බලලා, අන්තිමට මොන data එකද වෙනස් කරේ, කවුද ඒක කරේ කියලා හොයාගන්න පුළුවන්. ඒකෙන් Bug එකට හේතුව ඉක්මනටම හොයාගන්න පුළුවන්.
- නියාමනය සහ අනුකූලතාව (Compliance & Regulations): සමහර Industry වලට, නීතිමය හේතු නිසා data changes track කරන්න වෙනවා (උදා: Financial sector). Auditing කියන්නෙ ඒ අවශ්යතාවය සපුරගන්න තියෙන හොඳම ක්රමයක්.
- ආරක්ෂාව (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! 😉