📖 N:M 연관관계란?
- 두 엔티티가 서로 다수의 관계를 맺는 경우 →
@ManyToMany
📚 예시
Tutor는 여러 개의 Language를 사용할 수 있음Language도 여러 Tutor가 사용할 수 있음📦 객체 관점

Tutor(1..N) ◀───▶ Language(1..N)
Collection을 사용하여 N:M 설정이 가능함Tutor가 여러 개의 Language를 사용할 수 있음🗄️ 데이터베이스 관점

관계형 DB는 직접적인 N:M 관계를 지원하지 않음

따라서 중간 테이블을 만들어서 1:N, N:1로 풀어야 함
💡 N:M 단방향, 양방향
🔹 단방향 예시
@ManyToMany
@JoinTable(
name = "tutor_language",
joinColumns = @JoinColumn(name = "tutor_id"),
inverseJoinColumns = @JoinColumn(name = "language_id")
)
private List<Language> languages = new ArrayList<>();
tutor_language라는 중간 테이블 자동 생성Tutor → Language 참조 가능🔹 양방향 예시
@ManyToMany(mappedBy = "languages")
private List<Tutor> tutors = new ArrayList<>();
Language에서도 Tutor를 참조 가능mappedBy를 통해 반대쪽은 읽기 전용@OneToOne 양방향과 비슷하지만, 내부적으로는 중간 테이블 사용됨⚠️ N:M 매핑의 문제점
💡 @ManyToMany는 편리해 보이지만 실제로는 문제점이 많음
1. 추가 데이터 불가
Tutor와 Language 사이에 level, license 같은 속성을 넣을 수 없음@ManyToMany는 단순 연결만 가능2. 숨겨진 중간 테이블
3. 복합 PK 문제
tutor_id, language_id) 복합 키로 생성됨✅ 문제 해결 방법
→ 중간 테이블을 실제 엔티티로 만들어서 관리하면 됨
🔹 중간 테이블 엔티티 (예: TutorLanguage)
@Entity
@Table(name = "tutor_language")
public class TutorLanguage {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 단일 PK
@ManyToOne
@JoinColumn(name = "tutor_id")
private Tutor tutor;
@ManyToOne
@JoinColumn(name = "language_id")
private Language language;
private Integer level; // 추가 속성
private String license; // 추가 속성
}
🔹 Tutor 엔티티
@OneToMany(mappedBy = "tutor")
private List<TutorLanguage> tutorLanguages = new ArrayList<>();
🔹 Language 엔티티
@OneToMany(mappedBy = "language")
private List<TutorLanguage> tutorLanguages = new ArrayList<>();
🚀 실행 결과
tutor_language 테이블이 엔티티로 생성됨level, license 같은 부가 정보도 함께 저장 가능🧠 요약 정리
| 구분 | @ManyToMany 직접 사용 | 중간 엔티티(TutorLanguage) 사용 |
|---|---|---|
| 🔗 구조 | 자동 중간 테이블 생성 | 중간 테이블을 엔티티로 정의 |
| 📝 추가 속성 | 불가능 ❌ | 가능 (예: level, license) ✅ |
| ⚡ SQL 제어 | 제약 많음, 숨겨짐 | 명시적 제어 가능 |
| 🔑 PK 구조 | 복합키 (tutor_id, language_id) | 단일 PK (id) |
| 🛠️ 유지 보수 | 확장 어려움 | 유연하고 확장성 높음 |
| ✅ 실무 활용 | 거의 사용하지 않음 | 권장되는 방식 |