Entity Relationships: @OneToMany, @ManyToOne, @ManyToMany - SC ගයිඩ්

Entity Relationships: @OneToMany, @ManyToOne, @ManyToMany - SC ගයිඩ්

ආයුබෝවන්, අපේ Software Crew (SC) එකට සම්බන්ධ වෙච්ච හැමෝටම!

අපි අද කතා කරන්න යන්නේ ගොඩක් වැදගත්, හැබැයි මුලින් ටිකක් ව්‍යාකූල වෙන්න පුළුවන් මාතෘකාවක් ගැන – ඒ තමයි Entity Relationships. විශේෂයෙන්ම, @OneToMany, @ManyToOne, @ManyToMany කියන mapping ගැනයි අපි බලන්න යන්නේ. මේක හරියට database එකක් design කරනවා වගේම, අපේ code එකත් organize කරන්න අත්‍යවශ්‍ය දෙයක්. Java වල JPA (Java Persistence API) වගේ framework පාවිච්චි කරනකොට මේවා හරියටම තේරුම් ගන්න එක ගොඩක් වැදගත්.

ඇයි මේ Entity Relationships අපිට වැදගත් වෙන්නේ?

හිතන්නකෝ, ඔයා online shopping site එකක් හදනවා කියලා. මේකේ users ඉන්නවා, orders තියෙනවා, products තියෙනවා. මේ හැම එකක්ම database එකේ වෙන වෙනම tables විදියට තියෙන්නේ. හැබැයි, මේවා අතර සම්බන්ධතා තියෙනවා නේද? උදාහරණයක් විදියට, එක user කෙනෙක්ට orders ගොඩක් දාන්න පුළුවන්. ඒ වගේම, එක order එකකට product ගොඩක් තියෙන්න පුළුවන්. මේ වගේ සබඳතා database එකේ විතරක් නෙවෙයි, අපේ Java code එකෙත් represent කරන්න ඕනේ. එතනට තමයි මේ Entity Relationships එන්නේ.

මේවා හරියට manage කරගත්තොත් අපේ application එකේ data consistency එක රැකෙනවා, code maintain කරන්න ලේසි වෙනවා, ඒ වගේම performance එකත් improve කරගන්න පුළුවන් වෙනවා.

@OneToMany සහ @ManyToOne: එක කාසියේ දෙපැත්ත වගේ

මේ දෙක ගොඩක් වෙලාවට එකටම යන mappings. ඇත්තටම, මේවා එකම relationship එකක දෙපැත්ත විතරයි. අපි User කෙනෙක්ට Order ගොඩක් දාන්න පුළුවන් උදාහරණයෙන්ම බලමු.

@OneToMany (එකකට ගොඩක්)

මේක තියෙන්නේ “එක” පැත්තට. ඒ කියන්නේ, අපේ උදාහරණයට අනුව, User entity එකේ. එක User කෙනෙක්ට Order ගොඩක් තියෙන්න පුළුවන් නිසා, User entity එක ඇතුලේ Order entities වල List එකක් තියෙනවා කියලා අපි specify කරනවා.

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    // 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 String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public List<Order> getOrders() { return orders; }
    public void setOrders(List<Order> orders) { this.orders = orders; }

    // Helper methods to manage the bidirectional relationship
    public void addOrder(Order order) {
        orders.add(order);
        order.setUser(this);
    }

    public void removeOrder(Order order) {
        orders.remove(order);
        order.setUser(null);
    }
}

මෙහිදී, mappedBy = "user" කියන්නේ User entity එකේ orders list එක Order entity එකේ user field එකෙන් mapped වෙලා තියෙනවා කියන එකයි. cascade = CascadeType.ALL කියන්නේ User object එකකට මොකක් හරි operation එකක් (save, update, delete) කරනකොට, ඒකට අදාළ Order objects වලටත් ඒ operation එක apply වෙනවා කියන එකයි. orphanRemoval = true කියන්නේ Order එකක් User ගෙන් remove කලොත්, ඒ Order එක database එකෙනුත් delete වෙනවා කියන එක.

@ManyToOne (ගොඩකට එකක්)

මේක තියෙන්නේ “ගොඩක්” පැත්තට. ඒ කියන්නේ, අපේ උදාහරණයට අනුව, Order entity එකේ. එක Order එකක් අයිති වෙන්නේ එක User කෙනෙක්ට විතරක් නිසා, Order entity එක ඇතුලේ User object එකක් තියෙනවා කියලා අපි specify කරනවා.

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private LocalDateTime orderDate;
    private double totalAmount;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public LocalDateTime getOrderDate() { return orderDate; }
    public void setOrderDate(LocalDateTime orderDate) { this.orderDate = orderDate; }
    public double getTotalAmount() { return totalAmount; }
    public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; }
    public User getUser() { return user; }
    public void setUser(User user) { this.user = user; }
}

මෙහිදී, @ManyToOne annotation එකෙන් කියන්නේ මේ Order object එක User object එකක් එක්ක සම්බන්ධයි කියලා. @JoinColumn(name = "user_id", nullable = false) කියන්නේ database එකේ orders table එකේ user_id කියන foreign key column එක මේ User entity එකට link වෙනවා කියන එක. nullable = false කියන්නේ හැම Order එකකටම අනිවාර්යයෙන්ම User කෙනෙක් ඉන්න ඕනේ කියන එක.

fetch = FetchType.LAZY කියන්නේ Order එකක් load කරනකොට, ඒකට අදාළ User object එක load කරන්නේ අවශ්‍ය වුණොත් විතරයි කියන එකයි. (EAGER කියන්නේ හැම වෙලාවෙම එකටම load කරනවා කියන එක. ගොඩක් වෙලාවට LAZY තමයි හොඳ performance එකකට.)

@ManyToMany: ගොඩකට ගොඩක්

මේක තියෙන්නේ දෙපැත්තටම ගොඩක් entities සම්බන්ධ වෙන අවස්ථාවලට. උදාහරණයක් විදියට, එක student කෙනෙක්ට courses ගොඩක් ගන්න පුළුවන්, ඒ වගේම එක course එකකට students ගොඩක් ඉන්න පුළුවන්. මේ වගේ situation එකකදී කෙලින්ම students table එකයි courses table එකයි join කරන්න බෑ. ඒකට මැද්දට තව join table එකක් ඕනේ වෙනවා.

import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "students")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course", // Join table name
        joinColumns = @JoinColumn(name = "student_id"), // Foreign key for Student
        inverseJoinColumns = @JoinColumn(name = "course_id") // Foreign key for Course
    )
    private Set<Course> courses = new HashSet<>();

    // Getters and Setters and helper methods
    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 Set<Course> getCourses() { return courses; }
    public void setCourses(Set<Course> courses) { this.courses = courses; }

    public void addCourse(Course course) {
        this.courses.add(course);
        course.getStudents().add(this);
    }

    public void removeCourse(Course course) {
        this.courses.remove(course);
        course.getStudents().remove(this);
    }
}

@Entity
@Table(name = "courses")
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;

    @ManyToMany(mappedBy = "courses") // mappedBy is used on the inverse side
    private Set<Student> students = new HashSet<>();

    // Getters and Setters and helper methods
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public Set<Student> getStudents() { return students; }
    public void setStudents(Set<Student> students) { this.students = students; }
}

මෙහිදී, Student entity එකේ @JoinTable annotation එකෙන් කියන්නේ student_course කියන මැදිහත් table එක පාවිච්චි කරලා මේ සම්බන්ධතාවය හදනවා කියන එකයි. joinColumns එකෙන් student_course table එකේ student_id column එක මේ Student entity එකට connect කරනවා, inverseJoinColumns එකෙන් course_id column එක Course entity එකට connect කරනවා.

Course entity එකේ @ManyToMany(mappedBy = "courses") එකෙන් කියන්නේ මේ mapping එක Student entity එකේ courses field එකෙන් already mapped වෙලා තියෙනවා කියන එකයි. @ManyToMany relationship එකකදී, එක් පැත්තක (සාමාන්‍යයෙන් owning side එකේ) @JoinTable තියෙනවා, අනිත් පැත්ත mappedBy කරනවා.

ගොඩක් වෙලාවට @ManyToMany direct විදියට පාවිච්චි කරනවට වඩා, මැද්දට තව entity එකක් (association table with attributes) දාලා @OneToMany / @ManyToOne relationships දෙකක් විදියට handle කරන එක තමයි හොඳම practice එක. මොකද එතකොට association එකට අදාළ වෙනත් attributes (ex: enrollment date for student-course) එකතු කරන්න පුළුවන්.

ප්‍රායෝගික උපදෙස් සහ හොඳම භාවිතයන් (Best Practices)

  • Bidirectional vs. Unidirectional:
    • Unidirectional කියන්නේ එක් පැත්තකින් විතරක් relationship එක define කරන එක. මේක සරලයි, හැබැයි සමහර වෙලාවට අපිට දෙපැත්තටම data access කරන්න ඕනේ වෙනවා.
    • Bidirectional කියන්නේ දෙපැත්තටම relationship එක define කරන එක (අපි User සහ Order උදාහරණයේදී වගේ). මේක ටිකක් සංකීර්ණ වුණත්, ගොඩක් වෙලාවට වැඩේට පහසුයි. මතක තියාගන්න, bidirectional නම් mappedBy හරියට පාවිච්චි කරන්න ඕනේ. Owning side එක තමයි foreign key එක manage කරන්නේ.
  • Fetch Types (LAZY vs. EAGER):
    • FetchType.LAZY තමයි ගොඩක් වෙලාවට recommend කරන්නේ. ඒකෙන් application එකේ performance එක improve වෙනවා. (@OneToMany, @ManyToMany වල default එක LAZY. @ManyToOne, @OneToOne වල default එක EAGER.)
    • FetchType.EAGER පාවිච්චි කරන්නේ අනිවාර්යයෙන්ම ඒ linked entity එකත් එක්කම load වෙන්න ඕනේ නම් විතරයි. නැත්නම් N+1 query problem වගේ ප්‍රශ්න එන්න පුළුවන්.
  • Cascade Types:
    • CascadeType.ALL කියන්නේ හැම operation එකක්ම cascade කරනවා කියන එක. ප්‍රවේශමෙන් පාවිච්චි කරන්න, මොකද අනවශ්‍ය deletes වෙන්න පුළුවන්.
    • CascadeType.PERSIST (save), CascadeType.MERGE (update), CascadeType.REMOVE (delete) වගේ specific cascade types පාවිච්චි කරන්න පුළුවන්.
  • @ManyToMany වලට මැදිහත් Entity එකක්:
    • @ManyToMany direct පාවිච්චි කරනවට වඩා, මැද්දට join entity එකක් දාලා @OneToMany / @ManyToOne relationships දෙකක් විදියට handle කරන එක ගොඩක් වෙලාවට හොඳයි. මේකෙන් future requirements වලට (like adding attributes to the relationship) ready වෙන්න පුළුවන්.

නිගමනය

Entity Relationships කියන්නේ database modeling වල වගේම ORM (Object-Relational Mapping) framework පාවිච්චි කරනකොටත් ගොඩක් වැදගත් concept එකක්. @OneToMany, @ManyToOne, @ManyToMany හරියට තේරුම් අරගෙන, ඒවා නිවැරදිව පාවිච්චි කරන එකෙන් ඔයාගේ application එකේ data integrity එක, maintainability එක, සහ performance එක ගොඩක් improve කරගන්න පුළුවන්.

ඔයාලා දැන් මේ concepts තේරුම් අරගෙන ඇති කියලා හිතනවා. පුළුවන් නම්, පොඩි Spring Boot project එකක් හදලා මේ User සහ Order relationship එක implement කරලා බලන්න. User කෙනෙක්ව save කරන්න, Order එකක් save කරන්න, User කෙනෙක්ගේ Orders load කරලා බලන්න. එතකොට මේක තවත් හොඳට තේරෙයි.

කිසියම් ප්‍රශ්නයක් තියෙනවා නම්, නැත්නම් මේ ගැන තවත් අලුත් දෙයක් එකතු කරන්න තියෙනවා නම්, පහළින් comment එකක් දාගෙන යන්න. අපි එකට කතා කරමු!

මතක තියාගන්න, coding කියන්නේ ඉගෙන ගන්න එක. තව අලුත් දේවල් එක්ක අපි ඉක්මනින්ම හම්බවෙමු! තෙරුවන් සරණයි!