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 කියන්නේ ඉගෙන ගන්න එක. තව අලුත් දේවල් එක්ක අපි ඉක්මනින්ම හම්බවෙමු! තෙරුවන් සරණයි!