JPA - 다대다 연관관계

이유석·2023년 1월 13일
1

JPA - Entity

목록 보기
9/14
post-thumbnail

다대다 연관관계를 회원(Member)과 상품(Product)을 예시로 설명해보도록 하겠습니다.

  • 회원과 상품이 있습니다.
  • 회원은 여러 종류의 상품을 주문할 수 있습니다.
  • 상품은 다수의 회원에 의해 주문될 수 있습니다.
  • 즉, 회원과 상품은 다대다(N:M)의 관계입니다.

다대다 연관관계

JPA 에서 @ManyToMany 를 통해 다대다 연관관계를 제공하지만, 결론은 사용하지 않는 것 을 추천합니다.
그렇다면, 다대다 연관관계는 어떻게 객체로 표현할 수 있을까요??

  • 연결 테이블(조인 테이블)을 추가하여 일대다, 다대일 관계로 풀어내야 합니다.

테이블 모델링

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없습니다.

객체 모델링 (다대다 양방향)

객체는 컬렉션을 사용해서 객체 2개로 다대다 관계를 표현할 수 있습니다.

  • 회원 클래스(Member) 는 Member.products 필드를 통해서 회원이 주문한 상품 목록(List<Product>)에 접근할 수 있습니다.
  • 상품 클래스(Product) 는 product.members 필드를 통해서 상품을 주문한 회원 목록(List<Member>)에 접근할 수 있습니다.

@ManyToMany

@ManyToMany 를 사용한 다대다 연관관계 매핑 방법을 살펴보며, 왜 사용하지 않는 것 을 추천하는지 알아보도록 하겠습니다.

다대다 단방향

Member 클래스 (다대다 단방향 연관관계의 주인입니다.)

@Entity
public class Member {

	@Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @ManyToMany
    @JoinTable(name = "member_product,
    		  joinColumns = @JoinColumn(name = "MEMBER_ID"),
              inverseJoinColumn = @JoinColumn(name = "PRODUCT_ID"))
    private List<Product> products = new ArrayList<>();
    
    @Column(name = "USERNAME")
    private String username;

	// Getter, Setter, Constructor
}

Produdct 클래스

@Entity
public class Product {
	
    @Id
    @Column(name = "PRODUCT_ID")
    private Long id;
    
    @Column(name = "NAME")
    private String name;
}

다대다 단방향 매핑에서는 연관관계의 주인에 해당하는 클래스에 반대편 클래스를 Collection 프레임워크로 감싼 형태의 참조 필드로 작성해주시면 됩니다.
이때, 해당 참조 필드위에 @ManyToMany@JoinTable(name = "연결 테이블 이름")을 추가하여 줍니다.

@ManyToMany

다대다 관계에서 사용합니다.
조인 테이블을 생성합니다.

속성기능기본값
mappedBy연관관계의 주인 필드를 선택한다.
fetch글로벌 패치 전략을 설정한다.
(자세한 내용은 추후에)
FetchType.LAZY (지연 로딩)
cascade영속성 전이 기능을 사용한다.
(자세한 내용은 추후에)
targetEntity연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않음

@JoinTable

연결 테이블을 매핑할 때 사용합니다.

속성기능기본값
name연결 테이블의 이름을 지정한다.
joinColumns연결 테이블에 존재하는 다대다 연관관계의 주인에 해당하는 외래키를 매핑한다.
inverseJoinColumns연결 테이블에 존재하는 다대다 연관관계의 주인이 아닌 객체에 해당하는 외래키를 매핑한다.
이외에도 catalog, schema, uniqueConstraints 의 속성이 존재합니다.

다대다 양방향

@Entity
public class Member {

	@Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @ManyToMany
    @JoinTable(name = "member_product,
    		  joinColumns = @JoinColumn(name = "MEMBER_ID"),
              inverseJoinColumn = @JoinColumn(name = "PRODUCT_ID"))
    private List<Product> products = new ArrayList<>();
    
    @Column(name = "USERNAME")
    private String username;

	// Getter, Setter, Constructor
}

Produdct 클래스

@Entity
public class Product {
	
    @Id
    @Column(name = "PRODUCT_ID")
    private Long id;
    
    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
    
    @Column(name = "NAME")
    private String name;

    // Getter, Setter, Constructor
}

다대다 양방향 매핑에서는 다대다 단방향 코드에 추가로 연관관계의 주인이 아닌 클래스에 반대편 클래스(연관관계 주인)를 Collection 프레임워크로 감싼 형태의 참조 필드로 작성해주시면 됩니다.
이때, 해당 참조 필드위에 @ManyToMany(mappedBy = "반대쪽 매핑의 필드 이름값")을 추가하여 연관관계의 주인을 지정하여 줍니다.

@ManyToMany 의 한계

@ManyToMany 를 사용하면 편리한 점

  • 연결 테이블을 자동으로 생성 및 설정해줍니다.
  • 도메인 모델이 단순해집니다.

하지만 실무에서 @ManyToMany 매핑은 사용하기에는 한계가 있습니다.

  • 연결 테이블은 단순히 연결만 하고 끝나지 않습니다.
    연결 테이블에 추가 정보를 필요로 할 때 가 있습니다.
  • 또한, 연결 테이블이 숨겨져 있기 때문에 예상하지 못한 쿼리들이 실행됩니다.

@ManyToMany 의 한계 극복

연결 테이블용 엔티티를 생성 후,
@ManyToMany 를 @ManyToOne 과 @OneToMany 를 활용하여 일대다, 다대일 관계로 풀어줍니다.

다대다 (일대다, 다대일) 단방향

  • MySQL 에서 ORDER 는 예약어 이기 때문에, 테이블 이름을 수정해주어야 합니다. (예 - ORDERS)

즉, 위와같이 @ManyToMany 매핑을 @ManyToOne 과 @OneToMany 를 사용하여 코드를 작성합니다.

Member 클래스 (기존 다대다 단방향 매핑 시, 연관관계의 주인 입니다.)

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

	// 연결 테이블(MEMBER_PRODUCT)쪽이 외래키를 갖고있기 때문에, 연결 테이블이 연관관계의 주인이다.
    @OneToMany(mappedBy = "member") 
    private List<MemberProduct> memberProducts = new ArrayList<>();

    @Column(name = "USERNAME")
    private String username;

    // Getter, Setter, Constructor

}

Produdct 클래스

@Entity
public class Product {
	
    @Id
    @Column(name = "PRODUCT_ID")
    private Long id;

    @Column(name = "NAME")
    private String name;

    // Getter, Setter, Constructor
}

MemberProduct 클래스 (다대다 에서 연결 테이블)

@Table(name = "ORDERS")
@Entity
public class MemberProduct {

    @Id
    @Column(name = "ORDER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    @Column(name = "ORDERAMOUNT")
    private Integer orderAmount;

    @Column(name = "ORDERDATE")
    private LocalDateTime orderDate;
    
    // Getter, Setter, Constructor
}

연관관계 사용에 대한 예시 코드는 아래 링크를 참고해주시기 바랍니다.
소스 코드

다대다 (일대다, 다대일) 양방향

Member 클래스

@Entity
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

	// 연결 테이블(MEMBER_PRODUCT)쪽이 외래키를 갖고있기 때문에, 연결 테이블이 연관관계의 주인이다.
    @OneToMany(mappedBy = "member") 
    private List<MemberProduct> memberProducts;

    @Column(name = "USERNAME")
    private String username;

    // Getter, Setter, Constructor

}

Produdct 클래스

@Entity
public class Product {
	
    @Id
    @Column(name = "PRODUCT_ID")
    private Long id;
    
    // 연결 테이블(MEMBER_PRODUCT)쪽이 외래키를 갖고있기 때문에, 연결 테이블이 연관관계의 주인이다.
    @OneToMany(mappedBy = "product") 
    private List<MemberProduct> memberProducts;

    @Column(name = "NAME")
    private String name;

    // Getter, Setter, Constructor
}

MemberProduct 클래스 (다대다 에서 연결 테이블)

@Table(name = "ORDERS")
@Entity
public class MemberProduct {

    @Id
    @Column(name = "ORDER_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    @Column(name = "ORDERAMOUNT")
    private Integer orderAmount;

    @Column(name = "ORDERDATE")
    private LocalDateTime orderDate;
    
    // Getter, Setter, Constructor
}

연관관계 사용에 대한 예시 코드는 아래 링크를 참고해주시기 바랍니다.
소스 코드

profile
https://github.com/yuseogi0218

0개의 댓글