Home > Spring > πŸƒ[Spring] JPA 연관관계에 λŒ€ν•œ 좔가적인 κΈ°λŠ₯λ“€μ—λŠ” 무엇이 μžˆμ„κΉŒμš”? - N:M 관계

πŸƒ[Spring] JPA 연관관계에 λŒ€ν•œ 좔가적인 κΈ°λŠ₯λ“€μ—λŠ” 무엇이 μžˆμ„κΉŒμš”? - N:M 관계
Spring Framework

πŸƒ[Spring] JPA 연관관계에 λŒ€ν•œ 좔가적인 κΈ°λŠ₯λ“€μ—λŠ” 무엇이 μžˆμ„κΉŒμš”? - N:M 관계

  • JPAμ—μ„œ N:M κ΄€κ³„λž€ 두 μ—”ν‹°ν‹°κ°€ λ‹€λŒ€λ‹€(Many-to-Many)둜 μ—°κ²°λœ 관계λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.
    • 예λ₯Ό λ“€μ–΄, 학생(Student)κ³Ό κ°•μ˜(Course) κ΄€κ³„μ—μ„œ ν•˜λ‚˜μ˜ 학생이 μ—¬λŸ¬ κ°•μ˜λ₯Ό μˆ˜κ°•ν•  수 있고, λ™μ‹œμ— ν•˜λ‚˜μ˜ κ°•μ˜μ— μ—¬λŸ¬ 학생이 μˆ˜κ°•ν•  수 μžˆλŠ” κ²½μš°μ— N:M 관계가 μ„±λ¦½λ©λ‹ˆλ‹€.

1️⃣ N:M κ΄€κ³„μ˜ 맀핑 방식.

  • JPAμ—μ„œλŠ” @ManyToMany μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ N:M 관계λ₯Ό 맀핑할 수 μžˆμŠ΅λ‹ˆλ‹€.
    • N:M κ΄€κ³„μ—μ„œλŠ” μ—°κ²° ν…Œμ΄λΈ”(Join Table)이 ν•„μš”ν•˜λ©°, JPAλŠ” μ—°κ²° ν…Œμ΄λΈ”μ„ μžλ™μœΌλ‘œ μƒμ„±ν•˜κ±°λ‚˜ κ°œλ°œμžκ°€ 직접 μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

2️⃣ N:M κ΄€κ³„μ˜ κΈ°λ³Έ 맀핑.

  • N:M κ΄€κ³„λŠ” λ‹¨μˆœνžˆ @ManyToMany μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λŠ” 방식과, μ—°κ²° ν…Œμ΄λΈ”μ„ μ—”ν‹°ν‹°λ‘œ λΆ„λ¦¬ν•˜μ—¬ μ‚¬μš©ν•˜λŠ” 방식 두 가지가 μžˆμŠ΅λ‹ˆλ‹€.

1️⃣ 기본적인 @ManyToMany 맀핑

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "student_course", // μ—°κ²° ν…Œμ΄λΈ” 이름
        joinColumns = @JoinColumn(name = "student_id"), // ν˜„μž¬ μ—”ν‹°ν‹°(Student)의 μ™Έλž˜ ν‚€
        inverseJoinColumns = @JoinColumn(name = "course_id") // λ°˜λŒ€ μ—”ν‹°ν‹°(Course)의 μ™Έλž˜ ν‚€
    )
    private List<Course> courses = new ArrayList<>();
}

@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GeneratioonType.IDENTITY)
    private Long id;
    
    private String title;
    
    @ManyToMany(mappedBy = "course") // λ°˜λŒ€μͺ½ μ—”ν‹°ν‹°μ™€μ˜ μ–‘λ°©ν–₯ 관계 μ„€μ •.
    private List<Student> students = new ArrayList<>();
}

πŸ‘‰ μ„€λͺ….

  • @ManyToMany μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄ Student와 Course μ—”ν‹°ν‹° κ°„μ˜ N:M 관계λ₯Ό λ§€ν•‘ν•©λ‹ˆλ‹€.
  • @JoinTable을 μ‚¬μš©ν•˜μ—¬ μ—°κ²° ν…Œμ΄λΈ”(student_course)을 μ§€μ •ν•˜κ³ , 각 μ—”ν‹°ν‹°μ˜ μ™Έλž˜ ν‚€λ₯Ό joinColumns와 inverseJoinColumns으둜 μ„€μ •ν•©λ‹ˆλ‹€.
  • Student와 Course κ°„μ˜ μ–‘λ°©ν–₯ κ΄€κ³„λ‘œ μ„€μ •λ˜μ—ˆμœΌλ©°, mappedByλ₯Ό μ‚¬μš©ν•΄ λ°˜λŒ€μͺ½μ˜ 맀핑 ν•„λ“œλ₯Ό μ§€μ •ν•©λ‹ˆλ‹€.

3️⃣ μ—°κ²° μ—”ν‹°ν‹°λ₯Ό μ‚¬μš©ν•œ N:M 맀핑.

  • @ManyToMany κ΄€κ³„λŠ” κ°„λ‹¨ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.
    • 관계가 λ³΅μž‘ν•˜κ±°λ‚˜ 좔가적인 속성이 ν•„μš”ν•œ 경우 μ—°κ²° ν…Œμ΄λΈ”μ„ λ³„λ„μ˜ μ—”ν‹°ν‹°λ‘œ λΆ„λ¦¬ν•˜μ—¬ N:1 및 1:N κ΄€κ³„λ‘œ λ§€ν•‘ν•˜λŠ” 방식이 μ„ ν˜Έλ©λ‹ˆλ‹€.
      • 예λ₯Ό λ“€μ–΄, 학생과 κ°•μ˜ 사이에 학점(Grade)κ³Ό 같은 속성이 ν•„μš”ν•˜λ‹€λ©΄, μ—°κ²° 엔티티인 Enrollmentλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
        ```java
        @Entity
        public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

    private String name;

    @OneToMany(mappedBy = β€œstudent”)
    private List enrollments = new ArrayList<>();

    // getter, setter
    }

@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;

@OneToMany(mappedBy = "course")
private List<Enrollment> enrollments = new ArrayList<>();

// getter, setter }

@Entity
public class Enrollment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String grade;

@ManyToOne
@JoinColumn(name = "student_id")
private Student student;

@ManyToOne
@JoinColumn(name = "course_id")
private Course course;

// getter, setter } ```

πŸ‘‰ μ„€λͺ….

  • Enrollment μ—”ν‹°ν‹°λ₯Ό μΆ”κ°€ν•˜μ—¬ Student와 Course κ°„μ˜ 연결을 ν‘œν˜„ν•©λ‹ˆλ‹€.
    • 이제 Student와 CourseλŠ” Enrollment와 N:1 관계λ₯Ό κ°€μ§‘λ‹ˆλ‹€.
  • 이 방식은 μ—°κ²° 엔티티에 좔가적인 ν•„λ“œ(예: grade)λ₯Ό 포함할 수 μžˆμ–΄ μœ μ—°ν•©λ‹ˆλ‹€.

4️⃣ N:M 관계 μ‚¬μš© μ‹œ μ£Όμ˜μ‚¬ν•­.

  • 직접적인 @ManyToMany κ΄€κ³„λŠ” λ‹¨μˆœν•œ κ΄€κ³„μ—μ„œλ§Œ μ‚¬μš©ν•˜λ©°, λ³΅μž‘ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­μ΄ μžˆλŠ” 경우 μ—°κ²° ν…Œμ΄λΈ”μ„ μ—”ν‹°ν‹°λ‘œ λ§Œλ“€μ–΄ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
  • N:M κ΄€κ³„λŠ” 쑰인 ν…Œμ΄λΈ”μ„ μ΄μš©ν•˜λ―€λ‘œ, λ°μ΄ν„°μ˜ 양이 λ§Žμ•„μ§€λ©΄ μ„±λŠ₯ μ €ν•˜λ₯Ό μΌμœΌν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.
    • 지연 λ‘œλ”©(Lazy Loading) 섀정을 톡해 μ„±λŠ₯을 μ΅œμ ν™”ν•©λ‹ˆλ‹€.
  • λ³΅μž‘ν•œ κ΄€κ³„μ—μ„œμ˜ Cascade μ˜΅μ…˜μ€ μ‹ μ€‘ν•˜κ²Œ μ„€μ •ν•΄μ•Ό λΆˆν•„μš”ν•œ μ—°κ΄€ μ—”ν‹°ν‹°μ˜ μ €μž₯/μ‚­μ œλ₯Ό 방지할 수 μžˆμŠ΅λ‹ˆλ‹€.

5️⃣ μš”μ•½.

  • N:M κ΄€κ³„λŠ” @ManyToMany μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ‰½κ²Œ 맀핑할 수 μžˆμ§€λ§Œ, 좔가적인 속성이 ν•„μš”ν•˜κ±°λ‚˜ 관계가 λ³΅μž‘ν•΄μ§€λ©΄ μ—°κ²° ν…Œμ΄λΈ”μ„ 별도 μ—”ν‹°ν‹°λ‘œ λΆ„λ¦¬ν•˜μ—¬ μ²˜λ¦¬ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.
    • μ—°κ²° μ—”ν‹°ν‹°λ₯Ό λΆ„λ¦¬ν•˜λ©΄ μΆ”κ°€ 속성을 포함할 수 있으며, 더 μœ μ—°ν•œ 데이터 λͺ¨λΈλ§μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.