3 분 소요

📌 다대다 [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();
    }
}

참고 자료

댓글남기기