N:M(다 대 다) 매핑 2: @ManyToMany와 @JoinTable
2021.05.20 - [JPA/양방향연관관계] - N:M(다 대 다) 매핑 1: 매핑테이블
N:M(다 대 다) 매핑 1: 매핑테이블
RDB에서는 정규화된 테이블 2개로 N:M 관계를 매핑하는 것이 불가능하다. (정규화란 Table을 아름답게 만드는 과정(?)이다. 정규화에 대해 더 궁금하다면 검색을!!!) 따라서 매핑테이블을 따로 추가
code-mania.tistory.com
Entity 설계를 보기 전에 테이블이 어떻게 설계됐는지 여기서 먼저 확인해보자~~~
@ManyToMany와 @JoinTable
이번 글에서는 'N:M(다 대 다) 매핑 1: 매핑테이블'에서 설계했던 테이블을 Entity로 만들어볼 것이다.
@ManyToMany와 @JoinTable 어노테이션을 사용해서 N:M 양방향 연관관계를 매핑해보겠다.
각 Entity에서 연관관계를 매핑하는 부분만 간단하게 살펴보겠다.
Student Entity
package hellojpa;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Data
public class Student {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
@ManyToOne
@JoinColumn(name = "CLUB_ID", insertable = false, updatable = false)
private Club club;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
Locker locker;
@ManyToMany
@JoinTable(name = "StudentSubject",
joinColumns = @JoinColumn(name = "STUDENT_ID"),
inverseJoinColumns = @JoinColumn(name = "SUBJECT_ID"))
private List<Subject> subjects = new ArrayList<>();
}
N:M 관계를 매핑하므로, subjects의 Type을 List로 해줘야 한다.
@ManyToMany Annotation은 N:M 관계 매핑에서 사용하는 어노테이션이고,
@JoinTable은 매핑테이블을 설정하는 어노테이션이다.
(테이블명은 StudentSubject로 하였고, Student Table을 참조하는 컬럼명은 STUDENT_ID, 반대쪽의 Subject Table을 참조하는 컬럼명은 SUBEJCT_ID로 지정하였다.)
Subject Entity
package hellojpa;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Data
@Entity
public class Subject {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "subjects")
private List<Student> students = new ArrayList<>();
}
여기에도 students에 @ManyToMany 어노테이션이 달린 것을 볼 수 있다.
대신 Subject Entity의 students 필드는 현재 관계에서 주가 아닌 종이기때문에 mappedBy 옵션이 달려있는 것을 볼 수 있다.
아직 양방향 연관관계에서의 주와 종이 뭔지 모른다면 이 글을 참고하자!!!
N:1 양방향 연관관계 매핑하기
길지 않으니 이 글을 먼저 읽어보길 권장한다!!! code-mania.tistory.com/35 DB와 객체의 양방향 연관관계 차이 알아보기 이번 시간에는 양방향 연관관계에 대해 알아보겠다!!!! (좀 빡센 거 같다...) 단방
code-mania.tistory.com
JpaMain
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 수학 과목 등록
Subject math = new Subject();
math.setName("수학");
em.persist(math);
// 한국사 과목 등록
Subject history = new Subject();
history.setName("한국사");
em.persist(history);
// code-mania 학생 등록
Student codeMania = new Student();
codeMania.setName("code-mania");
codeMania.setAge(21);
codeMania.getSubjects().add(math); // 수학 과목 수강
codeMania.getSubjects().add(history); // 역사 과목 수강
em.persist(codeMania);
// code-lover 학생 등록
Student codeLover = new Student();
codeLover.setName("code-lover");
codeLover.setAge(21);
codeLover.getSubjects().add(history); // 역사 과목 수강
em.persist(codeLover);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
em.close();
emf.close();
}
}
JpaMain을 실행시키고, h2에서 결과를 확인해보면 매핑테이블인 StudentSubject Table에 데이터가 잘 들어간 것을 볼 수 있다.
Student Entity에서 Subject Entity를 List로 관리하므로 한 학생이 여러 과목을 수강할 수 있다.
다른 말로 Student:Subject=N:M 관계가 성립하게 된 것이다.
그런데 지금의 매핑테이블 형태는 우리가 처음에 살펴봤던 매핑테이블 형태와 다르다.
현재 매핑테이블은 @JoinTable에 의해 JPA가 자동으로 생성했고, 그 과정에서 최소한의 컬럼만이 정의됐다.
하지만 우리가 설계했던 Join Table에 PK를 위한 컬럼이 있었다.
또한 나중에 필요에 의해서 수강시작날짜 체크를 위한 컬럼을 추가해서 관리하게 될 수도 있는데,
최소한의 컬럼만을 만들어주는 @JoinTable 어노테이션으로 위와 같은 경우들을 해결하는 것은 불가능하다.
이러한 이유로 어떤 상황이 있을지 모르는 실무에서 @JoinTable은 봉인하는 게 좋다.
매핑테이블을 Entity로 따로 만들어 사용하는 것이 바람직한 방법이고, 다음 글에서는 이에 대해 알아보겠다!
git: git 주소(ManyToMany branch)
참고강의: 배달의 민족 개발팀장 김영한 강사님의 JPA 강의