ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม
แมป @OneToMany, @ManyToMany และ @JoinTable พร้อมประเภทการทำงานต่อเนื่องและกลยุทธ์การดึงข้อมูล
ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน
ประเภทความสัมพันธ์ใน JPA
JPA รองรับความสัมพันธ์สี่ประเภท ได้แก่ @OneToOne, @OneToMany, @ManyToOne และ @ManyToMany แต่ละประเภทจะแมปกับข้อจำกัดของคีย์ต่างประเทศหรือตารางเชื่อมโยง
@ManyToOne: ด้านเจ้าของ
ความสัมพันธ์แบบหลายต่อหนึ่ง (เช่น Order หลายรายการเป็นของ User หนึ่งราย) จะแมปด้วย @ManyToOne และใช้ @JoinColumn สำหรับคีย์นอก
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}@OneToMany: ด้านผกผัน
ด้านผกผัน (User มี Order หลายรายการ) ใช้ @OneToMany(mappedBy = "user") แอตทริบิวต์ mappedBy จะชี้ไปยังฟิลด์บนด้านเจ้าของที่เก็บ FK
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
}ตัวช่วยรักษาความสอดคล้องแบบสองทิศทาง
เมื่อใช้ความสัมพันธ์แบบสองทิศทาง ควรรักษาทั้งสองด้านของความสัมพันธ์ไว้ในเมธอดตัวช่วย เพื่อให้กราฟออบเจ็กต์มีความสอดคล้องกันก่อนส่งข้อมูลลงฐานข้อมูล
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // keep both sides in sync
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null);
}ชนิดการส่งต่อ
CascadeType.ALL จะส่งต่อการดำเนินการทั้งหมด (persist, merge, remove, refresh, detach) จากเอนทิตีแม่ไปยังเอนทิตีลูก ส่วน PERSIST และ MERGE เป็นตัวเลือกที่ปลอดภัยและใช้กันทั่วไป ขณะที่ REMOVE จะลบเอนทิตีลูกเมื่อเอนทิตีแม่ถูกลบ
@OneToMany(mappedBy = "user",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
private List<Order> orders;orphanRemoval: การลบเอนทิตีลูกโดยอัตโนมัติ
orphanRemoval = true จะลบเอนทิตีลูกเมื่อเอนทิตีนั้นถูกนำออกจากคอลเลกชันของเอนทิตีแม่ แม้จะไม่ได้เรียก entityManager.remove() อย่างชัดเจนก็ตาม
user.getOrders().remove(order); // triggers DELETE for the orphaned order on flush@ManyToMany กับ @JoinTable
ความสัมพันธ์แบบหลายต่อหลาย (เช่น Student และรายวิชา) ใช้ตารางเชื่อมโยง ให้กำหนดตารางนี้ที่ด้านเจ้าของด้วย @JoinTable ส่วนด้านผกผันใช้ mappedBy
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>();
}คอลัมน์เพิ่มเติมในตารางเชื่อมโยง
เมื่อตารางเชื่อมโยงต้องมีคอลัมน์เพิ่มเติม (เช่น วันที่ลงทะเบียน) ให้เปลี่ยน @ManyToMany เป็นความสัมพันธ์ @OneToMany / @ManyToOne สองรายการ พร้อมสร้างเอนทิตีใหม่แทนตารางเชื่อมโยง
@Entity
public class Enrollment {
@ManyToOne Student student;
@ManyToOne Course course;
private LocalDate enrolledAt;
}FetchType: LAZY เทียบกับ EAGER
การโหลดข้อมูลเริ่มต้น: @ManyToOne และ @OneToOne เป็น EAGER (โหลดทันที) ส่วน @OneToMany และ @ManyToMany เป็น LAZY (โหลดเมื่อเข้าถึง) ควรใช้ LAZY กับคอลเลกชันเสมอเพื่อหลีกเลี่ยงคิวรี N+1
// Always override to LAZY:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;ปัญหาคิวรี N+1
การเข้าถึงคอลเลกชันแบบ lazy ภายในลูปจะทำให้เกิดคิวรีหนึ่งรายการต่อเอนทิตีหนึ่งรายการ (N+1) แก้ไขได้ด้วย JOIN FETCH ใน JPQL หรือ @EntityGraph เพื่อโหลดคอลเลกชันด้วยคิวรีเดียว
// N+1 problem:
List<User> users = repo.findAll();
users.forEach(u -> u.getOrders().size()); // 1 + N queries!
// Fix:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();Set เทียบกับ List สำหรับคอลเลกชัน
ใช้ Set กับ @ManyToMany เพื่อหลีกเลี่ยงแถวซ้ำในตารางเชื่อมโยง ใช้ List กับ @OneToMany เมื่อจำเป็นต้องรักษาลำดับ ห้ามใช้ List กับ @ManyToMany เพราะ Hibernate จะลบและแทรกแถวทั้งหมดใหม่
ตรวจสอบความเข้าใจอย่างรวดเร็ว
mappedBy ในคำอธิบายประกอบ @OneToMany ระบุอะไร
สรุปทบทวน
ใช้ @ManyToOne+@JoinColumn ที่ด้านเจ้าของ (ด้าน FK) ใช้ @OneToMany(mappedBy=...) ที่ด้านผกผัน ใช้ LAZY กับคอลเลกชันเสมอ แก้ปัญหา N+1 ด้วย JOIN FETCH และใช้เอนทิตีเชื่อมโยงสำหรับความสัมพันธ์แบบหลายต่อหลายที่มีคอลัมน์เพิ่มเติม
คำถามที่พบบ่อย
บทเรียน “ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม”
แมป @OneToMany, @ManyToMany และ @JoinTable พร้อมประเภทการทำงานต่อเนื่องและกลยุทธ์การดึงข้อมูล คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม
ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การแมปเอนทิตีด้วยคำอธิบาย JPA
- รีโพซิทอรี Spring Data และเมธอดค้นหา
- ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม
- การแบ่งหน้า การเรียงลำดับ และโปรเจกชัน