Hibernate/JPA(EntityManager)

[JPA] @ManyToMany와 한계점 극복

유혁스쿨 2023. 6. 29. 12:42
728x90
반응형

관계형 데이터베이스는 정규화된 테이블 2개만으로 M:N 관계를 표현할 수 없다.

중간에 연결 테이블을 추가해서 1:M M:1 관계로 풀어내야 한다.

 

User (회원)이 있고 Product (상품)이 있다고 가정해보자.

하나의 회원은 여러개의 상품을 구매할 수 있고, 하나의 상품은 여러 회원에 의해 구매될 수 있는 상황이 발생한다.

 

반면, 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능해진다.

User는 List<Product>로 Product들을 가질수 있고, Product는 List<User>로 User들을 가질 수 있다.

@ManyToMany을 사용하고, @JoinTable을 사용하여 연결 테이블을 지정할 수 있게 된다.

@ManyToMany
@JoinTable(name = "USER_PRODUCT")
private List<Product> products = new ArrayList<>();

위와같이 지정하고 실행하면 아래와 같이 user_product 테이블이 생성되고, 

user_product테이블에 user와 product의 PK를 각각 PK이면서 FK로 지정이된다.

 

양방향으로 지정하기 위해서는 Product 클래스에 아래와 같이 양방향으로 지정해주면된다.

@ManyToMany(mappedBy = "products")
private List<User> users = new ArrayList<>();

XToOne과 똑같은데, 중간에 테이블이 생성된다는 차이가 있다.

 

@ManyToMany 매핑의 한계

편리해 보이지만 JPA를 사용할때는 실무에서 잘 사용하지 않는다.

연결 테이블이 단순히 연결만 하고 끝나지 않는다.

DB관점에서 봤을때 연결 테이블에는 주문시간, 수량 등 추가적인 데이터가 들어올 수 있다.


@ManyToMany 매핑의 한계 극복

연결 테이블 자체를 엔티티 승격화 개념으로 연결테이블용 엔티티를 추가한다.

@OneToMnay, @ManyToOne으로 풀어낸다.

 

@Entity
@Getter @Setter
@ToString
public class UserProduct {

    @Id @GeneratedValue
    private Long id;
    @ManyToOne
    @JoinColumn(name = "USER_ID")
    private User user;
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    private int count;
    private int price;
    private LocalDateTime orderDateTime;
    
}
@Entity(name = "USERS")
@Setter
@Getter
@ToString
public class User {
    @Id @GeneratedValue
    @Column(name = "USER_ID")
    private Long id;
    private String username;

    @OneToMany(mappedBy = "user")
    private List<UserProduct> products = new ArrayList<>();
}
@Entity
@Getter @Setter
@ToString
public class Product {
    @Id @GeneratedValue
    @Column(name = "PRODUCT_ID")
    private Long id;
    private String productname;

    @OneToMany(mappedBy = "product")
    private List<UserProduct> users = new ArrayList<>();
}

user_id와 product_id를 Composite하게 PK로 잡아주는데 이렇게 되면 Composite PK 인덱스를 생성해 줘야 한다.

(PK 라는게 연관되는 다른 테이블에 종속적으로 걸리게되면 시스템을 변경할때 유연하지 못한 상황이 발생한다.)

이것보다는 단순히 FK로 설계하고, PK는 되도록 비즈니스적으로 의미없는 값(UUID/DtKey)을 쓰는게 실무적으로 유연성이 생긴다.

(PK제약에서 벗어나므로 필요할때 제약을 걸어줌으로써 유연해진다.) 

 

결론적으로 실무에서 비즈니스는 단순하게 @ManyToMany 어노테이션 만으로 풀어낼 수 있지 않다.

따라서, 연결 테이블을 엔티티로 승격해야 한다.

 

 

728x90
반응형