[JPA] 다대다[N:M] 연관관계 매핑
📌 다대다 [N:M]
✔ 목차
- 연관관계 매핑시 고려사항 3가지
- 다대일 [N:1]
- 일대다 [1:N]
- 일대일 [1:1]
- 다대다 [N:M] 🚀
- 실전 예제 - 3. 다양한 연관관계 매핑
✔ 다대다 [N:M]
✅ 다대다 관계: 테이블
- 관계형 데이터베이스는 2개의 정규화된 테이블로 다대다 관계를 표현할 수 없다.
- 다대다 관계는 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어야한다.
- 중간 테이블을 만들어 관계를 연결한다.
✅ 다대다 관계: 객체
- 객체는 컬렉션을 사용하여 객체 2개로 다대다 관계 표현이 가능하다.
@Entity
@Table(name = "MEMBER")
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
//.. 중략
}
@Entity
@Table(name = "PRODUCT")
public class Product {
@Id
@GeneratedValue
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
/* Member Entity의 products 객체 참조 */
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
//.. 중략
}
- Member는 products, Product는 members를 가진다.
- @JoinTable(name = “테이블명”)을 통해 중간 테이블 지정.
Hibernate:
create table Member (
MEMBER_ID bigint not null,
USERNAME varchar(255),
LOCKER_ID bigint,
TEAM_ID bigint,
primary key (MEMBER_ID)
)
Hibernate:
create table MEMBER_PRODUCT (
Member_MEMBER_ID bigint not null,
products_id bigint not null
)
Hibernate:
create table Product (
id bigint not null,
name varchar(255),
primary key (id)
)
Hibernate:
alter table MEMBER_PRODUCT
add constraint FKl133ngpiayquuf2cu1njdq5hy
foreign key (products_id)
references Product
Hibernate:
alter table MEMBER_PRODUCT
add constraint FK10kgxdeb5bnrb5vonj4x9qtir
foreign key (Member_MEMBER_ID)
references Member
- 또한 위 사진을 보면 MEMBER_PRODUCT 테이블이 생성된 것을 볼 수 있다.
- foreign key (products_id), foreign key (Member_MEMBER_ID) 주목.
✅ 다대다 관계 @어노테이션
- @ManyToMany 사용
- @JoinTable로 연결 테이블 지정
- 다대다 매핑
- 단방향, 양방향 둘 다 가능
❌ 다대다 매핑의 한계
- 편리해 보이지만 실무에서 사용할 수 있는것이 아니다.
- 연결 테이블이 단순히 연결만하고 끝나지 않는다.
- 주문시간, 수량 같은 부가적인 데이터가 들어올 수 있음.
- 즉, 원하는 정보가 아닌 추가적인 정보가 들어갈 수 있다.
- 쿼리가 이상하게 나가는 현상이 발생할 수 있다.
- 다대다 연관관계 매핑은 사용을 지양하는것을 추천한다.
⚔️ 다대다 매핑 한계 극복
- 연결 테이블용 엔티티를 하나 추가한다(연결테이블을 엔티티로 승격시킨다)
- 즉, MemberProduct 엔티티(테이블)을 하나를 중간에 생성하는것을 의미한다.
- @ManyToMany -> @OneToMany, @ManyToOne으로 변경한다.
🔨 다대다 매핑 한계 극복 예시
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
// 💡 Before
// @ManyToMany
// @JoinTable(name = "MEMBER_PRODUCT")
// private List<Product> products = new ArrayList<>();
// 🔨 After
@OneToMany(mappedBy = "member")
private List<MemberProduct> memberProducts = new ArrayList<>();
//.. 중략
}
- @ManyToMany -> @OneToMany로 변경한다.
- @JoinTable 제거한다.
- 연관관계의 주인은 MemberProduct 테이블의 member가 된다.
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
// 💡 Before
// @ManyToMany(mappedBy = "products")
// private List<Member> members = new ArrayList<>();
// 🔨 After
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
//.. 중략
}
- @ManyToMany -> @OneToMany로 변경한다.
- 연관관계의 주인은 MemberProduct 테이블의 product가 된다.
@Entity
public class MemberProduct {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name="MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name="PRODUCT_ID")
private Product product;
// 추가적으로 컬럼을 설정할 수 있다.
private int count;
private int price;
private LocalDateTime orderDateTime;
}
- MemberProduct 엔티티를 하나 생성하여 다대다 연관관계 매핑 설정.
- 일대다(1:N), 다대일(N:1) 관계가 성립된다.
💡 다대다 한계 극복 정리
지금까지 다대다 연관관계의 한계를 극복하기위한 예제를 살펴보았다. 여기서 핵심은 기존에는 중간 테이블(엔티티)를 만들지 않고 @JoinTable(name = "MEMBER_PRODUCT")를 선언하여 다대다 매핑을 진행 하였는데 현재는 MemberProduct 엔티티를 중간 테이블로 설정 하여 매핑했다는 차이점이 존재한다.
🚀 간단한 실습
public class JpaMainSample {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
/* MemberSample */
MemberSample member = new MemberSample();
member.setUsername("테스트유저1");
em.persist(member); // member 등록
/* Product */
Product product = new Product();
product.setName("전자레인지");
em.persist(product); // product 등록
/* MemberProduct */
MemberProduct memberProduct = new MemberProduct();
memberProduct.setCount(1); // 수량
memberProduct.setPrice(50000); // 가격
memberProduct.setMember(member); // member 셋팅
memberProduct.setProduct(product); // product 셋팅
em.persist(memberProduct);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
댓글남기기