0Pricing
Java Academy · บทเรียน

ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม

แมป @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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การแมปเอนทิตีด้วยคำอธิบาย JPA
  2. รีโพซิทอรี Spring Data และเมธอดค้นหา
  3. ความสัมพันธ์แบบหนึ่งต่อกลุ่มและกลุ่มต่อกลุ่ม
  4. การแบ่งหน้า การเรียงลำดับ และโปรเจกชัน
← กลับไปที่ Java Academy