Spring Boot Soft Deletes: දත්ත මකා නොදමා කළමනාකරණය | SC Guide

Spring Boot Soft Deletes: දත්ත මකා නොදමා කළමනාකරණය | SC Guide

ආයුබෝවන් යාළුවනේ! අද අපි කතා කරන්න යන්නේ Spring Boot projects වල data management වලදී හුඟක් වැදගත් වෙන topic එකක් ගැන. ඒ තමයි Soft Deletes. සාමාන්‍යයෙන් අපි මොකක් හරි data record එකක් database එකෙන් අයින් කරනවා කිව්වම කරන්නේ DELETE SQL query එකක් දාලා ඒක සම්පූර්ණයෙන්ම database එකෙන් අයින් කරන එකනේ. ඒත්, හැමවෙලේම ඒක හොඳ විසඳුමක්ද? සමහර වෙලාවට අපිට කලින් delete කරපු data එකක් ආපහු ඕන වෙන්න පුළුවන්, නැත්තම් ඒ record එක තිබ්බා කියන එක record වෙලා තියෙන්න ඕන වෙන්න පුළුවන්. එතකොට තමයි මේ Soft Deletes කියන concept එක අපිට උදව් වෙන්නේ. අපි බලමු මේක හරියටම මොකක්ද, Spring Boot එක්ක මේක implement කරන්නේ කොහොමද කියලා.

මොකක්ද මේ Soft Delete කියන්නේ?

සරලව කිව්වොත්, Soft Delete කියන්නේ අපි database එකෙන් record එකක් Delete කරන්නේ නැතුව, ඒක ‘deleted’ විදිහට mark කරන එකට. හිතන්නකෝ ඔයාගේ database එකේ user කෙනෙක්ගේ record එකක් තියෙනවා කියලා. ඒ user අයින් කරන්න ඕන වුණාම අපි ඒ record එක database එකෙන් සම්පූර්ණයෙන්ම අයින් කරන්නේ නැතුව, ඒ record එකේ තියෙන is_deleted වගේ column එකක අගය true කරනවා. එතකොට ඒ record එක 'deleted' විදිහට identify කරන්න පුළුවන්. අනිත් query වලට ඒ record එක පෙන්නන්නෙත් නැහැ. ඒත් ඒ record එක තාමත් database එකේ තියෙනවා.

මේකෙන් ප්‍රධාන වාසි දෙකක් තියෙනවා. එකක්, අහම්බෙන් delete වුණ data recover කරන්න පුළුවන්. දෙවෙනි එක, auditing purposes වලට හුඟක් ප්‍රයෝජනවත්. ඒ කියන්නේ කවුද මොන වෙලාවෙද මේ record එක ‘delete’ කළේ කියලා දැනගන්න පුළුවන්. ඒ වගේම, data integrity එකත් හොඳින් පවත්වාගෙන යන්න පුළුවන්, මොකද ඒ record එකට අදාළව තියෙන වෙන relationships කැඩෙන්නේ නැති නිසා.

Soft Delete Implementation එක Spring Boot වලට ගේමු

Spring Boot projects වල, විශේෂයෙන් JPA (Java Persistence API) පාවිච්චි කරනකොට, Soft Delete implement කරන්න ක්‍රම කීපයක් තියෙනවා. අපි සාමාන්‍යයෙන් කරන්නේ entity එකට අලුත් field එකක් add කරන එක. මේ field එක boolean isDeleted කියන එක වෙන්න පුළුවන්, නැත්තම් LocalDateTime deletedAt වගේ timestamp එකක් වෙන්නත් පුළුවන්. deletedAt වගේ එකක් පාවිච්චි කරනවා නම්, ඒක null නම් record එක active, නැත්තම් deleted කියලා තේරුම් ගන්න පුළුවන්. ඒ වගේම කවදාද delete කළේ කියලත් දැනගන්න පුළුවන්.

ඊට පස්සේ, අපි data retrieve කරන හැම තැනකදීම, isDeleted = false (හෝ deletedAt is NULL) කියලා condition එකක් දාන්න ඕන. හැබැයි මේක හැම තැනකම manually කරන්න යන එක හුඟක් මහන්සි වැඩක්. මෙතනට තමයි JPA/Hibernate වල @Where annotation එක වගේ දේවල් අපිට උදව් වෙන්නේ. ඊට අමතරව, @SQLDelete annotation එකත් අපිට delete operations customize කරගන්න ලොකු සහයෝගයක් දෙනවා.

ප්‍රායෝගික උදාහරණයක්: Student Entity එක Soft Delete කරමු

හිතමු අපි Student කියන entity එකක් හදනවා කියලා. මේකේ සාමාන්‍ය fields වලට අමතරව, soft delete කරන්න අවශ්‍ය field එකකුත් add කරමු.

Student Entity එක හදමු

package com.scguide.softdelete.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import java.time.LocalDateTime;

@Entity
@SQLDelete(sql = "UPDATE student SET deleted = true, deleted_at = NOW() WHERE id=?")
@Where(clause = "deleted = false")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    private boolean deleted = Boolean.FALSE; // Default to not deleted
    private LocalDateTime deletedAt; // To track when it was deleted

    // Constructors
    public Student() {
    }

    public Student(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and Setters (omitted for brevity, assume Lombok or manually created)
    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 String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public boolean isDeleted() { return deleted; }
    public void setDeleted(boolean deleted) { this.deleted = deleted; }
    public LocalDateTime getDeletedAt() { return deletedAt; }
    public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }

    @Override
    public String toString() {
        return "Student{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", email='" + email + '\'' +
               ", deleted=" + deleted +
               ", deletedAt=" + deletedAt +
               '}';
    }
}

මේ entity එකේ අපි deleted කියන boolean field එකක් දාලා තියෙනවා. ඒ වගේම deletedAt කියන LocalDateTime field එකත් දාලා තියෙනවා. මේකෙන් record එක delete කරපු වෙලාව track කරන්න පුළුවන්.

  • @SQLDelete(sql = "UPDATE student SET deleted = true, deleted_at = NOW() WHERE id=?") කියන annotation එකෙන් කියන්නේ, අපි Student entity එකේ delete() method එක call කළාම (repository එකෙන් වගේ), JPA එකෙන් සාමාන්‍ය DELETE query එකක් දාන්නේ නැතුව, අපි දීලා තියෙන UPDATE query එක run කරනවා කියන එකයි. ඒ කියන්නේ deleted කියන field එක true කරනවා වගේම deleted_at field එකට වර්තමාන වේලාව (current timestamp) set කරනවා.
  • @Where(clause = "deleted = false") කියන annotation එක තමයි මේ soft delete concept එකේ අපේ වැඩේ ලේසි කරන්නේ. මේකෙන් වෙන්නේ, අපි Student entity එකෙන් data query කරන හැම වෙලාවකම (findById, findAll, findByName වගේ ඕනම method එකකින්), JPA එකෙන් generate කරන SQL query එකට WHERE deleted = false කියන condition එක automatically add කරන එක. ඒ කියන්නේ, අපිට deleted = false කියලා manually දාන්න ඕන වෙන්නේ නැහැ. database එකේ delete වෙලා තියෙන records අපිට පේන්නේ නැහැ. මේක JPA repository methods වලටත් custom JPQL/HQL queries වලටත් apply වෙනවා.

Student Repository එක හදමු

package com.scguide.softdelete.repository;

import com.scguide.softdelete.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

    // These methods will automatically apply the @Where clause (deleted = false)
    List<Student> findAll();
    Optional<Student> findById(Long id);

    // Custom method to find all (including soft-deleted) students for admin purposes
    // We explicitly override the @Where clause for this specific query
    @Query("SELECT s FROM Student s WHERE s.deleted = true")
    List<Student> findAllDeletedStudents();

    // Restore a soft-deleted student (example)
    @Modifying
    @Transactional
    @Query("UPDATE Student s SET s.deleted = false, s.deletedAt = null WHERE s.id = ?1")
    void restoreStudent(Long id);

    // You can also add methods to find students by other criteria
    List<Student> findByName(String name);
}

repository එකේදි, findAll() වගේ සාමාන්‍ය methods වලට @Where clause එක automatically apply වෙනවා. ඒ කියන්නේ ඒවා return කරන්නේ delete නොකරපු records විතරයි.

ඒත්, අපිට delete කරපු records ටික බලන්න ඕන නම්, findAllDeletedStudents() වගේ custom query එකක් ලියන්න පුළුවන්. මෙතනදි අපි @Where clause එක override කරලා deleted = true කියන condition එක පාවිච්චි කරනවා. ඒ වගේම restore කරන්නත් restoreStudent() වගේ @Modifying සහ @Transactional annotation යොදාගෙන method එකක් ලියන්න පුළුවන්.

Student Service එක හදමු

package com.scguide.softdelete.service;

import com.scguide.softdelete.entity.Student;
import com.scguide.softdelete.repository.StudentRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
public class StudentService {

    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public List<Student> getAllStudents() {
        return studentRepository.findAll(); // Only non-deleted students due to @Where
    }

    public Optional<Student> getStudentById(Long id) {
        return studentRepository.findById(id); // Only non-deleted student if exists
    }

    @Transactional
    public void softDeleteStudent(Long id) {
        // Option 1: Directly call deleteById() if @SQLDelete is configured on the entity
        studentRepository.deleteById(id); // This will trigger the SQLDelete update query

        // Option 2: Manually update and save (less common when @SQLDelete is used, but useful if no @SQLDelete)
        /*
        Optional<Student> studentOptional = studentRepository.findById(id);
        studentOptional.ifPresent(student -> {
            student.setDeleted(true);
            student.setDeletedAt(LocalDateTime.now());
            studentRepository.save(student);
        });
        */
    }

    public List<Student> getAllDeletedStudents() {
        return studentRepository.findAllDeletedStudents();
    }

    @Transactional
    public void restoreStudent(Long id) {
        studentRepository.restoreStudent(id);
    }

    public Student saveStudent(Student student) {
        // When saving a new student, ensure 'deleted' is false and 'deletedAt' is null
        student.setDeleted(false);
        student.setDeletedAt(null);
        return studentRepository.save(student);
    }
}

Service layer එකේදි, softDeleteStudent method එක ඇතුලේ අපි studentRepository.deleteById(id) call කරනවා. මෙතනදි මතක තියාගන්න ඕන දෙයක් තමයි, අපි මේ deleteById call කළත්, @SQLDelete annotation එක නිසා සාමාන්‍ය DELETE query එක run වෙන්නේ නැතුව, අපේ custom UPDATE query එක run වෙනවා. ඒකෙන් entity එකේ deleted field එක true කරලා deletedAt එකත් set කරනවා. මේ නිසා, අපිට findById වගේ methods වලින් data retrieve කරද්දි deleted students ලා පෙන්නන්නේ නැහැ.

Soft Delete එක්ක එන වාසි අවාසි

Soft Delete කියන්නේ ගොඩක් හොඳ විසඳුමක් වුණාට, හැමදේකම වගේ මේකෙත් වාසි වගේම අවාසිත් තියෙනවා.

වාසි (Pros):

  • දත්ත නැවත ලබාගැනීම (Data Recovery): අහම්බෙන් record එකක් ‘delete’ වුණාම ලේසියෙන්ම ආයෙත් active කරන්න පුළුවන්. restoreStudent() වගේ method එකක් පාවිච්චි කරලා ඉක්මනින්ම data recovery කරන්න පුළුවන්.
  • Auditing සහ Data Integrity (Auditing & Data Integrity): Record එකක් තිබ්බා කියන එකේ ඉතිහාසය maintain වෙනවා. ඒ වගේම ඒ record එකට අදාළව තියෙන වෙන relationships කැඩෙන්නේ නැහැ. Foreign Key constraints වගේ ඒවාට Soft Delete එකක් බලපාන්නේ නැහැ.
  • Legal & Compliance (නීතිමය අවශ්‍යතා): සමහර අවස්ථාවල data record එකක් database එකෙන් සම්පූර්ණයෙන්ම අයින් කරන්න බැහැ (e.g., financial transactions, user activity logs). ඒ වගේ වෙලාවට soft delete එක හොඳ විසඳුමක්.
  • User Experience (පරිශීලක අත්දැකීම): සමහර වෙලාවට user කෙනෙක්ට තමන්ගේ account එක 'deactivate' කරන්න අවශ්‍ය වෙන්න පුළුවන්, සම්පූර්ණයෙන්ම delete කරන්නේ නැතුව. Soft delete මේ වගේ scenarios වලටත් හොඳටම ගැලපෙනවා.

අවාසි (Cons):

  • වඩා වැඩි සංකීර්ණ බව (Increased Complexity): Entity, Repository, Service layers වලට අමතර logic එකතු කරන්න වෙනවා. Developers ලා හැමෝම මේ concept එක ගැන දැනුවත් වෙන්න ඕන.
  • කාර්ය සාධනයට බලපෑම් (Performance Impact): deleted column එකට index එකක් දාලා නැත්නම්, query performance එකට බලපෑම් වෙන්න පුළුවන්, මොකද හැම query එකකම WHERE deleted = false වගේ condition එකක් apply වෙන නිසා. deleted field එකට index එකක් අනිවාර්යයෙන්ම දාන්න!
  • Database Space Usage (Database Space Usage): Delete කරපු records වුණත් database එකේ තියෙන නිසා, database එකේ size එක වැඩි වෙන්න පුළුවන්. කාලයක් යද්දි මේක ප්‍රශ්නයක් වෙන්න පුළුවන්. එතකොට අපිට පුළුවන් archived records clean-up කරන background process එකක් implement කරන්න, නැත්තම් ඉතා පැරණි soft-deleted records වෙන archive table එකකට move කරන්න.
  • Query Complexity (Query Complexity): සමහර වෙලාවට, soft-deleted recordsත් එක්ක වැඩ කරන්න අවශ්‍ය වුණොත් (e.g., admin panels), query ලියන එක ටිකක් සංකීර්ණ වෙන්න පුළුවන්. ඒ වගේම, join operations වලදීත් මේ @Where clause එක නිසි ලෙස ක්‍රියාත්මක වෙනවද කියලා සැලකිලිමත් වෙන්න ඕන.

ඉදිරියට මොනවද කරන්න පුළුවන්?

අපි මේ කතා කරපු ක්‍රමය හොඳ වුණත්, ලොකු projects වලට තව ටිකක් advanced දේවල් ගැන හිතන්න පුළුවන්. මේවා ඔයාගේ project එකේ අවශ්‍යතාවය අනුව යොදාගන්න පුළුවන්.

  • Global Filters (Hibernate Filters): අපි දැක්කා @Where annotation එකෙන් තනි entity එකකට filter එකක් දාන හැටි. ඒත්, ඔයාගේ system එකේ entity ගොඩක් තියෙනවා නම්, හැම එකටම @Where දාන්නේ නැතුව, Hibernate Global Filter එකක් හදන්න පුළුවන්. ඒකෙන් පුළුවන් හැම entity එකකම isDeleted field එකට globally filter එකක් apply කරන්න. මේක runtime එකේදී enable/disable කරන්නත් පුළුවන්.
  • Spring Data JPA Auditing: deletedAt field එකට value එක set කරන එක manually කරන්නේ නැතුව @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy වගේ @EntityListeners එක්ක Spring Data JPA Auditing feature එක පාවිච්චි කරන්න පුළුවන්. මේකෙන් who, when, what changes were made කියන එක track කරන්න පුළුවන්. Soft delete කරන අවස්ථාවෙත් මේ Auditing features යොදාගන්න පුළුවන්.

Custom Base Entity: හැම entity එකකටම deleted field එක manually add කරන්නේ නැතුව, BaseEntity එකක් හදලා ඒක extend කරන්න පුළුවන්. මේ BaseEntity එකට @MappedSuperclass annotation එක දාලා id, deleted, deletedAt වගේ common fields ටික දාන්න පුළුවන්. එතකොට අලුත් entity හදන හැම වෙලාවෙම මේ fields add කරන්න අවශ්‍ය වෙන්නේ නැහැ.

@MappedSuperclass
public abstract class BaseSoftDeleteEntity {
    // ... id field ...
    private boolean deleted = Boolean.FALSE;
    private LocalDateTime deletedAt;

    // Getters and Setters
}

එතකොට ඔයාගේ Student Entity එකට මේ BaseSoftDeleteEntity එක extend කරන්න පුළුවන්:

@Entity
@SQLDelete(sql = "UPDATE student SET deleted = true, deleted_at = NOW() WHERE id=?")
@Where(clause = "deleted = false")
public class Student extends BaseSoftDeleteEntity {
    // ... other fields like name, email ...
}

නිගමනය

ඉතින් යාළුවනේ, මේ Soft Delete concept එක Spring Boot එක්ක implement කරන එක ඔයාලට පැහැදිලි වෙන්න ඇති කියලා හිතනවා. data integrity එකට, auditing වලට වගේම අහම්බෙන් data loss වෙන එක වළක්වාගන්නත් මේක හුඟක් ප්‍රයෝජනවත්. ඔයාලගේ project එකට මේක ගැළපෙනවද නැද්ද කියලා හිතලා බලන්න. මතක තියාගන්න, හැම solution එකක්ම හැම problem එකටම ගැළපෙන්නේ නැහැ. ඔයාගේ project එකේ අවශ්‍යතා අනුව තමයි හොඳම විසඳුම තෝරාගන්න ඕන.

ඔයාලගේ අත්දැකීම්, ප්‍රශ්න, අදහස් comment section එකේ කියන්න අමතක කරන්න එපා. මේක ඔයාලගේ projects වලට implement කරලා බලන්න, එහෙනම් තවත් අලුත් දෙයක් එක්ක ඉක්මනින්ම හම්බවෙමු! ජය වේවා!