[TIL] 다대다 관계( @ManyToMany | @ManyToOne @OneToMany)

냠냠빈·2024년 12월 17일

JPA에서 다대다 관계 이해하기

데이터베이스에서 다대다(Many-to-Many) 관계는 두 엔티티가 서로 여러 개의 관계를 맺을 때 사용됩니다.

JPA에서는 이를 표현하기 위해 @ManyToMany를 기본적으로 지원하지만, 실무에서는 @OneToMany@ManyToOne 조합을 활용한 중간 테이블(Entity) 방식을 더 많이 사용합니다.

오늘은 그 이유와 해결법을 알아보겠습니다.


1️⃣ @ManyToMany의 기본 개념

@ManyToMany는 두 테이블 간의 중간 테이블을 자동으로 생성하고 관리합니다.

기본 구조 예시

  • 학생(Student): 여러 수업(Class)을 수강할 수 있음.
  • 수업(Class): 여러 학생(Student)이 수강할 수 있음.

ERD 다이어그램

Student ──────┐
              | (자동 생성) student_class (중간 테이블)
Class   ──────┘

예시 코드

1) Student 엔티티

@Entity
public class Student {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany
    @JoinTable(name = "student_class",
               joinColumns = @JoinColumn(name = "student_id"),
               inverseJoinColumns = @JoinColumn(name = "class_id"))
    private List<Class> classes = new ArrayList<>();
}

2) Class 엔티티

@Entity
public class Class {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "classes")
    private List<Student> students = new ArrayList<>();
}

2️⃣ @ManyToMany의 한계

@ManyToMany는 중간 테이블을 자동으로 생성해 편리하지만 양방향 매핑되어있을 때 하이버네이트에 의해 생성된 중간 테이블은 관계 설정에 필수적으로 필요한 정보들만 담겨있을 뿐 비즈니스 로직상 필요한 정보들은 담기지 않습니다. 또한 불필요한 정보들도 자동으로 생성된 중간 테이블에 담길 수 있습니다. 따라서 @ManyToMany 보단 중간테이블에 대한 클래스를 직접 만들어 @ManyToOne@OneToMany의 조합을 만드는 것이 좋습니다.

  1. 비즈니스 로직이 필요한 필드 추가 불가
  • 중간 테이블에 추가적인 필드(등록 날짜, 상태 값 등)를 넣을 수 없습니다.
  1. 유연한 관리가 어려움
  • 복잡한 쿼리나 조건이 필요한 경우 유지보수에 제약이 발생합니다.

3️⃣ @OneToMany와 @ManyToOne 조합의 장점

실무에서는 중간 테이블을 엔티티로 직접 생성하여 @OneToMany@ManyToOne을 조합하는 방식을 권장합니다.

장점

  1. 중간 엔티티에 비즈니스 로직 필드 추가 가능
  • 추가 필드(예: 등록 날짜, 권한 등)를 자유롭게 넣을 수 있습니다.
  1. 유연한 관계 표현
  • 복잡한 다대다 관계 및 다양한 조건을 구현할 수 있습니다.
  1. 명확한 구조
    중간 엔티티를 통해 각 테이블 간의 관계가 더 명확하게 표현됩니다.

4️⃣ @OneToMany와 @ManyToOne을 사용한 다대다 관계 예시

ERD 다이어그램

Student ── Enrollment ── Class
  • Enrollment: 중간 엔티티로 학생과 수업의 연결을 담당하며 추가 필드를 포함

예시 코드

1) Student 엔티티

@Entity
public class Student {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
    private List<Enrollment> enrollments = new ArrayList<>();
}

2) Class 엔티티

@Entity
public class Class {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "clazz", cascade = CascadeType.ALL)
    private List<Enrollment> enrollments = new ArrayList<>();
}

3) Enrollment (중간 엔티티)

@Entity
public class Enrollment {
    @Id @GeneratedValue
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "class_id")
    private Class clazz;

    private LocalDate enrollmentDate; // 수강 신청 날짜
    private String status;            // 수강 상태 (예: ACTIVE, COMPLETED)
}

5️⃣ 다대다 관계 작성 시 주의사항

1. 자동 생성 테이블 사용 여부

  • 간단한 관계에서는 @ManyToMany를 사용해도 되지만, 비즈니스 로직이 추가될 가능성이 있는 경우 중간 엔티티를 만들어 사용하는 것이 좋습니다.
    2. 성능과 쿼리 최적화
  • N+1 문제: fetch 전략을 잘 설정해야 합니다. (@ManyToMany는 기본적으로 Lazy로 설정됨)
  • Fetch Join이나 Batch Fetch를 활용해 성능을 개선하세요.
    3. 일관성 유지
  • 중간 엔티티를 사용할 때, 양방향 관계를 설정하고 mappedBy를 명확히 설정해야 합니다.

6️⃣ 정리: 실무에서의 다대다 관계 구현

기법특징사용 시기
@ManyToMany중간 테이블 자동 생성, 필드 추가 불가단순한 다대다 관계일 때
@OneToMany + @ManyToOne중간 엔티티에 비즈니스 로직 필드 추가 가능비즈니스 필드 추가, 유연한 관리 필요한 복잡한 관계일때

참고 블로그

@ManyToMany를 사용하면 안 되는 이유
@ManyToMany, @OneToMany, @ManyToOne관계 작성하기
[JPA] 다대다 N : N 관계 풀어내기 (중간 테이블 생성)


profile
다 먹어버릴거야!

0개의 댓글